diff --git a/.clang-format b/.clang-format index a8054814d54..c3a9fa31eb5 100644 --- a/.clang-format +++ b/.clang-format @@ -3,9 +3,11 @@ AllowShortFunctionsOnASingleLine: Inline AlwaysBreakTemplateDeclarations: Yes BasedOnStyle: LLVM BreakConstructorInitializers: BeforeComma -ColumnLimit: 100 +ColumnLimit: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: true IndentWidth: 4 +IndentAccessModifiers: false +AccessModifierOffset: -4 PointerAlignment: Left IncludeCategories: - Regex: '^"\.\./' diff --git a/.clang-tidy b/.clang-tidy index f948363fdb9..1f6b1903372 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,21 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize-*,portability-*,llvm-*,-llvm-include-order,-modernize-use-nodiscard,-llvm-header-guard' -WarningsAsErrors: 'clang-diagnostic-*,clang-analyzer-*,portability-*,llvm-*' -HeaderFilterRegex: 'TrustWallet|^src|^./src' +Checks: > + clang-diagnostic-*, + clang-analyzer-*, + modernize-*, + portability-*, + llvm-*, + -clang-analyzer-core.uninitialized.Assign, + -llvm-include-order, + -llvm-header-guard, + -llvm-else-after-return, + -modernize-use-using, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + -modernize-deprecated-headers, + -modernize-avoid-c-arrays +WarningsAsErrors: false +HeaderFilterRegex: '^src|^./src' AnalyzeTemporaryDtors: false FormatStyle: none InheritParentConfig: 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..4838120c78f 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 +* @satoshiotomakan @gupnik +kotlin/ @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/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7f3a3e9b492..4ccb8163762 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ -## Testing instructions +## How to test @@ -20,8 +20,11 @@ -- [ ] Prefix PR title with `[WIP]` if necessary. +- [ ] Create pull request as draft initially, unless its complete. - [ ] Add tests to cover changes as needed. - [ ] Update documentation as needed. -- [ ] I have read the [guidelines](https://developer.trustwallet.com/wallet-core/newblockchain#integration-criteria) for adding a new blockchain -- [ ] If there is a related Issue, mention it in the description (e.g. Fixes # ). +- [ ] If there is a related Issue, mention it in the description. + +If you're adding a new blockchain + +- [ ] I have read the [guidelines](https://developer.trustwallet.com/wallet-core/newblockchain#integration-criteria) for adding a new blockchain. diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index dc90e5a1c3a..00000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: wontfix -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 4724d5602a3..a9c6bd8678b 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -2,39 +2,84 @@ 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-latest-large + 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' - runs-on: macos-10.15 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + gradle-version: 8.10.2 - steps: - - uses: actions/checkout@v2 - name: Install system dependencies - run: brew install boost ninja - - name: Install Android Dependencies run: | - $ANDROID_HOME/tools/bin/sdkmanager --verbose "cmake;3.10.2.4988404" "ndk;21.2.6472646" - $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-26;google_apis;x86" - - name: Accept Licenses - run: echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/tools/bin/sdkmanager --licenses + 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') }}-${{ hashFiles('tools/dependencies-version') }} + - 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..d7bde064e0d --- /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-24.04 + 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.8 + + - 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..336faa4ce81 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,29 +2,25 @@ 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 jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 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/flutter-ci.yml b/.github/workflows/flutter-ci.yml new file mode 100644 index 00000000000..e044e2d7b77 --- /dev/null +++ b/.github/workflows/flutter-ci.yml @@ -0,0 +1,70 @@ +name: Flutter 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: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - 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 }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + - 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: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Code generation + run: | + tools/generate-files native + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: '3.8.1' + cache: true + cache-key: dart-3.8.1 + cache-path: ${{ github.workspace }}/.pub-cache + + - name: Install Dart dependencies + run: | + cd flutter + dart pub get + dart pub upgrade + + - name: Flutter build + run: | + tools/flutter-build + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 4d922a6db57..2a18b249f43 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-10.15 + runs-on: macos-15-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 + 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') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + - 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..1201f224085 --- /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.10.2 + + - 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 }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + + - 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..aaa8070d8ce --- /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-14-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-sonarcloud.yml b/.github/workflows/linux-ci-sonarcloud.yml new file mode 100644 index 00000000000..f7e4643f343 --- /dev/null +++ b/.github/workflows/linux-ci-sonarcloud.yml @@ -0,0 +1,68 @@ +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 + # Temporarily disabled due to issues with SonarCloud account. + if: false + runs-on: ubuntu-24.04 + 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 }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + + - 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 34fc750a2e0..acda9dcfe10 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-24.04 + if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install system dependencies run: | - # build-essential libboost-all-dev clang-9 ruby-full cmake - sudo apt-get install libc++-dev libc++abi-dev ninja-build lcov llvm clang-tidy libboost-all-dev + tools/install-sys-dependencies-linux + 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 }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: | tools/install-dependencies @@ -28,36 +33,35 @@ 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 - env: - CC: /usr/bin/clang - CXX: /usr/bin/clang++ - - name: Prepare build with lint - if: github.ref == 'refs/heads/master' - run: | - cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCLANG_TIDY=ON -DCODE_COVERAGE=ON -DCLANG_ASAN=ON + tools/generate-files native env: CC: /usr/bin/clang CXX: /usr/bin/clang++ - - name: Prepare build without lint - if: github.ref != 'refs/heads/master' + - name: CMake (coverage/clang-tidy/clang-asan) 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++ CK_TIMEOUT_MULTIPLIER: 4 - - name: Gather code coverage + - name: Gather and check code coverage run: | sudo rm -rf coverage.info tools/coverage diff --git a/.github/workflows/linux-sampleapp-ci.yml b/.github/workflows/linux-sampleapp-ci.yml new file mode 100644 index 00000000000..66a7943858c --- /dev/null +++ b/.github/workflows/linux-sampleapp-ci.yml @@ -0,0 +1,95 @@ +name: Linux SampleApps 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: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - 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 }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + - 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 + run: | + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_UNITY_BUILD=ON + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + - name: Build + run: | + make -Cbuild -j12 TrustWalletCore + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + CK_TIMEOUT_MULTIPLIER: 4 + - name: Build and run C++ sample app + run: | + cd samples/cpp + cmake . -DWALLET_CORE=../../ -DCMAKE_BUILD_TYPE=Debug + make + ./sample + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + - name: Install Go + env: + GO_VERSION: 1.19 + GO_ARCH: amd64 + run: | + curl -O -L "https://golang.org/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + tar -xf "go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + sudo chown -R root:root ./go + sudo mv -v go /usr/local + - name: Build and run GoLang sample app + run: | + cd samples/go + go version + go build -o main + ./main + 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/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000000..cf0b7dfe462 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,263 @@ +name: Rust CI + +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. + rust-lints: + permissions: + contents: read + checks: write + runs-on: ubuntu-24.04 + 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.8 + + - 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 + + # Run Rust tests in WASM. + test-wasm: + runs-on: ubuntu-24.04 + 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.8 + + - 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.8 + + - 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' + + memory-profiler: + runs-on: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.8 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install llvm + run: | + # to get the symbolizer for debug symbol resolution + sudo apt install llvm + + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Enable debug symbols + run: | + cd rust + # to fix buggy leak analyzer: + # https://github.com/japaric/rust-san#unrealiable-leaksanitizer + # ensure there's a profile.dev section + if ! grep -qE '^[ \t]*[profile.dev]' Cargo.toml; then + echo >> Cargo.toml + echo '[profile.dev]' >> Cargo.toml + fi + # remove pre-existing opt-levels in profile.dev + sed -i '/^\s*\[profile.dev\]/,/^\s*\[/ {/^\s*opt-level/d}' Cargo.toml + # now set opt-level to 1 + sed -i '/^\s*\[profile.dev\]/a opt-level = 1' Cargo.toml + cat Cargo.toml + + - name: cargo test -Zsanitizer=address + # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 + run: | + cd rust + cargo test --lib --tests --all-features --target x86_64-unknown-linux-gnu + env: + ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0" + RUSTFLAGS: "-Z sanitizer=address" + + - name: cargo test -Zsanitizer=leak + if: always() + run: | + cd rust + cargo test --all-features --target x86_64-unknown-linux-gnu + env: + RUSTFLAGS: "-Z sanitizer=leak" + + coverage: + runs-on: ubuntu-24.04 + 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.8 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies dev + + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: | + cd rust + cargo generate-lockfile + + - name: Run tests + run: | + tools/rust-coverage + + - name: Run Doc tests + run: | + tools/rust-test doc + + - name: Record Rust version + run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" + + # TODO: Uncomment this when we have a codecov token + # - name: Upload to codecov.io + # uses: codecov/codecov-action@v5 + # with: + # fail_ci_if_error: true + # token: ${{ secrets.CODECOV_TOKEN }} + # env_vars: OS,RUST + + - name: Gather and check Rust code coverage + run: | + tools/check-coverage rust/coverage.stats rust/lcov.info diff --git a/.github/workflows/ts-ci.yml b/.github/workflows/ts-ci.yml deleted file mode 100644 index acc17b1602e..00000000000 --- a/.github/workflows/ts-ci.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Typescript CI - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: yarn install - working-directory: typescript - - name: Build and test - run: yarn build && yarn test - working-directory: typescript diff --git a/.github/workflows/wasm-ci.yml b/.github/workflows/wasm-ci.yml new file mode 100644 index 00000000000..3422e664796 --- /dev/null +++ b/.github/workflows/wasm-ci.yml @@ -0,0 +1,60 @@ +name: Wasm 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: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + + - name: Install system dependencies + run: | + 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@v3 + with: + path: build/local + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + + - 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: | + source emsdk/emsdk_env.sh + tools/generate-files wasm + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + + - name: Build for Wasm + run: | + source emsdk/emsdk_env.sh + tools/wasm-build + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + + - name: Test + run: | + npm install && npm run build-and-test + working-directory: wasm diff --git a/.gitignore b/.gitignore index 75a13e6cdf7..b5eaf7dd735 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ cmake-build-debug/ .cquery_cache/ .cxx/ .cache/ +build_pvs_studio/ # Dependencies node_modules @@ -22,17 +23,43 @@ lib/protobuf .vscode/ .project .history/ +*.xcuserdatad/ # Generated files +jni/android/generated jni/cpp/generated jni/java/wallet/core/jni +jni/proto/wallet +jni/dokka-out swift/Sources/Generated swift/wallet-core/ -src/Generated +codegen-v2/bindings/ + +src/Generated/*.h +src/Generated/*.cpp include/TrustWalletCore/TWHRP.h include/TrustWalletCore/TW*Proto.h +include/TrustWalletCore/TWEthereumChainID.h + +# Generated +include/TrustWalletCore/TWTONAddressConverter.h +include/TrustWalletCore/TWFFITest.h +include/TrustWalletCore/TWTONWallet.h +include/TrustWalletCore/TWTONMessageSigner.h +include/TrustWalletCore/TWMessageSigner.h +include/TrustWalletCore/TWWalletConnectRequest.h +include/TrustWalletCore/TWSolanaTransaction.h +include/TrustWalletCore/TWCryptoBoxPublicKey.h +include/TrustWalletCore/TWCryptoBoxSecretKey.h +include/TrustWalletCore/TWEthereum.h +include/TrustWalletCore/TWBarz.h + +# Wasm +emsdk/ +wasm-build/ # Code coverage files +lcov.info coverage.info coverage/ swift/test_output/ @@ -43,11 +70,16 @@ swift/test_output/ # Samples -- iOS Pods/ + # Samples -- C++ # build samples/cpp/CMakeFiles samples/cpp/CMakeCache.txt samples/cpp/Makefile samples/cpp/*.cmake + # built binary samples/cpp/sample + +# Rust target build +**/target/ diff --git a/CMakeLists.txt b/CMakeLists.txt index d0e086019c1..34a75490016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,49 +1,43 @@ -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. -project(TrustWalletCore) - -include(GNUInstallDirs) +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) -# 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} -stdlib=libc++ ${TW_CXX_WARNINGS}") -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) +project(TrustWalletCore) 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) @@ -52,72 +46,109 @@ 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) - if(CLANG_TIDY_BIN) - set(CMAKE_CXX_CLANG_TIDY clang-tidy;) - 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) +elseif (${FLUTTER}) + message("Configuring for Flutter") + file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) + add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) + 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) @@ -126,8 +157,10 @@ install(TARGETS TrustWalletCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore - FILES_MATCHING PATTERN "*.h") +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/Dockerfile b/Dockerfile index d27fb4a4c5f..082d5fd1969 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM ubuntu:18.04 +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive # Install some basics RUN apt-get update \ @@ -12,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 \ @@ -25,29 +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 -RUN tools/generate-files \ - && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ - && make -Cbuild -j12 +# 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: 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 new file mode 100644 index 00000000000..3278a4e87dd --- /dev/null +++ b/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "WalletCore", + platforms: [.iOS(.v13)], + products: [ + .library(name: "WalletCore", targets: ["WalletCore"]), + .library(name: "WalletCoreSwiftProtobuf", targets: ["WalletCoreSwiftProtobuf"]) + ], + dependencies: [], + targets: [ + .binaryTarget( + name: "WalletCore", + url: "https://github.com/trustwallet/wallet-core/releases/download/4.2.9/WalletCore.xcframework.zip", + checksum: "651894a9418fdd33ae5374367a6a64a57fa92b6e6ffb2d6723c319da97472cb4" + ), + .binaryTarget( + name: "WalletCoreSwiftProtobuf", + url: "https://github.com/trustwallet/wallet-core/releases/download/4.2.9/SwiftProtobuf.xcframework.zip", + checksum: "946efd4b0132b92208335902e0b65e0aba2d11b9dd6f6d79cc8318e2530c9ae0" + ) + ] +) diff --git a/README.md b/README.md index b07270aa340..06ce58f529f 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,38 @@ -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 for Android. +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) -![Docker CI](https://github.com/trustwallet/wallet-core/workflows/Docker%20CI/badge.svg) -![Typescript CI](https://github.com/trustwallet/wallet-core/workflows/Typescript%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) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) +![SPM](https://img.shields.io/badge/SPM-ready-blue) ![Cocoapods](https://img.shields.io/cocoapods/v/TrustWalletCore.svg) -![Cocoapods platforms](https://img.shields.io/cocoapods/p/TrustWalletCore.svg) # Documentation 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 **50** blockchains: Bitcoin, Ethereum, Binance Chain, and most major blockchain platforms. -The full list is [here](docs/coins.md). +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,52 +45,98 @@ If you want to use wallet core in your project follow these instructions. ## Android -Future Android releases will be hosted on [GitHub packages](https://github.com/trustwallet/wallet-core/packages/700258), please checkout [this guide](https://docs.github.com/en/packages/guides/configuring-gradle-for-use-with-github-packages#installing-a-package) for more details. +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) + +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 + +We currently support Swift Package Manager and CocoaPods (will discontinue in the future). + +### SPM -Add this dependency to build.gradle and run `gradle install` +Download latest `Package.swift` from [GitHub Releases](https://github.com/trustwallet/wallet-core/releases) and put it in a local `WalletCore` folder. -```groovy -plugins { - id 'maven' -} +Add this line to the `dependencies` parameter in your `Package.swift`: -dependencies { - implementation 'com.trustwallet:wallet-core:x.y.z' -} +```swift +.package(name: "WalletCore", path: "../WalletCore"), ``` -Replace x.y.z with latest version: -![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) -## iOS +Or add remote url + `master` branch, it points to recent (not always latest) binary release. + +```swift +.package(name: "WalletCore", url: "https://github.com/trustwallet/wallet-core", .branchItem("master")), +``` + +Then add libraries to target's `dependencies`: + +```swift +.product(name: "WalletCore", package: "WalletCore"), +.product(name: "WalletCoreSwiftProtobuf", package: "WalletCore"), +``` -We currently support only CocoaPods. Add this line to your Podfile and run `pod install`: +### CocoaPods + +Add this line to your Podfile and run `pod install`: ```ruby pod 'TrustWalletCore' ``` +## NPM (beta) + +```js +npm install @trustwallet/wallet-core +``` + +## Go (beta) + +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! +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 + +There are a few community-maintained projects that extend Wallet Core to some additional platforms and languages. Note this is not an endorsement, please do your own research before using them: +- Flutter binding https://github.com/weishirongzhen/flutter_trust_wallet_core +- Python binding https://github.com/phuang/wallet-core-python +- Wallet Core on Windows https://github.com/kaetemi/wallet-core-windows # 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/TrustWalletCore.podspec b/TrustWalletCore.podspec deleted file mode 100644 index 457b544a313..00000000000 --- a/TrustWalletCore.podspec +++ /dev/null @@ -1,158 +0,0 @@ -version = '2.5.6' - -Pod::Spec.new do |s| - s.name = 'TrustWalletCore' - s.version = version - s.summary = 'Trust Wallet core data structures and algorithms.' - s.homepage = 'https://github.com/trustwallet/wallet-core' - s.license = 'MIT' - s.authors = { 'Alejandro Isaza' => 'al@isaza.ca' } - s.module_name = 'WalletCore' - - s.ios.deployment_target = '12.0' - s.osx.deployment_target = '10.12' - s.swift_version = '5.1' - - s.source = { - git: 'git@github.com:trustwallet/wallet-core.git' - } - - s.default_subspec = 'Core' - - s.subspec 'Types' do |ss| - ss.source_files = - 'swift/Sources/Types/*.swift', - 'swift/Sources/Generated/Enums/*.swift', - 'swift/Sources/Generated/Protobuf/*.swift' - ss.dependency 'SwiftProtobuf' - end - - s.subspec 'Core' do |ss| - protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.14.0' - include_dir = 'build/local/include' - ss.source_files = - 'src/**/*.{c,cc,cpp,h}', - 'include/**/*.h', - 'swift/Sources/*.{swift,h,m,cpp}', - 'swift/Sources/Extensions/*.swift', - 'swift/Sources/Generated/*.{swift,h}', - 'trezor-crypto/crypto/**/*.{c,h}', - 'trezor-crypto/include/**/*.{h}', - "#{protobuf_source_dir}/src/google/protobuf/any.cc", - "#{protobuf_source_dir}/src/google/protobuf/any.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/any_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/api.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/arena.cc", - "#{protobuf_source_dir}/src/google/protobuf/arenastring.cc", - "#{protobuf_source_dir}/src/google/protobuf/compiler/importer.cc", - "#{protobuf_source_dir}/src/google/protobuf/compiler/parser.cc", - "#{protobuf_source_dir}/src/google/protobuf/descriptor.cc", - "#{protobuf_source_dir}/src/google/protobuf/descriptor.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/descriptor_database.cc", - "#{protobuf_source_dir}/src/google/protobuf/duration.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/dynamic_message.cc", - "#{protobuf_source_dir}/src/google/protobuf/empty.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/extension_set.cc", - "#{protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc", - "#{protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/io_win32.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/printer.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/strtod.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/tokenizer.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/map.cc", - "#{protobuf_source_dir}/src/google/protobuf/map_field.cc", - "#{protobuf_source_dir}/src/google/protobuf/message.cc", - "#{protobuf_source_dir}/src/google/protobuf/message_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/parse_context.cc", - "#{protobuf_source_dir}/src/google/protobuf/reflection_ops.cc", - "#{protobuf_source_dir}/src/google/protobuf/repeated_field.cc", - "#{protobuf_source_dir}/src/google/protobuf/service.cc", - "#{protobuf_source_dir}/src/google/protobuf/source_context.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/struct.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/common.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/int128.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/status.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/structurally_valid.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/strutil.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/substitute.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/time.cc", - "#{protobuf_source_dir}/src/google/protobuf/text_format.cc", - "#{protobuf_source_dir}/src/google/protobuf/timestamp.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/type.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/unknown_field_set.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/field_comparator.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/field_mask_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/json_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/time_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/wire_format.cc", - "#{protobuf_source_dir}/src/google/protobuf/wire_format_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc" - ss.exclude_files = - 'trezor-crypto/crypto/rand.c', - 'swift/Sources/Generated/WalletCore.h' - - ss.public_header_files = - 'include/**/*.h', - 'swift/Sources/*.h' - - ss.preserve_paths = - 'trezor-crypto/crypto/*.{table}', - "#{protobuf_source_dir}/src/**/*.{h,inc}", - "#{include_dir}/nlohmann/**/*.hpp", - 'src/proto/*.proto' - - ss.xcconfig = { - 'HEADER_SEARCH_PATHS' => '$(inherited) ' \ - '${PODS_ROOT}/TrustWalletCore/src ' \ - '${PODS_ROOT}/TrustWalletCore/trezor-crypto/crypto', - 'SYSTEM_HEADER_SEARCH_PATHS' => '$(inherited) ' \ - '/usr/local/include ' \ - '${PODS_ROOT}/TrustWalletCore/include ' \ - '${PODS_ROOT}/TrustWalletCore/trezor-crypto/include ' \ - "${PODS_ROOT}/TrustWalletCore/#{protobuf_source_dir}/src " \ - "${PODS_ROOT}/TrustWalletCore/#{include_dir} ", - 'GCC_WARN_UNUSED_FUNCTION' => 'NO', - 'GCC_WARN_64_TO_32_BIT_CONVERSION' => 'NO', - 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', - 'OTHER_CFLAGS' => '-DHAVE_PTHREAD=1', - 'OTHER_LDFLAGS' => '$(inherited) -fprofile-instr-generate' - } - ss.pod_target_xcconfig = { - 'SYSTEM_HEADER_SEARCH_PATHS' => '$(inherited) /usr/local/include' - } - ss.dependency 'TrustWalletCore/Types' - end - s.prepare_command = 'tools/install-dependencies && tools/generate-files' -end diff --git a/WalletCore.podspec b/WalletCore.podspec new file mode 100644 index 00000000000..ee960c3a1eb --- /dev/null +++ b/WalletCore.podspec @@ -0,0 +1,163 @@ +version = '2.6.5' + +Pod::Spec.new do |s| + s.name = 'WalletCore' + s.version = version + s.summary = 'Trust Wallet core data structures and algorithms.' + s.homepage = 'https://github.com/trustwallet/wallet-core' + s.license = 'MIT' + s.authors = { 'Alejandro Isaza' => 'al@isaza.ca' } + s.module_name = 'WalletCore' + + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.12' + s.swift_version = '5.1' + s.libraries = 'c++' + + s.source = { + git: 'git@github.com:trustwallet/wallet-core.git' + } + + s.default_subspec = 'Core' + + s.subspec 'Types' do |ss| + ss.source_files = + 'swift/Sources/Types/*.swift', + 'swift/Sources/Generated/Enums/*.swift', + 'swift/Sources/Generated/Protobuf/*.swift' + ss.dependency 'SwiftProtobuf' + end + + s.subspec 'Core' do |ss| + protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.20.3' + include_dir = 'build/local/include' + ss.source_files = + 'src/**/*.{c,cc,cpp,h}', + 'include/**/*.h', + 'swift/Sources/*.{swift,h,m,cpp}', + 'swift/Sources/Extensions/*.swift', + 'swift/Sources/Generated/*.{swift,h}', + 'trezor-crypto/crypto/**/*.{c,h}', + 'trezor-crypto/include/**/*.{h}', + "#{protobuf_source_dir}/src/google/protobuf/any.cc", + "#{protobuf_source_dir}/src/google/protobuf/any.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/any_lite.cc", + "#{protobuf_source_dir}/src/google/protobuf/api.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/arena.cc", + "#{protobuf_source_dir}/src/google/protobuf/arenastring.cc", + "#{protobuf_source_dir}/src/google/protobuf/arenaz_sampler.cc", + "#{protobuf_source_dir}/src/google/protobuf/compiler/importer.cc", + "#{protobuf_source_dir}/src/google/protobuf/compiler/parser.cc", + "#{protobuf_source_dir}/src/google/protobuf/descriptor.cc", + "#{protobuf_source_dir}/src/google/protobuf/descriptor.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/descriptor_database.cc", + "#{protobuf_source_dir}/src/google/protobuf/duration.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/dynamic_message.cc", + "#{protobuf_source_dir}/src/google/protobuf/empty.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/extension_set.cc", + "#{protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc", + "#{protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc", + "#{protobuf_source_dir}/src/google/protobuf/generated_message_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc", + "#{protobuf_source_dir}/src/google/protobuf/inlined_string_field.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/io_win32.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/printer.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/strtod.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/tokenizer.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.cc", + "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc", + "#{protobuf_source_dir}/src/google/protobuf/map.cc", + "#{protobuf_source_dir}/src/google/protobuf/map_field.cc", + "#{protobuf_source_dir}/src/google/protobuf/message.cc", + "#{protobuf_source_dir}/src/google/protobuf/message_lite.cc", + "#{protobuf_source_dir}/src/google/protobuf/parse_context.cc", + "#{protobuf_source_dir}/src/google/protobuf/reflection_ops.cc", + "#{protobuf_source_dir}/src/google/protobuf/repeated_field.cc", + "#{protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc", + "#{protobuf_source_dir}/src/google/protobuf/service.cc", + "#{protobuf_source_dir}/src/google/protobuf/source_context.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/struct.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/common.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/int128.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/status.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/structurally_valid.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/strutil.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/substitute.cc", + "#{protobuf_source_dir}/src/google/protobuf/stubs/time.cc", + "#{protobuf_source_dir}/src/google/protobuf/text_format.cc", + "#{protobuf_source_dir}/src/google/protobuf/timestamp.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/type.pb.cc", + "#{protobuf_source_dir}/src/google/protobuf/unknown_field_set.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/field_comparator.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/field_mask_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/json_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/time_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.cc", + "#{protobuf_source_dir}/src/google/protobuf/wire_format.cc", + "#{protobuf_source_dir}/src/google/protobuf/wire_format_lite.cc", + "#{protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc" + + ss.exclude_files = + 'trezor-crypto/include/TrezorCrypto/base58.h', + 'trezor-crypto/crypto/monero', + 'trezor-crypto/crypto/tests', + 'trezor-crypto/crypto/tools', + 'trezor-crypto/crypto/rand.c', + 'swift/Sources/Generated/WalletCore.h' + + ss.public_header_files = + 'include/**/*.h', + 'swift/Sources/*.h' + + ss.preserve_paths = + 'trezor-crypto/crypto/*.{table}', + "#{protobuf_source_dir}/src/**/*.{h,inc}", + "#{include_dir}/nlohmann/**/*.hpp", + 'src/proto/*.proto' + + ss.xcconfig = { + 'HEADER_SEARCH_PATHS' => '$(inherited) ' \ + '$(SRCROOT)/../../wallet-core ' \ + '${SRCROOT}/../../trezor-crypto/crypto ', + 'SYSTEM_HEADER_SEARCH_PATHS' => '$(inherited) ' \ + '/usr/local/include ' \ + '${SRCROOT}/../../include ' \ + '${SRCROOT}/../../../build/local/include ' \ + "${SRCROOT}/../../trezor-crypto/include " \ + "${SRCROOT}/../../protobuf ", + 'GCC_WARN_UNUSED_FUNCTION' => 'NO', + 'GCC_WARN_64_TO_32_BIT_CONVERSION' => 'NO', + 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', + 'OTHER_CFLAGS' => '-DHAVE_PTHREAD=1', + 'OTHER_LDFLAGS' => '$(inherited) -fprofile-instr-generate' + } + ss.dependency 'WalletCore/Types' + end +end diff --git a/WalletCoreDev.podspec b/WalletCoreDev.podspec deleted file mode 100644 index 2fba7fb34ca..00000000000 --- a/WalletCoreDev.podspec +++ /dev/null @@ -1,155 +0,0 @@ -version = '2.5.6' - -Pod::Spec.new do |s| - s.name = 'WalletCoreDev' - s.version = version - s.summary = 'Trust Wallet core data structures and algorithms.' - s.homepage = 'https://github.com/trustwallet/wallet-core' - s.license = 'MIT' - s.authors = { 'Alejandro Isaza' => 'al@isaza.ca' } - s.module_name = 'WalletCore' - - s.ios.deployment_target = '12.0' - s.osx.deployment_target = '10.12' - s.swift_version = '5.1' - s.libraries = 'c++' - - s.source = { - git: 'git@github.com:trustwallet/wallet-core.git' - } - - s.default_subspec = 'Core' - - s.subspec 'Types' do |ss| - ss.source_files = - 'swift/Sources/Types/*.swift', - 'swift/Sources/Generated/Enums/*.swift', - 'swift/Sources/Generated/Protobuf/*.swift' - ss.dependency 'SwiftProtobuf' - end - - s.subspec 'Core' do |ss| - protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.14.0' - include_dir = 'build/local/include' - ss.source_files = - 'src/**/*.{c,cc,cpp,h}', - 'include/**/*.h', - 'swift/Sources/*.{swift,h,m,cpp}', - 'swift/Sources/Extensions/*.swift', - 'swift/Sources/Generated/*.{swift,h}', - 'trezor-crypto/crypto/**/*.{c,h}', - 'trezor-crypto/include/**/*.{h}', - "#{protobuf_source_dir}/src/google/protobuf/any.cc", - "#{protobuf_source_dir}/src/google/protobuf/any.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/any_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/api.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/arena.cc", - "#{protobuf_source_dir}/src/google/protobuf/arenastring.cc", - "#{protobuf_source_dir}/src/google/protobuf/compiler/importer.cc", - "#{protobuf_source_dir}/src/google/protobuf/compiler/parser.cc", - "#{protobuf_source_dir}/src/google/protobuf/descriptor.cc", - "#{protobuf_source_dir}/src/google/protobuf/descriptor.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/descriptor_database.cc", - "#{protobuf_source_dir}/src/google/protobuf/duration.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/dynamic_message.cc", - "#{protobuf_source_dir}/src/google/protobuf/empty.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/extension_set.cc", - "#{protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc", - "#{protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/io_win32.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/printer.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/strtod.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/tokenizer.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.cc", - "#{protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/map.cc", - "#{protobuf_source_dir}/src/google/protobuf/map_field.cc", - "#{protobuf_source_dir}/src/google/protobuf/message.cc", - "#{protobuf_source_dir}/src/google/protobuf/message_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/parse_context.cc", - "#{protobuf_source_dir}/src/google/protobuf/reflection_ops.cc", - "#{protobuf_source_dir}/src/google/protobuf/repeated_field.cc", - "#{protobuf_source_dir}/src/google/protobuf/service.cc", - "#{protobuf_source_dir}/src/google/protobuf/source_context.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/struct.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/common.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/int128.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/status.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/structurally_valid.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/strutil.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/substitute.cc", - "#{protobuf_source_dir}/src/google/protobuf/stubs/time.cc", - "#{protobuf_source_dir}/src/google/protobuf/text_format.cc", - "#{protobuf_source_dir}/src/google/protobuf/timestamp.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/type.pb.cc", - "#{protobuf_source_dir}/src/google/protobuf/unknown_field_set.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/field_comparator.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/field_mask_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/json_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/time_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.cc", - "#{protobuf_source_dir}/src/google/protobuf/wire_format.cc", - "#{protobuf_source_dir}/src/google/protobuf/wire_format_lite.cc", - "#{protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc" - ss.exclude_files = - 'trezor-crypto/crypto/rand.c', - 'swift/Sources/Generated/WalletCore.h' - - ss.public_header_files = - 'include/**/*.h', - 'swift/Sources/*.h' - - ss.preserve_paths = - 'trezor-crypto/crypto/*.{table}', - "#{protobuf_source_dir}/src/**/*.{h,inc}", - "#{include_dir}/nlohmann/**/*.hpp", - 'src/proto/*.proto' - - ss.xcconfig = { - 'HEADER_SEARCH_PATHS' => '$(inherited) ' \ - '$(SRCROOT)/../../wallet-core ' \ - '${SRCROOT}/../../trezor-crypto/crypto ', - 'SYSTEM_HEADER_SEARCH_PATHS' => '$(inherited) ' \ - '/usr/local/include ' \ - '${SRCROOT}/../../include ' \ - '${SRCROOT}/../../../build/local/include ' \ - "${SRCROOT}/../../trezor-crypto/include " \ - "${SRCROOT}/../../protobuf ", - 'GCC_WARN_UNUSED_FUNCTION' => 'NO', - 'GCC_WARN_64_TO_32_BIT_CONVERSION' => 'NO', - 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', - 'OTHER_CFLAGS' => '-DHAVE_PTHREAD=1', - 'OTHER_LDFLAGS' => '$(inherited) -fprofile-instr-generate' - } - ss.dependency 'WalletCoreDev/Types' - end -end diff --git a/WalletCoreSwiftProtobuf.podspec b/WalletCoreSwiftProtobuf.podspec new file mode 100644 index 00000000000..135ef9092be --- /dev/null +++ b/WalletCoreSwiftProtobuf.podspec @@ -0,0 +1,22 @@ +# Original podspec: +# https://github.com/apple/swift-protobuf/blob/main/SwiftProtobuf.podspec +Pod::Spec.new do |s| + s.name = 'WalletCoreSwiftProtobuf' + s.version = '1.29.0' + s.license = { :type => 'Apache 2.0', :file => 'LICENSE.txt' } + s.summary = 'Swift Protobuf Runtime Library' + s.homepage = 'https://github.com/apple/swift-protobuf' + s.author = 'Apple Inc.' + s.source = { :git => 'https://github.com/apple/swift-protobuf.git', :tag => s.version } + + s.requires_arc = true + s.ios.deployment_target = '11.0' + s.osx.deployment_target = '10.13' + + s.cocoapods_version = '>= 1.13.0' + + s.source_files = 'Sources/SwiftProtobuf/**/*.swift' + s.resource_bundle = {'WalletCoreSwiftProtobuf' => ['Sources/SwiftProtobuf/PrivacyInfo.xcprivacy']} + + s.swift_versions = ['5.0'] +end diff --git a/android/app/build.gradle b/android/app/build.gradle index 22d53172334..2314ed0e075 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - ndkVersion '21.2.6472646' + namespace 'com.trustwallet.core.app' + compileSdk 35 + ndkVersion '28.0.12674087' defaultConfig { applicationId "com.trustwallet.core.app" minSdkVersion 23 - targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -21,14 +21,18 @@ android { minifyEnabled false // limit platforms built for testing ndk { - abiFilters 'x86' + abiFilters 'x86', 'arm64-v8a' } } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } 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" @@ -36,15 +40,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.34.0' -} -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 7089c35084c..af4411e737e 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 @@ -1,11 +1,11 @@ package com.trustwallet.core.app.blockchains +import kotlinx.coroutines.* import org.junit.Assert.assertEquals import org.junit.Test -import wallet.core.jni.HDWallet import wallet.core.jni.CoinType import wallet.core.jni.CoinType.* -import kotlinx.coroutines.* +import wallet.core.jni.HDWallet class CoinAddressDerivationTests { @@ -14,29 +14,43 @@ class CoinAddressDerivationTests { } @Test - fun testDeriveAddressesFromPhrase() { + fun testDeriveAddressesFromPhrase() = runBlocking { val wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", "") - for (i in 0 .. 4) { - GlobalScope.launch { - CoinType.values().forEach { coin -> + val scope = CoroutineScope(Dispatchers.IO) + val jobs = mutableListOf>() + for (i in 0..4) { + CoinType.values().forEach { coin -> + val job = scope.async { val privateKey = wallet.getKeyForCoin(coin) val address = coin.deriveAddress(privateKey) runDerivationChecks(coin, address) } + jobs.add(job) } } + jobs.forEach { it.await() } } 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 -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + + 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, SONIC, + -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + + RONIN -> assertEquals("ronin:8f348F300873Fd5DA36950B2aC75a26584584feE", address) ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address) GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address) GROESTLCOIN -> assertEquals("grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j", address) @@ -46,50 +60,101 @@ 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) - ZCOIN -> assertEquals("aEd5XFChyXobvEics2ppAqgK3Bgusjxtik", 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) AION -> assertEquals("0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e", address) 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) - TON -> assertEquals("EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo", address) ALGORAND -> assertEquals("JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ", address) + ACALA -> assertEquals("25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3", address) KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) + POLYMESH -> assertEquals("2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw", address) + PIVX -> assertEquals("D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) - CARDANO -> assertEquals("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", 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("oasis1qr2wymrk4mmt4kyjg3rzkn6jsxku3kk6p5jvrvxz", 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) + PACTUS -> assertEquals("pc1r7ys2g5a4xc2qtm0t4q987m4mvs57w5g0v4pvzg", 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..98f46092a88 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,19 +32,63 @@ 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) + assertEquals(CoinType.PACTUS.value(), 21888) } @Test fun testCoinPurpose() { assertEquals(Purpose.BIP84, CoinType.BITCOIN.purpose()) + assertEquals(Purpose.BIP44, CoinType.PACTUS.purpose()) } @Test fun testCoinCurve() { assertEquals(Curve.SECP256K1, CoinType.BITCOIN.curve()) + assertEquals(Curve.ED25519, CoinType.PACTUS.curve()) + } + + @Test + fun testDerivationPathBitcoin() { + 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.BITCOIN.value()).derivationPathWithDerivation(Derivation.BITCOINTAPROOT).toString() + assertEquals(res, "m/86'/0'/0'/0/0") + res = CoinType.createFromValue(CoinType.SOLANA.value()).derivationPathWithDerivation(Derivation.SOLANASOLANA).toString() + assertEquals(res, "m/44'/501'/0'/0'") + } + + @Test + fun testDeriveAddressFromPublicKeyAndDerivationBitcoin() { + val publicKey = PublicKey("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798".toHexByteArray(), PublicKeyType.SECP256K1) + + val address = CoinType.BITCOIN.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.BITCOINSEGWIT) + assertEquals(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") + } + + @Test + fun testDerivationPathPactus() { + var res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPath().toString() + assertEquals(res, "m/44'/21888'/3'/0'") + res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPathWithDerivation(Derivation.PACTUSMAINNET).toString() + assertEquals(res, "m/44'/21888'/3'/0'") + res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPathWithDerivation(Derivation.PACTUSTESTNET).toString() + assertEquals(res, "m/44'/21777'/3'/0'") + } + + @Test + fun testDeriveAddressFromPublicKeyAndDerivationPactus() { + val publicKey = PublicKey("95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa".toHexByteArray(), PublicKeyType.ED25519) + + val mainnet_address = CoinType.PACTUS.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.PACTUSMAINNET) + assertEquals(mainnet_address, "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr") + + val testnet_address = CoinType.PACTUS.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.PACTUSTESTNET) + assertEquals(testnet_address, "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg") } } 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 1fec77e58af..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,29 +18,69 @@ 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.TransactionPay.newBuilder() + val transaction = Algorand.Transfer.newBuilder() .setToAddress("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4") .setAmount(1000000000000) - .setFee(263000) - .setFirstRound(1937767) - .setLastRound(1938767) .build() val signingInput = Algorand.SigningInput.newBuilder() .setGenesisId("mainnet-v1.0") .setGenesisHash(ByteString.copyFrom("c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adf".toHexByteArray())) .setNote(ByteString.copyFrom("68656c6c6f".toHexByteArray())) .setPrivateKey("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b".toHexBytesInByteString()) - .setTransactionPay(transaction) + .setFirstRound(1937767) + .setLastRound(1938767) + .setFee(263000) + .setTransfer(transaction) .build() val output = AnySigner.sign(signingInput, ALGORAND, SigningOutput.parser()) - assertFalse(AnySigner.supportsJSON(ALGORAND.value())) assertEquals( Numeric.toHexString(output.encoded.toByteArray()), "0x82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179" ) } + + @Test + fun testSignJSON() { + assertTrue(AnySigner.supportsJSON(ALGORAND.value())) + + val json = """ + {"genesisId":"mainnet-v1.0","genesisHash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=","note":"aGVsbG8=","firstRound":"1937767","lastRound":"1938767","fee":"263000","transfer":{"toAddress":"CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4","amount":"1000000000000"}} + """ + val key = "d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b".toHexByteArray() + val result = AnySigner.signJSON(json, key, ALGORAND.value()) + + assertEquals("82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179", result) + } + } 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..d4b0bc016d4 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt @@ -0,0 +1,237 @@ +// 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 AptosTransactionBlindSigningWithABI() { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val payloadJson = """ + { + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + } + """.trimIndent() + val signingInput = Aptos.SigningInput.newBuilder() + .setChainId(1) + .setExpirationTimestampSecs(1735902711) + .setGasUnitPrice(100) + .setMaxGasAmount(50000) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(69) + .setAnyEncoded(payloadJson) + .setPrivateKey(key) + .setAbi(""" + [ + "vector", + "u64", + "bool" + ] + """.trimIndent()) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304" + ) + } + + @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" + ) + } + + @Test + fun AptosFungibleAssetTransfer() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val fungibleAssetTransferMessage = Aptos.FungibleAssetTransferMessage.newBuilder() + .setAmount(100000000) + .setTo("0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52") + .setMetadataAddress("0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12") + .build() + val signingInput = Aptos.SigningInput.newBuilder() + .setChainId(1) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(74) + .setGasUnitPrice(100) + .setMaxGasAmount(20) + .setExpirationTimestampSecs(1736060099) + .setFungibleAssetTransfer(fungibleAssetTransferMessage) + .setPrivateKey(key) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c" + ) + } +} 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 0e25638c1de..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 @@ -31,7 +29,7 @@ class TestBandChainSigner { val from = AnyAddress(publicKey, BANDCHAIN).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 1 + amount = "1" denom = "uband" }.build() @@ -46,7 +44,7 @@ class TestBandChainSigner { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 200 + amount = "200" denom = "uband" }.build() 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..884120800ca --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt @@ -0,0 +1,108 @@ +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.java.AnySigner +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.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.SigningInput.newBuilder() + .setPsbt(BitcoinV2.Psbt.newBuilder().setPsbt(psbt)) + .addPrivateKeys(privateKey) + .build() + + val legacyInput = Bitcoin.SigningInput.newBuilder() + .setSigningV2(input) + .build() + + val legacyOutput = AnySigner.sign(legacyInput, BITCOIN, Bitcoin.SigningOutput.parser()) + val output = legacyOutput.signingResultV2 + + assertEquals(output.error, SigningError.OK) + assertEquals( + output.psbt.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.SigningInput.newBuilder() + .setPsbt(BitcoinV2.Psbt.newBuilder().setPsbt(psbt)) + .addPublicKeys(ByteString.copyFrom(publicKey.data())) + .build() + + val legacyInput = Bitcoin.SigningInput.newBuilder() + .setSigningV2(input) + .build() + + val legacyPlan = AnySigner.plan(legacyInput, BITCOIN, Bitcoin.TransactionPlan.parser()) + val plan = legacyPlan.planningResultV2 + + assertEquals(plan.error, SigningError.OK) + + assertEquals(plan.getInputs(0).receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + assertEquals(plan.getInputs(0).value, 66_406) + + // Vault transfer + assertEquals(plan.getOutputs(0).toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") + assertEquals(plan.getOutputs(0).value, 60_000) + + // OP_RETURN + assertEquals( + plan.getOutputs(1).customScriptPubkey.toByteArray().toHex(), + "0x6a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a3530" + ) + assertEquals(plan.getOutputs(1).value, 0) + + // Change output + assertEquals(plan.getOutputs(2).toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + assertEquals(plan.getOutputs(2).value, 4_670) + + assertEquals(plan.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(plan.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..40b94298cd7 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,14 @@ 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.Utxo import wallet.core.jni.proto.Common.SigningError class TestBitcoinSigning { @@ -155,4 +161,203 @@ 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(Utxo.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 builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V2) + .addInputs(utxo0) + .addOutputs(out0) + .addOutputs(changeOutput) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + }) + .setDangerousUseFixedSchnorrRng(true) + .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(Utxo.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 builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V2) + .addInputs(utxo0) + .addOutputs(out0) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + }) + .setDangerousUseFixedSchnorrRng(true) + .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(Utxo.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(Utxo.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 builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V2) + .addInputs(utxo0) + .addInputs(utxo1) + .addOutputs(out0) + .addOutputs(changeOutput) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + }) + .setDangerousUseFixedSchnorrRng(true) + .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/bitcoincash/TestBitcoinCashSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoincash/TestBitcoinCashSigning.kt new file mode 100644 index 00000000000..5ea76b7f78c --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoincash/TestBitcoinCashSigning.kt @@ -0,0 +1,82 @@ +package com.trustwallet.core.app.blockchains.bitcoincash + +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.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Utxo +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.BitcoinV2 +import wallet.core.jni.proto.Common.SigningError + +class TestBitcoinCashSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignV2P2PKH() { + val privateKey = "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384".toHexBytesInByteString() + + val utxo1 = BitcoinV2.Input.newBuilder().apply { + outPoint = Utxo.OutPoint.newBuilder().apply { + hash = "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05".toHexBytesInByteString() + vout = 2 + }.build() + value = 5151 + sighashType = BitcoinSigHashType.ALL.value() + BitcoinSigHashType.FORK.value() + receiverAddress = "bitcoincash:qzhlrcrcne07x94h99thved2pgzdtv8ccujjy73xya" + } + + val out1 = BitcoinV2.Output.newBuilder().apply { + value = 600 + // Legacy address + toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + } + val changeOutputExplicit = BitcoinV2.Output.newBuilder().apply { + value = 4325 + // Legacy address + toAddress = "qz0q3xmg38sr94rw8wg45vujah7kzma3cskxymnw06" + } + + val builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V1) + .addInputs(utxo1) + .addOutputs(out1) + .addOutputs(changeOutputExplicit) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(546) + .build() + + val input = BitcoinV2.SigningInput.newBuilder() + .addPrivateKeys(privateKey) + .setBuilder(builder) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + hrp = "bitcoincash" + }) + .build() + + val legacyInput = Bitcoin.SigningInput.newBuilder() + .setSigningV2(input) + // `CoinType` must be set to be handled correctly. + .setCoinType(CoinType.BITCOINCASH.value()) + .build() + + val outputLegacy = AnySigner.sign(legacyInput, CoinType.BITCOINCASH, Bitcoin.SigningOutput.parser()) + assertEquals(outputLegacy.error, SigningError.OK) + assert(outputLegacy.hasSigningResultV2()) + + val output = outputLegacy.signingResultV2 + assertEquals(output.error, SigningError.OK) + assertEquals( + Numeric.toHexString(output.encoded.toByteArray()), + "0x0100000001e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05020000006b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5bffffffff0258020000000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ace5100000000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000" + ) + } +} 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 new file mode 100644 index 00000000000..d8eb3424893 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.bluzelle + +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 TestBluzelleAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddressPublicKey() { + + val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val expectedAddress = "bluzelle1jf9aaj9myrzsnmpdr7twecnaftzmku2myvn4dg" + val actualAddress = AnyAddress(publicKey, CoinType.BLUZELLE).description() + + val expectedPublicKeyData = "0x035df185566521d6a7802319ee06e1a28e97b7772dfb5fdd13ca6f0575518968e4" + val actualPublicKeyData = publicKey.data().toHex() + + assertEquals(expectedAddress, actualAddress) + assertEquals(expectedPublicKeyData, actualPublicKeyData) + } + + @Test + fun testAddressValidation() { + val addr = listOf( + "bluzelle1yhtq5zm293m2r3sp2guj9m5pg5e273n6r0szul", + "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + ) + addr.forEach { + assert(CoinType.BLUZELLE.validate(it)) + assertEquals(it, AnyAddress(it, CoinType.BLUZELLE).description()) + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..bf40f0dcc67 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.bluzelle + +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.AnyAddress +import wallet.core.jni.CoinType.BLUZELLE +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput + +class TestBluzelleSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigningTransaction() { + + // Submitted Realworld tx for the following test : https://bigdipper.net.bluzelle.com/transactions/B3A7F30539CCDF72D210BC995FAF65B43F9BE04FA9F8AFAE0EC969660744002F + val key = + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, BLUZELLE).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "1" + denom = "ubnt" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "1000" + denom = "ubnt" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 500000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + accountNumber = 590 + chainId = "net-6" + memo = "" + sequence = 2 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, BLUZELLE, SigningOutput.parser()) + val jsonPayload = output.json + + val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"ubnt"}],"gas":"500000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"ubnt"}],"from_address":"bluzelle1hsk6jryyqjfhp5dhc55tc9jtckygx0epzzw0fm","to_address":"bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"5e3e13x1F+Y4+cPNvE1jQ/Mrz0J2RQCY69re3g4xuTY3Gw7MNGQ+8E34d9DgvcNLPM05nehdOv/0SvekY/ihIQ=="}]}}""" + assertEquals(expectedJsonPayload, jsonPayload) + + } + + @Test + fun testSigningJSON() { + + // Submitted realworld tx for the following test : https://bigdipper.net.bluzelle.com/transactions/DEF394BE0DD1075CDC8B8618A7858AAA4A03A43D04476381224E316E06FD3A5B + val myPrivateKey = "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + val myAddress = "bluzelle1hsk6jryyqjfhp5dhc55tc9jtckygx0epzzw0fm" + + val toAddress = "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + val json = """ + { + "accountNumber": "590", + "chainId": "net-6", + "sequence": "3", + "fee": { + "amounts": [{ + "denom": "ubnt", + "amount": "1000" + }], + "gas": "500000" + }, + "memo": "Testing", + "messages": [{ + "sendCoinsMessage": { + "fromAddress": "$myAddress", + "toAddress": "$toAddress", + "amounts": [{ + "denom": "ubnt", + "amount": "2" + }] + } + }] + } + """ + val key = myPrivateKey.toHexByteArray() + val actualResult = AnySigner.signJSON(json, key, BLUZELLE.value()) + + val expectedResult = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"ubnt"}],"gas":"500000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"2","denom":"ubnt"}],"from_address":"bluzelle1hsk6jryyqjfhp5dhc55tc9jtckygx0epzzw0fm","to_address":"bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"2JcfVwq9N3UAk5Lfki7+CngqcefgjfH1q/8chtJMJvEHRe6PvuYKMv5pjeN0Z5Vk2BArJT3V3EHxbpbiY2eLWw=="}]}}""" + assertEquals(expectedResult, actualResult) + } +} 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 c71dbe93ab6..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 @@ -20,12 +18,12 @@ class TestCardanoAddress { @Test fun testAddress() { - val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4".toHexByteArray()) - val pubkey = key.publicKeyEd25519Extended + val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a".toHexByteArray()) + val pubkey = key.publicKeyEd25519Cardano val address = AnyAddress(pubkey, CoinType.CARDANO) - val expected = AnyAddress("addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668", CoinType.CARDANO) + val expected = AnyAddress("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t", CoinType.CARDANO) - assertEquals(pubkey.data().toHex(), "0x57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") + assertEquals(pubkey.data().toHex(), "0x57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d483e4b09a4ba73725625c346870e109bd335831f2ba0b72b09bfb44040f1016caed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a") assertEquals(address.description(), expected.description()) } } 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 new file mode 100644 index 00000000000..d9450e9881c --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.cardano + +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.toHexByteArray +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.Common.SigningError + + +class TestCardanoSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignTransfer1() { + val message = Cardano.Transfer.newBuilder() + .setToAddress("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5") + .setChangeAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(7_000_000) + .build() + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setTtl(53333333) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"))) + .setOutputIndex(1) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(1_500_000) + .build() + input.addUtxos(utxo1) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"))) + .setOutputIndex(0) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(6_500_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()), + "0x83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + + val txid = output.txId + 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() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetName("SUNDAE") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("01312d00"))) // 20000000 + .build() + val toTokenBundle = Cardano.TokenBundle.newBuilder() + .addToken(toToken) + .build() + + // check min ADA amount, set it + val minAmount = wallet.core.jni.Cardano.minAdaAmount(toTokenBundle.toByteArray()) + assertEquals(minAmount, 1_444_443) + + val message = Cardano.Transfer.newBuilder() + .setToAddress("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5") + .setChangeAddress("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq") + .setAmount(minAmount) + .setUseMaxAmount(false) + .setTokenAmount(toTokenBundle) + .build() + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setTtl(53333333) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"))) + .setOutputIndex(1) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(8_051_373) + val token3 = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetNameHex("43554259") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("2dc6c0"))) // 3000000 + .build() + utxo1.addTokenAmount(token3) + input.addUtxos(utxo1.build()) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"))) + .setOutputIndex(2) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + .setAmount(2_000_000) + val token1 = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetNameHex("53554e444145") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("04d3e8d9"))) // 80996569 + .build() + utxo2.addTokenAmount(token1) + val token2 = Cardano.TokenAmount.newBuilder() + .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") + .setAssetName("CUBY") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("1e8480"))) // 2000000 + .build() + utxo2.addTokenAmount(token2) + input.addUtxos(utxo2.build()) + + 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()), + "0x83a400838258206a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa863916700825820aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b800825820ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840000182825839010ae9267a9b4f6edb4d69c548f3cba39654d7e67f37ffd5e1352f303e9d63b100507122da18e9cb58995a50f2e80ff65d00be6243cf89a186821a0012593aa1581c219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5ea153636f6f6c63617473736f6369657479343536370182583901299de4a3d24637ef4050033c214754963b829f43cbc311527d2b4bc8e36798a9ada6e3341ac239057e012225fe5c862f49b52db0f5e208731a002b1525021a0002b19b031a055007d5a1008182582088bd26e8656fa7dead846c3373588f0192da5bfb90bf5d3fb877decfb3b3fd085840da8656aca0dacc57d4c2d957fc7dff03908f6dcf60c48f1e40b3006e2fd0cfacfa4c24fa02e35a310572526586d4ce0d30bf660ba274c8efd507848cbe177d09f6"); + + val txid = output.txId + 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 ff0ed3a94c9..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 @@ -1,14 +1,16 @@ -package com.trustwalval.core.app.blockchains.cosmos +package com.trustwallet.core.app.blockchains.cosmos import android.util.Log import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHex import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.* import wallet.core.jni.CoinType.COSMOS 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 TestCosmosTransactions { @@ -17,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 = @@ -25,7 +125,7 @@ class TestCosmosTransactions { val from = AnyAddress(publicKey, COSMOS).description() val txAmount = Cosmos.Amount.newBuilder().apply { - amount = 1 + amount = "1" denom = "muon" }.build() @@ -40,7 +140,7 @@ class TestCosmosTransactions { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 200 + amount = "200" denom = "muon" }.build() @@ -50,6 +150,7 @@ class TestCosmosTransactions { }.build() val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf accountNumber = 1037 chainId = "gaia-13003" memo = "" @@ -60,11 +161,12 @@ class TestCosmosTransactions { }.build() val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) - val jsonPayload = output.json - - val expectedJsonPayload = """{"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=="}]}}""" - assertEquals(expectedJsonPayload, jsonPayload) + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\"}" + ) + assertEquals(output.errorMessage, "") } @Test @@ -93,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 new file mode 100644 index 00000000000..214c391624d --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.cryptoorg + +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 TestCryptoorgAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.CRYPTOORG) + val expected = AnyAddress("cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd", CoinType.CRYPTOORG) + + assertEquals(pubkey.data().toHex(), "0x03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77") + assertEquals(address.description(), expected.description()) + } +} 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 new file mode 100644 index 00000000000..74090e73a29 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.cryptoorg + +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.CRYPTOORG +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 TestCryptoorgSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun CryptoorgTransactionSigning() { + val key = PrivateKey("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, CRYPTOORG).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "50000000" + denom = "basecro" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "5000" + denom = "basecro" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 125798 + chainId = "crypto-org-chain-mainnet-1" + memo = "" + sequence = 2 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CRYPTOORG, SigningOutput.parser()) + + // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") + 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 05c17837ddc..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 val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - private var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" - private var alicePubKeyHex = "0xfd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" - - @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 ac37e8f520e..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt +++ /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. - -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") - } - - val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" - var alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" - - val bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" - var bobSeedHex = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e" - var bobPubKeyHex = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36" - - @Test - fun signTransaction() { - val transaction = Elrond.TransactionMessage.newBuilder() - .setNonce(0) - .setValue("0") - .setSender(aliceBech32) - .setReceiver(bobBech32) - .setGasPrice(1000000000) - .setGasLimit(50000) - .setData("foo") - .setChainId("1") - .setVersion(1) - .build() - - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) - - val signingInput = Elrond.SigningInput.newBuilder() - .setPrivateKey(privateKey) - .setTransaction(transaction) - .build() - - val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00" - - assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":0,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","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..b793671d644 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -0,0 +1,447 @@ +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 { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() + }.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 { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() + }.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 { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { + addAllCalls(listOf( + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(approveCall) + }.build(), + Ethereum.Transaction.SCWalletBatch.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\"}") + } + + // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b + @Test + fun testSignEip7702EoaBatched() { + val transferFunc1 = EthereumAbiFunction("transfer") + transferFunc1.addParamAddress("0x2EF648D7C03412B832726fd4683E2625deA047Ba".toHexByteArray(), false) + // 100_000_000_000_000 + transferFunc1.addParamUInt256("0x5af3107a4000".toHexByteArray(), false) + val transferPayload1 = EthereumAbi.encode(transferFunc1) + + val transferFunc2 = EthereumAbiFunction("transfer") + transferFunc2.addParamAddress("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".toHexByteArray(), false) + // 500_000_000_000_000 + transferFunc2.addParamUInt256("0x1c6bf52634000".toHexByteArray(), false) + val transferPayload2 = EthereumAbi.encode(transferFunc2) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x12".toHexByteArray()) + txMode = TransactionMode.SetCode + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz + addAllCalls(listOf( + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload1) + }.build(), + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload2) + }.build() + )) + }.build() + }.build() + + eip7702Authorization = Ethereum.Authorization.newBuilder().apply { + address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") + } + + // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 + @Test + fun testSignEnvelopedBiz() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x02".toHexByteArray()) + txMode = TransactionMode.Enveloped + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz + transaction = Ethereum.Transaction.newBuilder().apply { + erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { + to = "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e" + amount = ByteString.copyFrom("0xb5e620f48000".toHexByteArray()) + }.build() + }.build() + }.build() + }.build() + + // TWT token. + toAddress = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9") + } + + @Test + fun testAuthorizationHash() { + val chainId = "0x01".toHexByteArray() + val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + val nonce = "0x01".toHexByteArray() + + val authorizationHash = WCBarz.getAuthorizationHash(chainId, contractAddress, nonce) + assertEquals(Numeric.toHexString(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem + } + + @Test + fun testSignAuthorization() { + val chainId = "0x01".toHexByteArray() + val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + val nonce = "0x01".toHexByteArray() + val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" + + val signedAuthorization = WCBarz.signAuthorization(chainId, contractAddress, nonce, privateKey) + val json = org.json.JSONObject(signedAuthorization) + + // Verified with viem + assertEquals(Numeric.toHexString(chainId), json.getString("chainId")) + assertEquals(contractAddress, json.getString("address")) + assertEquals(Numeric.toHexString(nonce), json.getString("nonce")) + assertEquals("0x01", json.getString("yParity")) + assertEquals("0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710", json.getString("r")) + assertEquals("0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c", json.getString("s")) + } + + @Test + fun testBarzTransferAccountDeployedV07() { + val chainIdByteArray = "0x7A69".toHexByteArray() // 31337 + val wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029" + + val transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + data = ByteString.EMPTY + }.build() + + val userOpV07 = Ethereum.UserOperationV0_7.newBuilder().apply { + entryPoint = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + sender = wallet + preVerificationGas = ByteString.copyFrom("0xF4240".toHexByteArray()) // 1000000 + verificationGasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + factory = "0xf471789937856d80e589f5996cf8b0511ddd9de4" + factoryData = ByteString.copyFrom("0xf471789937856d80e589f5996cf8b0511ddd9de4".toHexByteArray()) + paymaster = "0xf62849f9a0b5bf2913b396098f7c7019b51a820a" + paymasterVerificationGasLimit = ByteString.copyFrom("0x1869F".toHexByteArray()) // 99999 + paymasterPostOpGasLimit = ByteString.copyFrom("0x15B38".toHexByteArray()) // 88888 + paymasterData = ByteString.copyFrom( + "0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b" + .toHexByteArray() + ) + }.build() + + // Create signing input + val signingInput = Ethereum.SigningInput.newBuilder().apply { + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + chainId = ByteString.copyFrom(chainIdByteArray) // 31337 + nonce = ByteString.copyFrom("0x00".toHexByteArray()) + txMode = Ethereum.TransactionMode.UserOp + gasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxInclusionFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) + toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + + transaction = Ethereum.Transaction.newBuilder().apply { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + this.transfer = transfer + }.build() + }.build() + }.build() + + userOperationV07 = userOpV07 + }.build() + + val output = AnySigner.sign(signingInput, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) + + assertEquals( + "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", + Numeric.toHexString(output.preHash.toByteArray()) + ) + + val codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b" + val codeName = "Biz" + val codeVersion = "v1.0.0" + val typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867" + val domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472" + val hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" + + val encodedHash = WCBarz.getEncodedHash( + chainIdByteArray, + codeAddress, + codeName, + codeVersion, + typeHash, + domainSeparatorHash, + wallet, + hash + ) + assertEquals( + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", + Numeric.toHexString(encodedHash) + ) + + val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" + val signedHash = WCBarz.getSignedHash( + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", + privateKey + ) + assertEquals( + "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c", + Numeric.toHexString(signedHash) + ) + } +} 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 5461b7e6ed0..bd192a97070 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,9 +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 wallet.core.jni.EthereumAbi +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.CoinType +import wallet.core.jni.proto.EthereumAbi class TestEthereumAbiDecoder { @@ -34,9 +37,203 @@ 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 = wallet.core.jni.EthereumAbi.encodeTyped( + """ + { + "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!" + } + } + """) + 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") + } + + @Test + fun testEthereumAbiGetFunctionSignature() { + val abiJson = """ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + """.trimIndent() + + val functionSignature = wallet.core.jni.EthereumAbi.getFunctionSignature(abiJson) + assertEquals(functionSignature, "transfer(address,uint256)") + } } 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 66e4866ff21..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 @@ -8,7 +8,7 @@ import com.trustwallet.core.app.utils.Numeric import wallet.core.jni.EthereumAbi import wallet.core.jni.EthereumAbiValue -class TestEthereumAbiEncoder { +class TestEthereumAbiValue { init { System.loadLibrary("TrustWalletCore") @@ -24,7 +24,7 @@ class TestEthereumAbiEncoder { val encoded = EthereumAbi.encode(function) assertEquals("0xa35856da" + "0000000000000000000000000000000000000000000000000000000000000001", - Numeric.toHexString(encoded)); + Numeric.toHexString(encoded)) } @Test @@ -52,7 +52,7 @@ class TestEthereumAbiEncoder { "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000003", - Numeric.toHexString(encoded)); + Numeric.toHexString(encoded)) // original output value assertEquals(0, function.getParamUInt64(0, true)) // decode output @@ -87,7 +87,7 @@ class TestEthereumAbiEncoder { 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 TestEthereumAbiEncoder { @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 f93f2ae450a..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,7 +9,10 @@ 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 import org.junit.Assert.assertArrayEquals @@ -27,6 +30,7 @@ class TestEthereumTransactionSigner { toAddress = "0x3535353535353535353535353535353535353535" chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x9".toHexByteArray()) + // txMode not set, Legacy is the default gasPrice = ByteString.copyFrom("0x04a817c800".toHexByteArray()) gasLimit = ByteString.copyFrom("0x5208".toHexByteArray()) transaction = Ethereum.Transaction.newBuilder().apply { @@ -49,6 +53,7 @@ class TestEthereumTransactionSigner { toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x0".toHexByteArray()) + // txMode not set, Legacy is the default gasPrice = ByteString.copyFrom("0x09c7652400".toHexByteArray()) gasLimit = ByteString.copyFrom("0x0130B9".toHexByteArray()) transaction = Ethereum.Transaction.newBuilder().apply { @@ -65,6 +70,31 @@ class TestEthereumTransactionSigner { assertEquals(Numeric.toHexString(output.data.toByteArray()), "0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000") } + @Test + fun testEthereumERC20_1559_Signing() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI + chainId = ByteString.copyFrom("0x1".toHexByteArray()) + nonce = ByteString.copyFrom("0x0".toHexByteArray()) + txMode = TransactionMode.Enveloped + maxInclusionFeePerGas = ByteString.copyFrom("0x77359400".toHexByteArray()) // 2000000000 + maxFeePerGas = ByteString.copyFrom("0xB2D05E00".toHexByteArray()) // 3000000000 + gasLimit = ByteString.copyFrom("0x0130B9".toHexByteArray()) + transaction = Ethereum.Transaction.newBuilder().apply { + erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { + to = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + amount = ByteString.copyFrom("0x1bc16d674ec80000".toHexByteArray()) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf") + } + @Test fun testEthereumERC721Signing() { val signingInput = Ethereum.SigningInput.newBuilder() @@ -73,6 +103,7 @@ class TestEthereumTransactionSigner { toAddress = "0x0d8c864DA1985525e0af0acBEEF6562881827bd5" chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x02de".toHexByteArray()) + // txMode not set, Legacy is the default gasPrice = ByteString.copyFrom("0x22ecb25c00".toHexByteArray()) // 150 Gwei gasLimit = ByteString.copyFrom("0x0130b9".toHexByteArray()) transaction = Ethereum.Transaction.newBuilder().apply { @@ -98,6 +129,7 @@ class TestEthereumTransactionSigner { toAddress = "0x4e45e92ed38f885d39a733c14f1817217a89d425" // contract chainId = ByteString.copyFrom("0x01".toHexByteArray()) nonce = ByteString.copyFrom("0x00".toHexByteArray()) + // txMode not set, Legacy is the default gasPrice = ByteString.copyFrom("0x09C7652400".toHexByteArray()) // 42000000000 gasLimit = ByteString.copyFrom("0x0130b9".toHexByteArray()) // 78009 transaction = Ethereum.Transaction.newBuilder().apply { @@ -117,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/groestlcoin/TestGroestlcoinSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/groestlcoin/TestGroestlcoinSigner.kt new file mode 100644 index 00000000000..64cb6bba4f5 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/groestlcoin/TestGroestlcoinSigner.kt @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.groestlcoin + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertEquals +import org.junit.Test + +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType.GROESTLCOIN +import wallet.core.jni.proto.Common.SigningError +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Bitcoin.SigningOutput +import wallet.core.jni.proto.BitcoinV2 +import wallet.core.jni.proto.Utxo + +class TestGroestlcoinSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignV2P2PKH() { + // Successfully broadcasted: https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 + val privateKeyData = Numeric.hexStringToByteArray("dc334e7347f2f9f72fce789b11832bdf78adf0158bc6617e6d2d2a530a0d4bc6") + val dustSatoshis = 546.toLong() + val senderAddress = "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne" + val toAddress = "31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P" + val changeAddress = "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM" + + val txid0 = Numeric.hexStringToByteArray("8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895").reversedArray() + val utxo0 = BitcoinV2.Input.newBuilder() + .setOutPoint(Utxo.OutPoint.newBuilder().apply { + hash = ByteString.copyFrom(txid0) + vout = 1 + }) + .setValue(4774) + .setSighashType(BitcoinSigHashType.ALL.value()) + .setReceiverAddress(senderAddress) + + val out0 = BitcoinV2.Output.newBuilder() + .setValue(2500) + .setToAddress(toAddress) + + val changeOut = BitcoinV2.Output.newBuilder() + .setValue(2048) + .setToAddress(changeAddress) + + val builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.UseDefault) + .addInputs(utxo0) + .addOutputs(out0) + .addOutputs(changeOut) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 36 + p2ShPrefix = 5 + }) + .build() + + val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { + signingV2 = signingInput + coinType = GROESTLCOIN.value() + } + + val output = AnySigner.sign(legacySigningInput.build(), GROESTLCOIN, SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.signingResultV2.errorMessage, "") + assertEquals(output.signingResultV2.error, SigningError.OK) + assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x010000000001019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f0100000000ffffffff02c40900000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700080000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88ac024830450221009bbd0228dcb7343828633ded99d216555d587b74db40c4a46f560187eca222dd022032364cf6dbf9c0213076beb6b4a20935d4e9c827a551c3f6f8cbb22d8b464467012102e9c9b9b76e982ad8fa9a7f48470eafbeeba9bf6d287579318c517db5157d936e00000000") + assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0x40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt index d2f7251cac1..c0a9af1bd37 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt @@ -43,4 +43,15 @@ class TestHarmonyTransactionSigner { assertEquals(Numeric.toHexString(sign.r.toByteArray()), "0x325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d") assertEquals(Numeric.toHexString(sign.s.toByteArray()), "0x6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b") } + + @Test + fun testSignJSON() { + val json = """ + {"chainId":"Ag==","transactionMessage":{"nonce":"AQ==","gasPrice":"AA==","gasLimit":"Ugg=","toAddress":"one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k","amount":"Br/I2l7oIgAA","fromShardId":"AQ==","toShardId":"AA=="}} + """ + val key = "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48".toHexByteArray() + val result = AnySigner.signJSON(json, key, HARMONY.value()) + + assertEquals("f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc", result) + } } 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/kava/TestKavaTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt index 06b3c7b686a..b715150a080 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt @@ -1,4 +1,4 @@ -package com.trustwalval.core.app.blockchains.cosmos +package com.trustwallet.core.app.blockchains.cosmos import android.util.Log import com.google.protobuf.ByteString @@ -40,7 +40,7 @@ class TestKavaTransactions { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = 100 + amount = "100" denom = "ukava" }.build() 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 new file mode 100644 index 00000000000..9a2ecfc3811 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.kcc + +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 TestKuCoinCommunityChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.KUCOINCOMMUNITYCHAIN) + val expected = AnyAddress("0xE5cA667d795685E9915E5F4b4254ca832eEB398B", CoinType.KUCOINCOMMUNITYCHAIN) + + assertEquals(pubkey.data().toHex(), "0x0413bde18e3329af54d51a24f424fe09a8d7d42c324c07e10e53a6e139cbee80e6288142dec2ed46f7b81dccbb28d6168cdc7b208928730cbeeb911f8db6a707bb") + assertEquals(address.description(), expected.description()) + } +} 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..82929dcb8c2 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt @@ -0,0 +1,307 @@ +// 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 signGenericActionWithRelayer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(42) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .setRelayer(carolBech32) + .build() + + val genericAction = MultiversX.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("1000000000000000000") + .setVersion(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 = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":42,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","relayer":"$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 b0839d0b5ba..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,23 +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. package com.trustwallet.core.app.blockchains.oasis 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 junit.framework.Assert.assertEquals import org.junit.Test +import org.junit.Assert.assertEquals import wallet.core.java.AnySigner import wallet.core.jni.CoinType.OASIS import wallet.core.jni.proto.Oasis import wallet.core.jni.proto.Oasis.SigningOutput -import java.math.BigInteger class TestOasisSigner { @@ -49,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 new file mode 100644 index 00000000000..88b33d1b9fd --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.osmosis + +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 TestOsmosisAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.OSMOSIS) + val expected = AnyAddress("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", CoinType.OSMOSIS) + + assertEquals(pubkey.data().toHex(), "0x02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649") + assertEquals(address.description(), expected.description()) + } +} 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 new file mode 100644 index 00000000000..679583594f7 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.osmosis + +import android.util.Log +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 com.trustwallet.core.app.utils.toHex +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.jni.CoinType.OSMOSIS +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 TestOsmosisSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun OsmosisTransactionSigning() { + val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, OSMOSIS).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "99800" + denom = "uosmo" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "200" + denom = "uosmo" + }.build() + + val osmosisFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 124703 + chainId = "osmosis-1" + memo = "" + sequence = 0 + fee = osmosisFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + 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.errorMessage, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt new file mode 100644 index 00000000000..96e7217fc70 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.pactus + +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 TestPactusAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testMainnetAddress() { + val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.PACTUS) + val expected = AnyAddress("pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr", CoinType.PACTUS) + + assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa") + assertEquals(address.description(), expected.description()) + } + + @Test + fun testTestnetAddress() { + val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.PACTUS, Derivation.PACTUSTESTNET) + val expected = AnyAddress("tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg", CoinType.PACTUS) + + assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt new file mode 100644 index 00000000000..81bbe9bf532 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.pactus + +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.PrivateKey +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.CoinType.PACTUS +import wallet.core.jni.proto.Pactus +import wallet.core.jni.proto.Pactus.SigningOutput +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertArrayEquals + +class TestPactusSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testPactusTransferSigning() { + // Successfully broadcasted transaction: + // https://pacviewer.com/transaction/1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f + // + val signingInput = Pactus.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom( + PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6" + .toHexByteArray()).data() + ) + transaction = Pactus.TransactionMessage.newBuilder().apply { + lockTime = 2335524 + fee = 10000000 + memo = "wallet-core" + transfer = Pactus.TransferPayload.newBuilder().apply { + sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr" + receiver = "pc1r0g22ufzn8qtw0742dmfglnw73e260hep0k3yra" + amount = 200000000 + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), PACTUS, SigningOutput.parser()) + + assertEquals( + "0x1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f", + Numeric.toHexString(output.transactionId.toByteArray()) + ) + + assertEquals( + "0x4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9b49d33a0fc8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736693eda8506", + Numeric.toHexString(output.signature.toByteArray()) + ) + + assertEquals( + "0x000124a3230080ade2040b77616c6c65742d636f726501037098338e0b6808119dfd4457ab806b9c2059b89b037a14ae24533816e7faaa6ed28fcdde8e55a7df218084af5f4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9b49d33a0fc8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736693eda850695794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa", + Numeric.toHexString(output.signedTransactionData.toByteArray()) + ) + } + + @Test + fun testPactusBondWithPublicKeySigning() { + // Successfully broadcasted transaction: + // https://pacviewer.com/transaction/d194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f + // + val signingInput = Pactus.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom( + PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6" + .toHexByteArray()).data() + ) + transaction = Pactus.TransactionMessage.newBuilder().apply { + lockTime = 2339009 + fee = 10000000 + memo = "wallet-core" + bond = Pactus.BondPayload.newBuilder().apply { + sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr" + receiver = "pc1p9y5gmu9l002tt60wak9extgvwm69rq3a9ackrl" + stake = 1000000000 + publicKey = "public1pnz75msstqdrq5eguvcwanug0zauhqjw2cc4flmez3qethnp68y64ehc4k69amapj7x4na2uda0snqz4yxujgx3jsse4f64fgy7jkh0xauvhrc5ts09vfk48g85t0js66hvajm6xruemsvlxqv3xvkyur8v9v0mtn" + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), PACTUS, SigningOutput.parser()) + + assertEquals( + "0xd194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f", + Numeric.toHexString(output.transactionId.toByteArray()) + ) + + assertEquals( + "0x0d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda55b4338328adac79d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff65e39ce7006300", + Numeric.toHexString(output.signature.toByteArray()) + ) + + assertEquals( + "0x0001c1b0230080ade2040b77616c6c65742d636f726502037098338e0b6808119dfd4457ab806b9c2059b89b0129288df0bf7bd4b5e9eeed8b932d0c76f451823d6098bd4dc20b03460a651c661dd9f10f17797049cac62a9fef228832bbcc3a39355cdf15b68bddf432f1ab3eab8debe1300aa43724834650866a9d552827a56bbcdde32e3c517079589b54e83d16f9435abb3b2de8c3e677067cc0644ccb13833b8094ebdc030d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda55b4338328adac79d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff65e39ce700630095794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa", + Numeric.toHexString(output.signedTransactionData.toByteArray()) + ) + } + + @Test + fun testPactusBondWithoutPublicKeySigning() { + // Successfully broadcasted transaction: + // https://pacviewer.com/transaction/f83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80 + // + val signingInput = Pactus.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom( + PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6" + .toHexByteArray()).data() + ) + transaction = Pactus.TransactionMessage.newBuilder().apply { + lockTime = 2335580 + fee = 10000000 + memo = "wallet-core" + bond = Pactus.BondPayload.newBuilder().apply { + sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr" + receiver = "pc1p6taz5l2kq5ppnxv4agnqj48svnvsy797xpe6wd" + stake = 1000000000 + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), PACTUS, SigningOutput.parser()) + + assertEquals( + "0xf83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80", + Numeric.toHexString(output.transactionId.toByteArray()) + ) + + assertEquals( + "0x9e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d85c087a8748ff0715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc436aa58f9a8f00d", + Numeric.toHexString(output.signature.toByteArray()) + ) + + assertEquals( + "0x00015ca3230080ade2040b77616c6c65742d636f726502037098338e0b6808119dfd4457ab806b9c2059b89b01d2fa2a7d560502199995ea260954f064d90278be008094ebdc039e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d85c087a8748ff0715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc436aa58f9a8f00d95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa", + Numeric.toHexString(output.signedTransactionData.toByteArray()) + ) + } +} 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 2c3f703bc2e..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 @@ -22,7 +20,7 @@ class TestPolkadotSigner { } val genesisHashStr = "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3".toHexBytesInByteString() - val privateKeyThrow2 = "0x70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f".toHexBytesInByteString() + val iOSTestKey = "7f44b19b391a8015ca4c7d94097b3695867a448d1391e7f3243f06987bdb6858".toHexBytesInByteString() @Test fun PolkadotTransactionSigning() { @@ -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 { @@ -56,27 +54,22 @@ class TestPolkadotSigner { @Test fun PolkadotTransactionSignBondAndNominate() { - val eraP = Polkadot.Era.newBuilder().apply { - blockNumber = 3856651 - period = 64 - } val call = Polkadot.Staking.BondAndNominate.newBuilder().apply { - controller = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2" - value = "0x77359400".toHexBytesInByteString() // 0.2 + controller = "13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5" + value = "0x02540be400".toHexBytesInByteString() // 1 DOT rewardDestination = Polkadot.RewardDestination.STASH - addNominators("14xKzzU1ZYDnzFj7FgdtDAYSMJNARjDc2gNw4XAFDgr4uXgp") - addNominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih") + addNominators("1zugcavYA9yCuYwiEYeMHNJm9gXznYjNfXQjZsZukF1Mpow") + addNominators("15oKi7HoBQbwwdQc47k71q4sJJWnu5opn1pqoGx4NAEYZSHs") } val input = Polkadot.SigningInput.newBuilder().apply { genesisHash = genesisHashStr - blockHash = "0x3a886617f4bbd4fe2bbe7369acae4163ed0b19ffbf061083abc5e0836ad58f77".toHexBytesInByteString() - nonce = 6 - specVersion = 27 - network = Polkadot.Network.POLKADOT - transactionVersion = 5 - privateKey = privateKeyThrow2 - era = eraP.build() + blockHash = genesisHashStr + nonce = 4 + specVersion = 30 + network = POLKADOT.ss58Prefix() + transactionVersion = 7 + privateKey = iOSTestKey stakingCall = Polkadot.Staking.newBuilder().apply { bondAndNominate = call.build() }.build() @@ -85,8 +78,39 @@ class TestPolkadotSigner { val output = AnySigner.sign(input.build(), POLKADOT, SigningOutput.parser()) val encoded = Numeric.toHexString(output.encoded.toByteArray()) - // https://polkadot.subscan.io/extrinsic/0xc7a016f961dbf35d58feea22694e7d79ac77175a8cc40cb017bb5e87d56142ce - val expected = "0x5103849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783007d549324f270eb5932b898ce5fc166c3f30942c96668f52d6cc86c7b61a8d65680cd0a979f1e0a43ef9418e6571edab6d9c391a1696abdf56db2af348862d50eb50018001a000807009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783030094357701070508aee72821ca00e62304e4f0d858122a65b87c8df4f0eae224ae064b951d39f610127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a" + // https://polkadot.subscan.io/extrinsic/4955314-2 + val expected = "0x6103840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00a8b1f859d788f11a958e98b731358f89cf3fdd41a667ea992522e8d4f46915f4c03a1896f2ac54bdc5f16e2ce8a2a3bf233d02aad8192332afd2113ed6688e0d0010001a02080700007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b540201070508002c2a55b5ffdca266bd0207df97565b03255f70783ca1a349be5ed9f44589c36000d44533a4d21fd9d6f5d57c8cd05c61a6f23f9131cec8ae386b6b437db399ec3d" + assertEquals(encoded, expected) + } + + @Test + fun PolkadotTransactionSignChillAndUnbond() { + val call = Polkadot.Staking.ChillAndUnbond.newBuilder().apply { + value = "0x1766444D00".toHexBytesInByteString() // 10.05 DOT + } + + val input = Polkadot.SigningInput.newBuilder().apply { + genesisHash = genesisHashStr + blockHash = "0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0".toHexBytesInByteString() + nonce = 6 + specVersion = 9200 + network = POLKADOT.ss58Prefix() + transactionVersion = 12 + privateKey = "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266".toHexBytesInByteString() + era = Polkadot.Era.newBuilder().apply { + blockNumber = 10541373 + period = 64 + }.build() + stakingCall = Polkadot.Staking.newBuilder().apply { + chillAndUnbond = call.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLKADOT, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + // https://polkadot.subscan.io/extrinsic/10541383-2 + val expected = "0xd10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617" assertEquals(encoded, expected) } } 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/polymesh/TestPolymeshAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt new file mode 100644 index 00000000000..a7e77d9a3e0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.polymesh + +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 TestPolymeshAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.POLYMESH) + val expected = AnyAddress("2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", CoinType.POLYMESH) + + assertEquals(pubkey.data().toHex(), "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt new file mode 100644 index 00000000000..116c5b76d40 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.polymesh + +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.POLYMESH +import wallet.core.jni.proto.Polkadot +import wallet.core.jni.proto.Polymesh +import wallet.core.jni.proto.Polymesh.SigningOutput + +class TestPolymeshSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + val genesisHashStr = "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063".toHexBytesInByteString() + // Private key for testing. DO NOT USE, since this is public. + val TestKey1 = "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexBytesInByteString() + + @Test + fun PolymeshTransactionSigning() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + val blockHashStr = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d".toHexBytesInByteString() + + var call = Polymesh.Balance.Transfer.newBuilder().apply { + toAddress = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7" + value = "0x0F4240".toHexBytesInByteString() + } + + val input = Polymesh.SigningInput.newBuilder().apply { + genesisHash = genesisHashStr + blockHash = blockHashStr + era = Polkadot.Era.newBuilder().apply { + blockNumber = 16_102_106 + period = 64 + }.build() + network = POLYMESH.ss58Prefix() + nonce = 1 + specVersion = 7000005 + transactionVersion = 7 + privateKey = TestKey1 + runtimeCall = Polymesh.RuntimeCall.newBuilder().apply { + balanceCall = Polymesh.Balance.newBuilder().apply { + transfer = call.build() + }.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLYMESH, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + val expected = "0x390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + assertEquals(encoded, expected) + } +} 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 new file mode 100644 index 00000000000..64c33ba9c58 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.smartbitcoincash + +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 TestSmartBitcoinCashAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.SMARTBITCOINCASH) + val expected = AnyAddress("0x8bFC9477684987dcAf0970b9bce5E3D9267C99C0", CoinType.SMARTBITCOINCASH) + + 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/TestSolanaMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaMessageSigner.kt new file mode 100644 index 00000000000..b4eb3752fc7 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaMessageSigner.kt @@ -0,0 +1,44 @@ +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.CoinType.SOLANA +import wallet.core.jni.MessageSigner +import wallet.core.jni.proto.Common.SigningError +import wallet.core.jni.proto.Solana + +class TestSolanaMessageSigner { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testMessageSign() { + val signingInput = Solana.MessageSigningInput.newBuilder().apply { + privateKey = ByteString.copyFrom("44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d".toHexByteArray()) + message = "Hello world" + }.build() + + val outputData = MessageSigner.sign(SOLANA, signingInput.toByteArray()) + val output = Solana.MessageSigningOutput.parseFrom(outputData) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.signature, "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG") + } + + @Test + fun testMessageVerify() { + val verifyingInput = Solana.MessageVerifyingInput.newBuilder().apply { + publicKey = ByteString.copyFrom("ee6d61a89fc8f9909585a996bb0d2b2ac69ae23b5acf39a19f32631239ba06f9".toHexByteArray()) + signature = "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG" + message = "Hello world" + }.build() + + assert(MessageSigner.verify(SOLANA, verifyingInput.toByteArray())) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaSigner.kt index 24a70f48412..f2fe4679fa9 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaSigner.kt @@ -2,6 +2,7 @@ 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 @@ -40,26 +41,27 @@ class TestSolanaSigner { @Test fun testDelegateStakeSign() { - val delegateStakeMessage = Solana.Stake.newBuilder().apply { + val delegateStakeMessage = Solana.DelegateStake.newBuilder().apply { validatorPubkey = commonValidatorPubkey value = 42 + stakeAccount = "" }.build() val signingInput = Solana.SigningInput.newBuilder().apply { - stakeTransaction = delegateStakeMessage + delegateStakeTransaction = delegateStakeMessage recentBlockhash = blockhash privateKey = ByteString.copyFrom(Base58.decodeNoCheck(commonPrivateKey)) }.build() val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser()) - val expectedString = "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQPouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xyATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGvwfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWukgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgzU285SkncqNSgxkpZVhmenTXpuZv74rXzariX8P4sprRgKUoj4b7Nu72Pya1zr7k45isMwgxtLnnnTK5k7mrZRDw3jBSBuukJBja93zaidm8HCQdwQsBt5CNSgSXug1R2t6Sdm5tjJrsd1gyRv7udFbHCdbVEeatzULNSSGdwjwwJDy1DTC12ddBNHd8k5ic5TDwrWdfCxbDRoFYw849YNNUuyNAPz1jDCkLG9af6KFFLxfuR9pnF8jSyTcQAq95YiiD9sC3mAUoe8AkYfy929XzTEatP1vasMvo" + val expectedString = "j24mVM9Zgu5vDZhPLGGuCRXQnP9djNtxdHh4txN3S7dwJsNNL5fbhzGpPgSUAcLGoMVCfF9TuqTYfpfJnb4sJFe1ahM8yPL5HwuKL6py5AZJFi8SWx9fvaVB699dCPo1GT3JoEBLPCZ9o2jQtnwzLkzTYJnKv2axqhKWFE2sz6TBA5J39eZcjMFUYgyxz6Q5S4MWqYQCb8UET2NAEZoKcfy7j8N25WXL6Gj4j3hBZjpHQQNaGaNEprEqyma3ZuVhpGiCALSsuzVLX3wZVo4icXwe952deMFA4tH3BK1jcSQCgfmcKDJ9nd7bdrnUUs4BoMdF1uDZB5LxE2UH8QiqtYvaUcorF4SJ3gPxM5ykbyPsNK1cSYZF9NMpW2GofyC17eELwnHQTQB2kqphxJZu7BahvkwiDPPeeydiXAkBspJ3nc3PCBujv6WJw22ZHw5j6zAP8ZGnCW44pqtWD5qifF9tTKhySKdANNiWifs3tSCCPQqjfJXu14drNinR6VG8rJxS1qgmRYiRQUa7m1vtoaZFRN5qKUeAfoFKkAVaNnMdwgsNqNH4dqBodTCJFs1LkYwhgRZdZGbwXTn1j7vpR3DSnv4g72i2H556srzK53jdUmdv6yfxt516XDSshqZtHnKZ1tudxKjBXwsqT3imDiZFVka9wKWUAYMCi4XZ79CY6Xpsd9c18U2e9TCngQmgkTATFgrqysfraokNffgqWxvsPMugksbvbPjJs3iCzByvphkC9p7hCf6LwbeF8XnVB91EAgRDA4VLE1f9wkcq5zjy879YWJ4r516h3PQszTz1EaJXNAXdbk5Em7eyuuabGP1Q3nijFTL2yhMDsXpgrjAuEAABNxFMd4J1JRMaic615mHrhwociksrsfQK" assertEquals(output.encoded, expectedString) } @Test fun testDeactivateStakeSign() { val deactivateStakeMessage = Solana.DeactivateStake.newBuilder().apply { - validatorPubkey = commonValidatorPubkey + stakeAccount = "6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv" }.build() val signingInput = Solana.SigningInput.newBuilder().apply { deactivateStakeTransaction = deactivateStakeMessage @@ -76,7 +78,7 @@ class TestSolanaSigner { @Test fun testWithdrawStakeSign() { val withdrawStakeMessage = Solana.WithdrawStake.newBuilder().apply { - validatorPubkey = commonValidatorPubkey + stakeAccount = "6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv" value = 42 }.build() val signingInput = Solana.SigningInput.newBuilder().apply { @@ -87,7 +89,7 @@ class TestSolanaSigner { val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser()) - val expectedString = "7Y1Wg1yHNs8MgWFiFSfcsRtqdMwZg8oGeQnTABYDfyDnof4VSFw63s3PuSxvUCJqqHKgYNVb8UTNcNiYHY8kng4NqTcVV5SA1KAWRzKHVGUxNWioAEXXVot5iJ1XbUWuuZUZBtsraaBjNyfmgWEDje3ESdGhiVL7vadU1uHeBuUKwM3nqB6yoeggeNyzmT34hs9utyehTFg48MAfrKEFKxaby7YZD6JbXFS1SyG1kxKWnCpoPgX3efwDwukmyDwxrKdABt9eTwmaiXKbTnK1hzBTatNfnJ9ePuWkhWFrjyDrGdx5S5KpybxET2vV9CSpExcD51BA6NPemTpjbhLYnJEzHWBGfYqfxu7p3257NHhpQQrSU56adk4dAQFjEYP" + val expectedString = "NL7WgagucfLd6AkTtcKe1dqd47xxzF356Q7tEhPrz1LRzZiAmokAaUkpwJ7X71Pmz97zZf9gZQU5BNswdcdpqUL8n1jwn4CoZMaPJhX5LF43Sj817cgreSG14TEWfKertpVpTtc5zY7vkDM7t9wjYhkaqgYz76HQtqAqRHnHF2Qr9EEfLj4zYRerWtyfS3EVyVUaasPxJ5vkcaonEfpGc6uWecaFr2A3YbzEBQpWXjMaXLqmMDtNS8rTNZmwvToa71ddFZKDgaHDcc6Lkg8qriZ3aQbUqL1TbeYp2mk9dWTKY62L1YFE2DyZV5P2qz5feywcMZ9JW6X1wBmiHFCseC42QbnbTibr1VdqLbGx7UWn5tHWk5jCN2aatEPfbFDZ" assertEquals(output.encoded, expectedString) } @@ -153,4 +155,15 @@ class TestSolanaSigner { val expectedString = "3Y2MVz2VVi7aEyC9q1awwdk1ModDBPHRSacKmTYnSgkmbbJeZ62Fub1bVPSHaTy4LUcQpzCQYhHAKtTKXUDYijEeLsMAUqPBEMAq1w8zCdqDpdXy6M4PuwNtYVV1WgqeiEsiMWpPp4BGWKfcziwFbmYueUGituacJq4wTnt92fho8mFi49XW64gEG4iNGScDtJkY7Geq8PKiLh1E9JMJoceiHxKbmxzCmmLTxEHdhySYHcDUSXnXWogZskeZNBMtR9dNjEMkCzEjrxRpBtJPtUNshciY45mDPNmw4j3xyLCBTRikyfFLc5g11r3UgyVD4YokoPRvrEXsgt6W3yjBshropBm6mY2eJYvfY2eZz4Yq8kLcUatCHVKtjcb1mP9Ww57KisJ9bRhipC8sodFaMYhZARMEa4a1u9eH4MyNUATRGNXarwQSBY46PWS3nKP6QBK7Dw7Ppp9MmYkdPcXKaLScbyLF3jKu6dHWMkHw3WdXSsM1wwXjXnWF9LxdwaEVcDmySWybj6aKD9QCWTU5kdncqJU56f7SYNRTN289WdUFGNDmSh56tj2v1" assertEquals(output.encoded, expectedString) } + + @Test + fun testSignJSON() { + val json = """ + {"recentBlockhash":"11111111111111111111111111111111","transferTransaction":{"recipient":"EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd","value":"42"}} + """ + val key = "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63".toHexByteArray() + val result = AnySigner.signJSON(json, key, SOLANA.value()) + + assertEquals("3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZUjikfDzbWBG2aFtG3gHT1QfLzyFKHM4HQtMQMNXqay1NAeiiYZjNhx9UvMX4uAQZ4Q6rx6m2AYfQ7aoMUrejq298q1wBFdtS9XVB5QTiStnzC7zs97FUEK2T4XapjF1519EyFBViTfHpGpnf5bfizDzsW9kYUtRDW1UC2LgHr7npgq5W9TBmHf9hSmRgM9XXucjXLqubNWE7HUMhbKjuBqkirRM", result) + } } 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..535970c9957 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt @@ -0,0 +1,136 @@ +package com.trustwallet.core.app.blockchains.solana + +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.Base64 +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 +import wallet.core.jni.proto.Solana.Encoding + +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) + } + + @Test + fun testSetPriorityFee() { + val privateKey = ByteString.copyFrom("baf2b2dbbbad7ca96c1fa199c686f3d8fbd2c7b352f307e37e04f33df6741f18".toHexByteArray()) + val originalTx = "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAEDODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG6GdPcA92ORzVJe2jfG8KQqqMHr9YTLu30oM4i7MFEoBAgIAAQwCAAAA6AMAAAAAAAA=" + + // Step 1 - Check if there are no price and limit instructions in the original transaction. + assertEquals(SolanaTransaction.getComputeUnitPrice(originalTx), null) + assertEquals(SolanaTransaction.getComputeUnitLimit(originalTx), null) + + // Step 2 - Set price and limit instructions. + val txWithPrice = SolanaTransaction.setComputeUnitPrice(originalTx, "1000") + val updatedTx = SolanaTransaction.setComputeUnitLimit(txWithPrice, "10000") + + assertEquals(updatedTx, "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA") + + // Step 3 - Check if price and limit instructions are set successfully. + assertEquals(SolanaTransaction.getComputeUnitPrice(updatedTx), "1000") + assertEquals(SolanaTransaction.getComputeUnitLimit(updatedTx), "10000") + + // Step 4 - Decode transaction into a `RawMessage` Protobuf. + val updatedTxData = Base64.decode(updatedTx) + val decodedData = TransactionDecoder.decode(SOLANA, updatedTxData) + val decodedOutput = DecodingTransactionOutput.parseFrom(decodedData) + + assertEquals(decodedOutput.error, SigningError.OK) + + // Step 5 - Sign the decoded `RawMessage` transaction. + val signingInput = SigningInput.newBuilder() + .setPrivateKey(privateKey) + .setRawMessage(decodedOutput.transaction) + .setTxEncoding(Encoding.Base64) + .build() + val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser()) + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/2ho7wZUXbDNz12xGfsXg2kcNMqkBAQjv7YNXNcVcuCmbC4p9FZe9ELeM2gMjq9MKQPpmE3nBW5pbdgwVCfNLr1h8 + val expectedString = "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA" + assertEquals(output.encoded, expectedString) + } + + @Test + fun testSetFeePayer() { + val originalTx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA==" + + // Step 1 - Add fee payer to the transaction. + val updatedTx = SolanaTransaction.setFeePayer(originalTx, "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ") + assertEquals(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==") + + // This case originates from a test case in C++. Here, only the most critical function is verified for correctness, + // while the remaining steps have been omitted. + // Step 2 - Decode transaction into a `RawMessage` Protobuf. + // Step 3 - Obtain preimage hash. + // Step 4 - Compile transaction info. + } +} \ 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/TestStellarAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt index ddd89799276..9d11510cf1f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt @@ -18,7 +18,7 @@ class TestAddress { val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.STELLAR) - assertEquals(pubkey.data().toHex(), "0x09A966BCAACC103E38896BAAE3F8C2F06C21FD47DD4F864FF0D33F9819DF5CA2".toLowerCase()) + assertEquals(pubkey.data().toHex(), "0x09A966BCAACC103E38896BAAE3F8C2F06C21FD47DD4F864FF0D33F9819DF5CA2".lowercase()) assertEquals(address.description(), "GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI") } 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 0f1dbf2dac6..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 @@ -1,15 +1,15 @@ -package com.trustwalval.core.app.blockchains.terra +package com.trustwallet.core.app.blockchains.terra -import android.util.Log 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.* -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 +import wallet.core.java.AnySigner class TestTerraTransactions { @@ -20,20 +20,19 @@ 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 + amount = "1000000" denom = "uluna" }.build() 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,20 +50,65 @@ 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 testSigningWasmTerraTransferTx() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRAV2).description() + + val wasmTransferMessage = Cosmos.Message.WasmExecuteContractTransfer.newBuilder().apply { + senderAddress = from + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" + amount = ByteString.copyFrom("0x3D090".toHexByteArray()) // 250000 + recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmExecuteContractTransferMessage = 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 = "phoenix-1" + memo = "" + sequence = 3 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRAV2, SigningOutput.parser()) + 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..635d615734a --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt @@ -0,0 +1,124 @@ +// 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(ByteString.copyFrom("0A".toHexByteArray())) // 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(ByteString.copyFrom("1DCD6500".toHexByteArray())) // 500 * 1000 * 1000 + .setToOwner("EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8") + .setResponseAddress("EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk") + .setForwardAmount(ByteString.copyFrom("01".toHexByteArray())) // 1 + .build() + + val transfer = TheOpenNetwork.Transfer.newBuilder() + .setDest("EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja") + .setAmount(ByteString.copyFrom("05F5E100".toHexByteArray())) // 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 transfer = TheOpenNetwork.Transfer.newBuilder() + .setDest(dogeChatbotDeployingAddress) + // 0.069 TON + .setAmount(ByteString.copyFrom("041CDB40".toHexByteArray())) // 69_000_000 + .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) + .setBounceable(false) + .setStateInit(dogeChatbotStateInit) + .setCustomPayload(commentPayload) + + 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 new file mode 100644 index 00000000000..0974995d3f1 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainAddress.kt @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.thorchain + +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.* +import wallet.core.jni.CoinType.THORCHAIN + +class TestTHORChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddressValidation() { + var addr = listOf( + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65" + ) + addr.forEach { + assert(CoinType.THORCHAIN.validate(it)) + assertEquals(it, AnyAddress(it, CoinType.THORCHAIN).description()) + } + } +} 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 new file mode 100644 index 00000000000..fde6325529f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.thorchain + +import android.util.Log +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.THORCHAIN +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 TestTHORChainSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun THORChainTransactionSigning() { + val key = + PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, THORCHAIN).data() + val to = AnyAddress("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", THORCHAIN).data() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "38000000" + denom = "rune" + }.build() + + val sendCoinsMsg = Cosmos.Message.THORChainSend.newBuilder().apply { + fromAddress = ByteString.copyFrom(from) + toAddress = ByteString.copyFrom(to) + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + thorchainSendMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "200" + denom = "rune" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 2500000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + chainId = "thorchain-mainnet-v1" + accountNumber = 593 + sequence = 21 + memo = "" + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, THORCHAIN, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=\"}") + 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 new file mode 100644 index 00000000000..68f0f8acc55 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt @@ -0,0 +1,75 @@ +package com.trustwallet.core.app.blockchains.thorchainswap + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import wallet.core.jni.PrivateKey +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.ETHEREUM +import wallet.core.jni.proto.Ethereum.SigningOutput +import wallet.core.jni.proto.THORChainSwap +import wallet.core.jni.THORChainSwap.buildSwap +import com.trustwallet.core.app.utils.Numeric + +class TestTHORChainSwap { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSwapEthBnbWithFee() { + // prepare swap input + val input = THORChainSwap.SwapInput.newBuilder() + input.apply { + fromAsset = THORChainSwap.Asset.newBuilder().apply { + chain = THORChainSwap.Chain.ETH + }.build() + fromAddress = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7" + toAsset = THORChainSwap.Asset.newBuilder().apply { + chain = THORChainSwap.Chain.BNB + symbol = "BNB" + tokenId = "" + }.build() + toAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" + vaultAddress = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC" + routerAddress = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B" + fromAmount = "50000000000000000" + toAmountLimit = "600003" + affiliateFeeAddress = "tthor1ql2tcqyrqsgnql2tcqyj2n8kfdmt9lh0yzql2tcqy" + affiliateFeeRateBp = "10" + } + + // serialize input + val inputSerialized = input.build().toByteArray() + assertEquals(Numeric.toHexString(inputSerialized), "0x0a020802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a11353030303030303030303030303030303042063630303030334a2f7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c327463717952023130") + + // invoke swap + val outputData = buildSwap(inputSerialized) + assertEquals(outputData.count(), 192) + + // parse result in proto + val outputProto = THORChainSwap.SwapOutput.newBuilder().mergeFrom(outputData) + assertEquals(outputProto.fromChain, THORChainSwap.Chain.ETH) + assertEquals(outputProto.toChain, THORChainSwap.Chain.BNB) + assertEquals(outputProto.error.code, THORChainSwap.ErrorCode.OK) + assertTrue(outputProto.hasEthereum()) + val txInput = outputProto.ethereum + + // set few fields before signing + val txInputFull = txInput.toBuilder().apply { + chainId = ByteString.copyFrom("0x01".toHexByteArray()) + nonce = ByteString.copyFrom("0x03".toHexByteArray()) + gasPrice = ByteString.copyFrom("0x06FC23AC00".toHexByteArray()) + gasLimit = ByteString.copyFrom("0x013880".toHexByteArray()) + privateKey = ByteString.copyFrom(PrivateKey("0x4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904".toHexByteArray()).data()) + }.build() + + // sign and encode resulting input + val output = AnySigner.sign(txInputFull, ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f8d90103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b86e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030333a7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c32746371793a3130c001a05c16871b66fd0fa8f658d6f171310bab332d09e0533d6c97329a59ddc93a9a11a05ed2be94e6dbb640e58920c8be4fa597cd5f0a918123245acb899042dd43777f") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ton/TestTONAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ton/TestTONAddress.kt deleted file mode 100644 index fa07137d530..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ton/TestTONAddress.kt +++ /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. - -package com.trustwallet.core.app.blockchains.ton - -import com.trustwallet.core.app.utils.toHexByteArray -import org.junit.Assert.assertEquals -import org.junit.Test -import wallet.core.jni.* - -class TestTONAddress { - - init { - System.loadLibrary("TrustWalletCore") - } - - @Test - fun testAddress() { - val pubkey = PublicKey("F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3".toHexByteArray(), PublicKeyType.ED25519) - val address = AnyAddress(pubkey, CoinType.TON) - val expected = AnyAddress("EQAkAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHUx3Y", CoinType.TON) - - assertEquals(address.description(), expected.description()) - } -} 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..7730a59f9ca --- /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("bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c", 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/waves/TestWavesAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt index d0669b9feca..ef8b4217e6c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt @@ -18,7 +18,7 @@ class TestAddress { val pubkey = key.publicKeyCurve25519 val address = AnyAddress(pubkey, CoinType.WAVES) - assertEquals(pubkey.data().toHex(), "0x559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d".toLowerCase()) + assertEquals(pubkey.data().toHex(), "0x559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d".lowercase()) assertEquals(address.description(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds") } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zcash/TestZcashSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zcash/TestZcashSigner.kt new file mode 100644 index 00000000000..a5e7a434ef2 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zcash/TestZcashSigner.kt @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.zcash + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertEquals +import org.junit.Test + +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType.ZCASH +import wallet.core.jni.proto.Common.SigningError +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Bitcoin.SigningOutput +import wallet.core.jni.proto.BitcoinV2 +import wallet.core.jni.proto.Utxo +import wallet.core.jni.proto.Zcash + +class TestZcashSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignZcashV2() { + // Successfully broadcasted: https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 + val privateKeyData = Numeric.hexStringToByteArray("a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559") + val dustSatoshis = 546.toLong() + val senderAddress = "t1gWVE2uyrET2CxSmCaBiKzmWxQdHhnvMSz" + val toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS" + val sapplingBranchId = Numeric.hexStringToByteArray("0xbb09b876") + + val txid0 = Numeric.hexStringToByteArray("3a19dd44032dfed61bfca5ba5751aab8a107b30609cbd5d70dc5ef09885b6853").reversedArray() + val utxo0 = BitcoinV2.Input.newBuilder() + .setOutPoint(Utxo.OutPoint.newBuilder().apply { + hash = ByteString.copyFrom(txid0) + vout = 0 + }) + .setValue(494_000) + .setSighashType(BitcoinSigHashType.ALL.value()) + .setReceiverAddress(senderAddress) + + val out0 = BitcoinV2.Output.newBuilder() + .setValue(488_000) + .setToAddress(toAddress) + + val builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.UseDefault) + .addInputs(utxo0) + .addOutputs(out0) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + // Set ZCash specific extra parameters. + .setZcashExtraData(Zcash.TransactionBuilderExtraData.newBuilder().apply { + branchId = ByteString.copyFrom(sapplingBranchId) + }) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 184 + p2ShPrefix = 189 + }) + .build() + + val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { + signingV2 = signingInput + coinType = ZCASH.value() + } + + val output = AnySigner.sign(legacySigningInput.build(), ZCASH, SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.signingResultV2.errorMessage, "") + assertEquals(output.signingResultV2.error, SigningError.OK) + assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000") + assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0xec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256") + } +} 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..e440a5f0f32 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAnyAddress.kt @@ -0,0 +1,93 @@ +// 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") + + val address3 = AnyAddress(pubkey, coin, Derivation.BITCOINTAPROOT) + assertEquals(address3.description(), "bc1pnncpg8s7gu7t6xmmzxqarcj8ydthmaz8gr4m76eephjfprs53maswgel0w") + } + + @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/TestBech32.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBech32.kt new file mode 100644 index 00000000000..64b32728c5c --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBech32.kt @@ -0,0 +1,49 @@ +package com.trustwallet.core.app.utils + +import org.junit.Assert.* +import org.junit.Test +import wallet.core.jni.Bech32 + +class TestBech32 { + init { + System.loadLibrary("TrustWalletCore"); + } + + @Test + fun testEncode() { + val data = Numeric.hexStringToByteArray("00443214c74254b635cf84653a56d7c675be77df") + assertEquals(Bech32.encode("abcdef", data), "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw") + } + + @Test + fun testDecode() { + val decoded = Bech32.decode("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw") + assertEquals(Numeric.toHexString(decoded), "0x00443214c74254b635cf84653a56d7c675be77df") + } + + @Test + fun testDecodeWrongChecksumVariant() { + // This is a Bech32m variant, not Bech32 variant. So it should fail using Bech32 decoder. + val decoded = Bech32.decode("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx") + assertNull(decoded) + } + + @Test + fun testEncodeM() { + val data = Numeric.hexStringToByteArray("ffbbcdeb38bdab49ca307b9ac5a928398a418820") + assertEquals(Bech32.encodeM("abcdef", data), "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx") + } + + @Test + fun testDecodeM() { + val decoded = Bech32.decodeM("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx") + assertEquals(Numeric.toHexString(decoded), "0xffbbcdeb38bdab49ca307b9ac5a928398a418820") + } + + @Test + fun testDecodeMWrongChecksumVariant() { + // This is a Bech32 variant, not Bech32m variant. So it should fail using Bech32M decoder. + val decoded = Bech32.decodeM("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw") + assertNull(decoded) + } +} 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/TestData.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt index 439c74f99ab..4df94d6b558 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestData.kt @@ -15,25 +15,22 @@ class TestData { assertEquals(Numeric.toHexString(data), "0x01020304") } + @Test fun testUsingExtensions() { - { - val data = "01020304".toHexBytes() - assertEquals(data.toHex(), "0x01020304") - } - { // with prefix - val data = "0x01020304".toHexBytes() - assertEquals(data.toHex(), "0x01020304") - } + val data = "01020304".toHexBytes() + assertEquals(data.toHex(), "0x01020304") + + // with prefix + val data2 = "0x01020304".toHexBytes() + assertEquals(data2.toHex(), "0x01020304") } + @Test fun testOddLength() { - { - val data = "0x0".toHexBytes() - assertEquals(data.toHex(), "0x00") - } - { - val data = "0x28fa6ae00".toHexBytes() - assertEquals(data.toHex(), "0x28fa6ae00") - } + val data = "0x0".toHexBytes() + assertEquals(data.toHex(), "0x00") + + val data2 = "0x28fa6ae00".toHexBytes() + assertEquals(data2.toHex(), "0x028fa6ae00") } } 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 a9b18786966..b4f3d147fb6 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,16 +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 { @@ -22,16 +24,176 @@ class TestHDWallet { val password = "TREZOR" @Test - fun testSeed() { + 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) + assertEquals(hd.mnemonic(), words) + assertEquals(Numeric.toHexString(hd.entropy()), "0xba5821e8c356c05ba5f025d9532fe0f21f65d594") assertEquals(Numeric.toHexString(hd.seed()), "0x7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29") } @Test - fun testSeedNoPass() { + fun testCreateFromMnemonicNoPass() { val hd = HDWallet(words, "") - assertEquals(Numeric.toHexString(hd.seed()), "0x354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d" - ) + assertEquals(Numeric.toHexString(hd.seed()), "0x354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d") + } + + @Test + fun testCreateFromSpanishMnemonic() { + val mnemonic = "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut" + try { + HDWallet(mnemonic, "") + fail("Missing exception") + } catch (ex: Exception) { + assertTrue(ex is InvalidParameterException) + } + val wallet = HDWallet(mnemonic, "", false) + val address = wallet.getAddressForCoin(CoinType.ETHEREUM) + + assertEquals(address, "0xa4531dE99E22B2166d340E7221669DF565c52024") + } + + @Test + fun testGenerate() { + val hd = HDWallet(128, "") + assertTrue(Mnemonic.isValid(hd.mnemonic())) + } + + @Test + fun tesCreateFromEntropy() { + val hd = HDWallet(Numeric.hexStringToByteArray("ba5821e8c356c05ba5f025d9532fe0f21f65d594"), "TREZOR") + assertEquals(hd.mnemonic(), words) + assertEquals(Numeric.toHexString(hd.entropy()), "0xba5821e8c356c05ba5f025d9532fe0f21f65d594") + } + + @Test + fun testGetKeyForCoinBitcoin() { + val coin = CoinType.BITCOIN + val wallet = HDWallet(words, password) + val key = wallet.getKeyForCoin(coin) + + val address = coin.deriveAddress(key) + assertEquals(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + @Test + fun testGetKeyDerivationBitcoin() { + 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") + + val key4 = wallet.getKeyDerivation(coin, Derivation.BITCOINTAPROOT) + assertEquals(key4.data().toHex(), "0xa2c4d6df786f118f20330affd65d248ffdc0750ae9cbc729d27c640302afd030") + } + + @Test + fun testGetAddressForCoinBitcoin() { + val coin = CoinType.BITCOIN + val wallet = HDWallet(words, password) + + val address = wallet.getAddressForCoin(coin) + assertEquals(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + @Test + fun testGetAddressDerivationBitcoin() { + 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") + + val address4 = wallet.getAddressDerivation(coin, Derivation.BITCOINTAPROOT) + assertEquals(address4, "bc1pgqks0cynn93ymve4x0jq3u7hne77908nlysp289hc44yc4cmy0hslyckrz") + } + + @Test + fun testGetKeyForCoinPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + val key = wallet.getKeyForCoin(coin) + + val address = coin.deriveAddress(key) + assertEquals(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + @Test + fun testGetKeyDerivationPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + + val key1 = wallet.getKeyDerivation(coin, Derivation.PACTUSMAINNET) + assertEquals(key1.data().toHex(), "0x153fefb8168f246f9f77c60ea10765c1c39828329e87284ddd316770717f3a5e") + + val key2 = wallet.getKeyDerivation(coin, Derivation.PACTUSTESTNET) + assertEquals(key2.data().toHex(), "0x54f3c54dd6af5794bea1f86de05b8b9f164215e8deee896f604919046399e54d") + } + + @Test + fun testGetAddressForCoinPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + + val address = wallet.getAddressForCoin(coin) + assertEquals(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + @Test + fun testGetAddressDerivationPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + + val address1 = wallet.getAddressDerivation(coin, Derivation.PACTUSMAINNET) + assertEquals(address1, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + + val address2 = wallet.getAddressDerivation(coin, Derivation.PACTUSTESTNET) + assertEquals(address2, "tpc1rjtamyqp203j4367q4plkp4qt32d7sv34kfmj5e") } @Test 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..9b00f5adbfa 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() @@ -80,4 +91,36 @@ class TestKeyStore { val privateKey = newKeyStore.decryptPrivateKey("".toByteArray()) assertNull(privateKey) } + + @Test + fun testImportKeyEncodedEthereum() { + val privateKeyHex = "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + val password = "password".toByteArray() + val key = StoredKey.importPrivateKeyEncoded(privateKeyHex, "name", password, CoinType.ETHEREUM) + val json = key.exportJSON() + + val keyStore = StoredKey.importJSON(json) + val storedEncoded = keyStore.decryptPrivateKeyEncoded(password) + + assertTrue(keyStore.hasPrivateKeyEncoded()) + assertNotNull(keyStore) + assertNotNull(storedEncoded) + assertEquals(privateKeyHex, storedEncoded) + } + + @Test + fun testImportKeyEncodedSolana() { + val privateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr" + val password = "password".toByteArray() + val key = StoredKey.importPrivateKeyEncoded(privateKeyBase58, "name", password, CoinType.SOLANA) + val json = key.exportJSON() + + val keyStore = StoredKey.importJSON(json) + val storedEncoded = keyStore.decryptPrivateKeyEncoded(password) + + assertTrue(keyStore.hasPrivateKeyEncoded()) + assertNotNull(keyStore) + assertNotNull(storedEncoded) + assertEquals(privateKeyBase58, storedEncoded) + } } 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/TestMnemonic.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestMnemonic.kt index b673f9dc561..4c6082b9756 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestMnemonic.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestMnemonic.kt @@ -21,6 +21,9 @@ class TestMnemonic { @Test fun testIsWordValid() { assertTrue(Mnemonic.isValidWord("credit")); + + assertFalse(Mnemonic.isValidWord("di")); + assertFalse(Mnemonic.isValidWord("cr")); assertFalse(Mnemonic.isValidWord("hybridous")); } 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 c6d069ae45b..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() - 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") + fun testGetPublicKeyCoinType() { + val privateKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5".toHexBytes() + val privateKey = PrivateKey(privateKeyData) + assertEquals(privateKey.getPublicKey(CoinType.ETHEREUM).data().toHex(), "0x0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); } - - @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()) - } - - @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 bf5201ac072..9e475a4589b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,31 +1,26 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - ext.kotlin_version = '1.4.21' + ext.kotlin_version = '2.1.0' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:8.8.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.15.2' } } allprojects { repositories { google() - jcenter() + mavenCentral() } } 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 493d6573668..36074adc5de 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Mar 16 12:35:47 JST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip diff --git a/android/gradlew b/android/gradlew index fbd7c515832..d0054d28acf 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 -s )" 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 72831e7be9f..00000000000 --- a/android/trustwalletcore/build.gradle +++ /dev/null @@ -1,51 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'maven-publish' -group='com.github.trustwallet' - -android { - compileSdkVersion 28 - ndkVersion '21.2.6472646' - defaultConfig { - minSdkVersion 23 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - } - - lintOptions { - abortOnError false - disable 'InvalidPackage' - } - - buildTypes { - release { - minifyEnabled false - } - debug { - minifyEnabled false - // limit platforms built for testing - ndk { - abiFilters 'x86' - } - } - } - - sourceSets { - main.java.srcDirs += '../../jni/java' - } - - externalNativeBuild { - cmake { - version "3.10.2" - path "../../CMakeLists.txt" - } - } -} - -dependencies { - implementation 'io.grpc:grpc-protobuf:1.34.0' -} - -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..fe07d5c85bd --- /dev/null +++ b/android/wallet-core/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' + +group = 'com.trustwallet' + +android { + namespace 'wallet.core' + compileSdk 35 + ndkVersion '28.0.12674087' + defaultConfig { + minSdkVersion 23 + externalNativeBuild { + cmake { + arguments "-DCMAKE_BUILD_TYPE=Release", "-DTW_UNITY_BUILD=ON" + } + } + } + + + 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() + } + } + lint { + abortOnError false + disable 'InvalidPackage' + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +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 e5c948496d8..10e5812aa06 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,27 +1,47 @@ #!/usr/bin/env bash +# +# Initializes the workspace with dependencies, then performs full build # Fail if any commands fails set -e -echo "#### Initializing... ####" +source tools/parse_args "$@" + +if isHelp; then + echo "usage: bootstrap.sh [-h | --help] [all | wasm | android | ios]" + echo "" + echo "Installs dependencies and prepares WalletCore for building" + exit 0 +fi + +echo "#### Installing system dependencies ... ####" +if [[ $(uname -s) == "Darwin" ]]; then + tools/install-sys-dependencies-mac +else + tools/install-sys-dependencies-linux +fi + +echo "#### Installing C++ libraries ... ####" tools/install-dependencies -echo "#### Generating files... ####" -tools/generate-files +echo "#### Installing Rust toolchain and tools ... ####" +tools/install-rust-dependencies dev -echo "#### Building... ####" -cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -make -Cbuild -j12 tests TrezorCryptoTests +# WASM +if isTargetSpecified "wasm"; then + echo "#### Installing WASM environment ... ####" + tools/install-wasm-dependencies +fi -if [ -x "$(command -v clang-tidy)" ]; then - echo "#### Linting... ####" - tools/lint +# Android +if isTargetSpecified "android"; then + echo "#### Installing Android dependencies ... ####" + tools/install-android-dependencies fi -echo "#### Testing... ####" -export CK_TIMEOUT_MULTIPLIER=4 -build/trezor-crypto/crypto/tests/TrezorCryptoTests +echo "#### Generating files... ####" +tools/generate-files "$@" -ROOT="`dirname \"$0\"`" -TESTS_ROOT="`(cd \"$ROOT/tests\" && pwd)`" -build/tests/tests "$TESTS_ROOT" +echo "" +echo "WalletCore is ready for development!" +echo "Consider running native C++ tests via './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 3ef08f79653..6228eed7bfc 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -1,5 +1,9 @@ -set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.14.0) -set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.14.0) +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + +set(protobuf_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.20.3) +set(protobuf_source_dir ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.20.3) # sort + uniq -u # https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotobuf.cmake @@ -12,6 +16,7 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/api.pb.cc ${protobuf_source_dir}/src/google/protobuf/arena.cc ${protobuf_source_dir}/src/google/protobuf/arenastring.cc + ${protobuf_source_dir}/src/google/protobuf/arenaz_sampler.cc ${protobuf_source_dir}/src/google/protobuf/compiler/importer.cc ${protobuf_source_dir}/src/google/protobuf/compiler/parser.cc ${protobuf_source_dir}/src/google/protobuf/descriptor.cc @@ -24,11 +29,13 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/extension_set_heavy.cc ${protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc - ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc - ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_util.cc ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc + ${protobuf_source_dir}/src/google/protobuf/inlined_string_field.cc ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc ${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc ${protobuf_source_dir}/src/google/protobuf/io/io_win32.cc @@ -43,8 +50,10 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/message.cc ${protobuf_source_dir}/src/google/protobuf/message_lite.cc ${protobuf_source_dir}/src/google/protobuf/parse_context.cc + ${protobuf_source_dir}/src/google/protobuf/reflection_internal.h ${protobuf_source_dir}/src/google/protobuf/reflection_ops.cc ${protobuf_source_dir}/src/google/protobuf/repeated_field.cc + ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc ${protobuf_source_dir}/src/google/protobuf/service.cc ${protobuf_source_dir}/src/google/protobuf/source_context.pb.cc ${protobuf_source_dir}/src/google/protobuf/struct.pb.cc @@ -78,7 +87,6 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc - ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.cc ${protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc ${protobuf_source_dir}/src/google/protobuf/util/json_util.cc ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc @@ -94,7 +102,9 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/any.pb.h ${protobuf_source_dir}/src/google/protobuf/api.pb.h ${protobuf_source_dir}/src/google/protobuf/arena.h + ${protobuf_source_dir}/src/google/protobuf/arena_impl.h ${protobuf_source_dir}/src/google/protobuf/arenastring.h + ${protobuf_source_dir}/src/google/protobuf/arenaz_sampler.h ${protobuf_source_dir}/src/google/protobuf/compiler/importer.h ${protobuf_source_dir}/src/google/protobuf/compiler/parser.h ${protobuf_source_dir}/src/google/protobuf/descriptor.h @@ -103,39 +113,67 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/duration.pb.h ${protobuf_source_dir}/src/google/protobuf/dynamic_message.h ${protobuf_source_dir}/src/google/protobuf/empty.pb.h + ${protobuf_source_dir}/src/google/protobuf/explicitly_constructed.h ${protobuf_source_dir}/src/google/protobuf/extension_set.h + ${protobuf_source_dir}/src/google/protobuf/extension_set_inl.h + ${protobuf_source_dir}/src/google/protobuf/field_access_listener.h ${protobuf_source_dir}/src/google/protobuf/field_mask.pb.h + ${protobuf_source_dir}/src/google/protobuf/generated_enum_reflection.h + ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.h ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_decl.h + ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.h ${protobuf_source_dir}/src/google/protobuf/generated_message_util.h + ${protobuf_source_dir}/src/google/protobuf/has_bits.h ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.h + ${protobuf_source_dir}/src/google/protobuf/inlined_string_field.h ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.h ${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.h + ${protobuf_source_dir}/src/google/protobuf/io/io_win32.h ${protobuf_source_dir}/src/google/protobuf/io/printer.h ${protobuf_source_dir}/src/google/protobuf/io/strtod.h ${protobuf_source_dir}/src/google/protobuf/io/tokenizer.h ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.h ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl.h ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.h + ${protobuf_source_dir}/src/google/protobuf/map.h + ${protobuf_source_dir}/src/google/protobuf/map_entry.h + ${protobuf_source_dir}/src/google/protobuf/map_entry_lite.h ${protobuf_source_dir}/src/google/protobuf/map_field.h + ${protobuf_source_dir}/src/google/protobuf/map_field_inl.h + ${protobuf_source_dir}/src/google/protobuf/map_field_lite.h + ${protobuf_source_dir}/src/google/protobuf/map_type_handler.h ${protobuf_source_dir}/src/google/protobuf/message.h ${protobuf_source_dir}/src/google/protobuf/message_lite.h + ${protobuf_source_dir}/src/google/protobuf/metadata.h + ${protobuf_source_dir}/src/google/protobuf/metadata_lite.h ${protobuf_source_dir}/src/google/protobuf/parse_context.h + ${protobuf_source_dir}/src/google/protobuf/port.h + ${protobuf_source_dir}/src/google/protobuf/reflection.h ${protobuf_source_dir}/src/google/protobuf/reflection_ops.h ${protobuf_source_dir}/src/google/protobuf/repeated_field.h + ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.h ${protobuf_source_dir}/src/google/protobuf/service.h ${protobuf_source_dir}/src/google/protobuf/source_context.pb.h ${protobuf_source_dir}/src/google/protobuf/struct.pb.h ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.h + ${protobuf_source_dir}/src/google/protobuf/stubs/callback.h + ${protobuf_source_dir}/src/google/protobuf/stubs/casts.h ${protobuf_source_dir}/src/google/protobuf/stubs/common.h - ${protobuf_source_dir}/src/google/protobuf/stubs/int128.h + ${protobuf_source_dir}/src/google/protobuf/stubs/hash.h + ${protobuf_source_dir}/src/google/protobuf/stubs/logging.h + ${protobuf_source_dir}/src/google/protobuf/stubs/macros.h + ${protobuf_source_dir}/src/google/protobuf/stubs/map_util.h + ${protobuf_source_dir}/src/google/protobuf/stubs/mutex.h ${protobuf_source_dir}/src/google/protobuf/stubs/once.h + ${protobuf_source_dir}/src/google/protobuf/stubs/platform_macros.h + ${protobuf_source_dir}/src/google/protobuf/stubs/port.h ${protobuf_source_dir}/src/google/protobuf/stubs/status.h - ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.h + ${protobuf_source_dir}/src/google/protobuf/stubs/stl_util.h ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.h - ${protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.h ${protobuf_source_dir}/src/google/protobuf/stubs/strutil.h - ${protobuf_source_dir}/src/google/protobuf/stubs/substitute.h - ${protobuf_source_dir}/src/google/protobuf/stubs/time.h + ${protobuf_source_dir}/src/google/protobuf/stubs/template_util.h ${protobuf_source_dir}/src/google/protobuf/text_format.h ${protobuf_source_dir}/src/google/protobuf/timestamp.pb.h ${protobuf_source_dir}/src/google/protobuf/type.pb.h @@ -143,23 +181,10 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/util/delimited_message_util.h ${protobuf_source_dir}/src/google/protobuf/util/field_comparator.h ${protobuf_source_dir}/src/google/protobuf/util/field_mask_util.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.h - ${protobuf_source_dir}/src/google/protobuf/util/internal/utility.h ${protobuf_source_dir}/src/google/protobuf/util/json_util.h ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.h ${protobuf_source_dir}/src/google/protobuf/util/time_util.h + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver.h ${protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.h ${protobuf_source_dir}/src/google/protobuf/wire_format.h ${protobuf_source_dir}/src/google/protobuf/wire_format_lite.h @@ -173,7 +198,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 @@ -181,10 +206,10 @@ set_target_properties( LINK_FLAGS -no-undefined ) -target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32) +target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32 -Wno-invalid-noreturn) install(TARGETS protobuf LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/protobuf - ) \ No newline at end of file +) diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake new file mode 100644 index 00000000000..14a781a120d --- /dev/null +++ b/cmake/StandardSettings.cmake @@ -0,0 +1,88 @@ +# +# Default settings +# +if (NOT FLUTTER) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) +endif () +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 OR FLUTTER) + 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/ios.toolchain.cmake b/cmake/ios.toolchain.cmake deleted file mode 100644 index fc4134843c3..00000000000 --- a/cmake/ios.toolchain.cmake +++ /dev/null @@ -1,710 +0,0 @@ -# This file is part of the ios-cmake project. It was retrieved from -# https://github.com/cristeab/ios-cmake.git, which is a fork of -# https://code.google.com/p/ios-cmake/. Which in turn is based off of -# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which -# are included with CMake 2.8.4 -# -# The ios-cmake project is licensed under the new BSD license. -# -# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software, -# Kitware, Inc., Insight Software Consortium. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# This file is based off of the Platform/Darwin.cmake and -# Platform/UnixPaths.cmake files which are included with CMake 2.8.4 -# It has been altered for iOS development. -# -# Updated by Alex Stewart (alexs.mac@gmail.com) -# -# ***************************************************************************** -# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com) -# under the BSD-3-Clause license -# https://github.com/leetal/ios-cmake -# ***************************************************************************** -# -# INFORMATION / HELP -# -# The following arguments control the behaviour of this toolchain: -# -# PLATFORM: (default "OS") -# OS = Build for iPhoneOS. -# OS64 = Build for arm64 iphoneOS. -# OS64COMBINED = Build for arm64 x86_64 iphoneOS. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) -# SIMULATOR = Build for x86 i386 iphoneOS Simulator. -# SIMULATOR64 = Build for x86_64 iphoneOS Simulator. -# TVOS = Build for arm64 tvOS. -# TVOSCOMBINED = Build for arm64 x86_64 tvOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY) -# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator. -# WATCHOS = Build for armv7k arm64_32 for watchOS. -# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY) -# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator. -# MAC_CATALYST = Build for macOS Catalyst -# -# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is -# automatically determined from PLATFORM and xcodebuild, but -# can also be manually specified (although this should not be required). -# -# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform -# being compiled for. By default this is automatically determined from -# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should -# not be required). -# -# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 2.0 on watchOS and 9.0 on tvOS+iOS -# -# ENABLE_BITCODE: (1|0) Enables or disables bitcode support. Default 1 (true) -# -# ENABLE_ARC: (1|0) Enables or disables ARC support. Default 1 (true, ARC enabled by default) -# -# ENABLE_VISIBILITY: (1|0) Enables or disables symbol visibility support. Default 0 (false, visibility hidden by default) -# -# ENABLE_STRICT_TRY_COMPILE: (1|0) Enables or disables strict try_compile() on all Check* directives (will run linker -# to actually check if linking is possible). Default 0 (false, will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY) -# -# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM -# OS = armv7 armv7s arm64 (if applicable) -# OS64 = arm64 (if applicable) -# SIMULATOR = i386 -# SIMULATOR64 = x86_64 -# TVOS = arm64 -# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated) -# WATCHOS = armv7k arm64_32 (if applicable) -# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated) -# MAC_CATALYST = x86_64 -# -# This toolchain defines the following variables for use externally: -# -# XCODE_VERSION: Version number (not including Build version) of Xcode detected. -# SDK_VERSION: Version of SDK being used. -# CMAKE_OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM). -# -# This toolchain defines the following macros for use externally: -# -# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT) -# A convenience macro for setting xcode specific properties on targets. -# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel -# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all"). -# -# find_host_package (PROGRAM ARGS) -# A macro used to find executable programs on the host system, not within the -# environment. Thanks to the android-cmake project for providing the -# command. -# -# ******************************** DEPRECATIONS ******************************* -# -# IOS_DEPLOYMENT_TARGET: (Deprecated) Alias to DEPLOYMENT_TARGET -# CMAKE_IOS_DEVELOPER_ROOT: (Deprecated) Alias to CMAKE_DEVELOPER_ROOT -# IOS_PLATFORM: (Deprecated) Alias to PLATFORM -# IOS_ARCH: (Deprecated) Alias to ARCHS -# -# ***************************************************************************** -# - -# Fix for PThread library not in path -set(CMAKE_THREAD_LIBS_INIT "-lpthread") -set(CMAKE_HAVE_THREADS_LIBRARY 1) -set(CMAKE_USE_WIN32_THREADS_INIT 0) -set(CMAKE_USE_PTHREADS_INIT 1) - -# Cache what generator is used -set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}" CACHE STRING "Expose CMAKE_GENERATOR" FORCE) - -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") - set(MODERN_CMAKE YES) -endif() - -# Get the Xcode version being used. -execute_process(COMMAND xcodebuild -version - OUTPUT_VARIABLE XCODE_VERSION - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) -string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION "${XCODE_VERSION}") -string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION "${XCODE_VERSION}") - -######## ALIASES (DEPRECATION WARNINGS) - -if(DEFINED IOS_PLATFORM) - set(PLATFORM ${IOS_PLATFORM}) - message(DEPRECATION "IOS_PLATFORM argument is DEPRECATED. Consider using the new PLATFORM argument instead.") -endif() - -if(DEFINED IOS_DEPLOYMENT_TARGET) - set(DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET}) - message(DEPRECATION "IOS_DEPLOYMENT_TARGET argument is DEPRECATED. Consider using the new DEPLOYMENT_TARGET argument instead.") -endif() - -if(DEFINED CMAKE_IOS_DEVELOPER_ROOT) - set(CMAKE_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT}) - message(DEPRECATION "CMAKE_IOS_DEVELOPER_ROOT argument is DEPRECATED. Consider using the new CMAKE_DEVELOPER_ROOT argument instead.") -endif() - -if(DEFINED IOS_ARCH) - set(ARCHS ${IOS_ARCH}) - message(DEPRECATION "IOS_ARCH argument is DEPRECATED. Consider using the new ARCHS argument instead.") -endif() - -######## END ALIASES - -# Unset the FORCE on cache variables if in try_compile() -set(FORCE_CACHE FORCE) -get_property(_CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE) -if(_CMAKE_IN_TRY_COMPILE) - unset(FORCE_CACHE) -endif() - -# Default to building for iPhoneOS if not specified otherwise, and we cannot -# determine the platform from the CMAKE_OSX_ARCHITECTURES variable. The use -# of CMAKE_OSX_ARCHITECTURES is such that try_compile() projects can correctly -# determine the value of PLATFORM from the root project, as -# CMAKE_OSX_ARCHITECTURES is propagated to them by CMake. -if(NOT DEFINED PLATFORM) - if (CMAKE_OSX_ARCHITECTURES) - if(CMAKE_OSX_ARCHITECTURES MATCHES ".*arm.*" AND CMAKE_OSX_SYSROOT MATCHES ".*iphoneos.*") - set(PLATFORM "OS") - elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*") - set(PLATFORM "SIMULATOR") - elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*") - set(PLATFORM "SIMULATOR64") - elseif(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvos.*") - set(PLATFORM "TVOS") - elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvsimulator.*") - set(PLATFORM "SIMULATOR_TVOS") - elseif(CMAKE_OSX_ARCHITECTURES MATCHES ".*armv7k.*" AND CMAKE_OSX_SYSROOT MATCHES ".*watchos.*") - set(PLATFORM "WATCHOS") - elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*watchsimulator.*") - set(PLATFORM "SIMULATOR_WATCHOS") - endif() - endif() - if (NOT PLATFORM) - set(PLATFORM "OS") - endif() -endif() - -set(PLATFORM_INT "${PLATFORM}" CACHE STRING "Type of platform for which the build targets.") - -# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially) -if(PLATFORM_INT STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) - set(PLATFORM_INT "OS64") - message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") -elseif(PLATFORM_INT STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) - set(PLATFORM_INT "SIMULATOR64") - message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") -endif() - -# Determine the platform name and architectures for use in xcodebuild commands -# from the specified PLATFORM name. -if(PLATFORM_INT STREQUAL "OS") - set(SDK_NAME iphoneos) - if(NOT ARCHS) - set(ARCHS armv7 armv7s arm64) - endif() -elseif(PLATFORM_INT STREQUAL "OS64") - set(SDK_NAME iphoneos) - if(NOT ARCHS) - if (XCODE_VERSION VERSION_GREATER 10.0) - set(ARCHS arm64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example - else() - set(ARCHS arm64) - endif() - endif() -elseif(PLATFORM_INT STREQUAL "OS64COMBINED") - set(SDK_NAME iphoneos) - if(MODERN_CMAKE) - if(NOT ARCHS) - if (XCODE_VERSION VERSION_GREATER 10.0) - set(ARCHS arm64 x86_64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example - else() - set(ARCHS arm64 x86_64) - endif() - endif() - else() - message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work") - endif() -elseif(PLATFORM_INT STREQUAL "SIMULATOR") - set(SDK_NAME iphonesimulator) - if(NOT ARCHS) - set(ARCHS i386) - endif() - message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.") -elseif(PLATFORM_INT STREQUAL "SIMULATOR64") - set(SDK_NAME iphonesimulator) - if(NOT ARCHS) - set(ARCHS x86_64) - endif() -elseif(PLATFORM_INT STREQUAL "TVOS") - set(SDK_NAME appletvos) - if(NOT ARCHS) - set(ARCHS arm64) - endif() -elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED") - set(SDK_NAME appletvos) - if(MODERN_CMAKE) - if(NOT ARCHS) - set(ARCHS arm64 x86_64) - endif() - else() - message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work") - endif() -elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") - set(SDK_NAME appletvsimulator) - if(NOT ARCHS) - set(ARCHS x86_64) - endif() -elseif(PLATFORM_INT STREQUAL "WATCHOS") - set(SDK_NAME watchos) - if(NOT ARCHS) - if (XCODE_VERSION VERSION_GREATER 10.0) - set(ARCHS armv7k arm64_32) - else() - set(ARCHS armv7k) - endif() - endif() -elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED") - set(SDK_NAME watchos) - if(MODERN_CMAKE) - if(NOT ARCHS) - if (XCODE_VERSION VERSION_GREATER 10.0) - set(ARCHS armv7k arm64_32 i386) - else() - set(ARCHS armv7k i386) - endif() - endif() - else() - message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work") - endif() -elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") - set(SDK_NAME watchsimulator) - if(NOT ARCHS) - set(ARCHS i386) - endif() -elseif(PLATFORM_INT STREQUAL "MAC_CATALYST") - set(SDK_NAME macosx) - if(NOT ARCHS) - set(ARCHS x86_64) - endif() -else() - message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}") -endif() - -if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode") - message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode") -endif() - -# If user did not specify the SDK root to use, then query xcodebuild for it. -execute_process(COMMAND xcodebuild -version -sdk ${SDK_NAME} Path - OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) -if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT) - message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain" - "is pointing to the correct path. Please run:" - "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" - "and see if that fixes the problem for you.") - message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} " - "does not exist.") -elseif(DEFINED CMAKE_OSX_SYSROOT_INT) - set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") -endif() - -# Set Xcode property for SDKROOT as well if Xcode generator is used -if(USED_CMAKE_GENERATOR MATCHES "Xcode") - set(CMAKE_OSX_SYSROOT "${SDK_NAME}" CACHE INTERNAL "") - if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM) - set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "123456789A" CACHE INTERNAL "") - endif() -endif() - -# Specify minimum version of deployment target. -if(NOT DEFINED DEPLOYMENT_TARGET) - if (PLATFORM_INT STREQUAL "WATCHOS" OR PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") - # Unless specified, SDK version 2.0 is used by default as minimum target version (watchOS). - set(DEPLOYMENT_TARGET "2.0" - CACHE STRING "Minimum SDK version to build for." ) - else() - # Unless specified, SDK version 9.0 is used by default as minimum target version (iOS, tvOS). - set(DEPLOYMENT_TARGET "9.0" - CACHE STRING "Minimum SDK version to build for." ) - endif() - message(STATUS "Using the default min-version since DEPLOYMENT_TARGET not provided!") -endif() - -# Use bitcode or not -if(NOT DEFINED ENABLE_BITCODE AND NOT ARCHS MATCHES "((^|;|, )(i386|x86_64))+") - # Unless specified, enable bitcode support by default - message(STATUS "Enabling bitcode support by default. ENABLE_BITCODE not provided!") - set(ENABLE_BITCODE TRUE) -elseif(NOT DEFINED ENABLE_BITCODE) - message(STATUS "Disabling bitcode support by default on simulators. ENABLE_BITCODE not provided for override!") - set(ENABLE_BITCODE FALSE) -endif() -set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL "Whether or not to enable bitcode" ${FORCE_CACHE}) -# Use ARC or not -if(NOT DEFINED ENABLE_ARC) - # Unless specified, enable ARC support by default - set(ENABLE_ARC TRUE) - message(STATUS "Enabling ARC support by default. ENABLE_ARC not provided!") -endif() -set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" ${FORCE_CACHE}) -# Use hidden visibility or not -if(NOT DEFINED ENABLE_VISIBILITY) - # Unless specified, disable symbols visibility by default - set(ENABLE_VISIBILITY FALSE) - message(STATUS "Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!") -endif() -set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols (-fvisibility=hidden)" ${FORCE_CACHE}) -# Set strict compiler checks or not -if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE) - # Unless specified, disable strict try_compile() - set(ENABLE_STRICT_TRY_COMPILE FALSE) - message(STATUS "Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!") -endif() -set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL "Whether or not to use strict compiler checks" ${FORCE_CACHE}) -# Get the SDK version information. -execute_process(COMMAND xcodebuild -sdk ${CMAKE_OSX_SYSROOT} -version SDKVersion - OUTPUT_VARIABLE SDK_VERSION - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - -# Find the Developer root for the specific iOS platform being compiled for -# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in -# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain -# this information from xcrun or xcodebuild. -if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode") - get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT} PATH) - get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH) - - if (NOT DEFINED CMAKE_DEVELOPER_ROOT) - message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: " - "${CMAKE_DEVELOPER_ROOT} does not exist.") - endif() -endif() -# Find the C & C++ compilers for the specified SDK. -if(NOT CMAKE_C_COMPILER) - execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang - OUTPUT_VARIABLE CMAKE_C_COMPILER - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") -endif() -if(NOT CMAKE_CXX_COMPILER) - execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang++ - OUTPUT_VARIABLE CMAKE_CXX_COMPILER - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}") -endif() -# Find (Apple's) libtool. -execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find libtool - OUTPUT_VARIABLE BUILD_LIBTOOL - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) -message(STATUS "Using libtool: ${BUILD_LIBTOOL}") -# Configure libtool to be used instead of ar + ranlib to build static libraries. -# This is required on Xcode 7+, but should also work on previous versions of -# Xcode. -set(CMAKE_C_CREATE_STATIC_LIBRARY - "${BUILD_LIBTOOL} -static -o ") -set(CMAKE_CXX_CREATE_STATIC_LIBRARY - "${BUILD_LIBTOOL} -static -o ") -# Find the toolchain's provided install_name_tool if none is found on the host -if(NOT CMAKE_INSTALL_NAME_TOOL) - execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find install_name_tool - OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE STRING "" ${FORCE_CACHE}) -endif() -# Get the version of Darwin (OS X) of the host. -execute_process(COMMAND uname -r - OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) -if(SDK_NAME MATCHES "iphone") - set(CMAKE_SYSTEM_NAME iOS CACHE INTERNAL "" ${FORCE_CACHE}) -endif() -# CMake 3.14+ support building for iOS, watchOS and tvOS out of the box. -if(MODERN_CMAKE) - if(SDK_NAME MATCHES "appletv") - set(CMAKE_SYSTEM_NAME tvOS CACHE INTERNAL "" ${FORCE_CACHE}) - elseif(SDK_NAME MATCHES "watch") - set(CMAKE_SYSTEM_NAME watchOS CACHE INTERNAL "" ${FORCE_CACHE}) - endif() - # Provide flags for a combined FAT library build on newer CMake versions - if(PLATFORM_INT MATCHES ".*COMBINED") - set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "NO" CACHE INTERNAL "" ${FORCE_CACHE}) - set(CMAKE_IOS_INSTALL_COMBINED YES CACHE INTERNAL "" ${FORCE_CACHE}) - message(STATUS "Will combine built (static) artifacts into FAT lib...") - endif() -elseif(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.10") - # Legacy code path prior to CMake 3.14 or fallback if no SDK_NAME specified - set(CMAKE_SYSTEM_NAME iOS CACHE INTERNAL "" ${FORCE_CACHE}) -else() - # Legacy code path prior to CMake 3.14 or fallback if no SDK_NAME specified - set(CMAKE_SYSTEM_NAME Darwin CACHE INTERNAL "" ${FORCE_CACHE}) -endif() -# Standard settings. -set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "") -set(UNIX TRUE CACHE BOOL "") -set(APPLE TRUE CACHE BOOL "") -set(IOS TRUE CACHE BOOL "") -set(CMAKE_AR ar CACHE FILEPATH "" FORCE) -set(CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE) -set(CMAKE_STRIP strip CACHE FILEPATH "" FORCE) -# Set the architectures for which to build. -set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE STRING "Build architecture for iOS") -# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks -if(ENABLE_STRICT_TRY_COMPILE_INT) - message(STATUS "Using strict compiler checks (default in CMake).") -else() - set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -endif() -# All iOS/Darwin specific settings - some may be redundant. -set(CMAKE_MACOSX_BUNDLE YES) -set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") -set(CMAKE_SHARED_LIBRARY_PREFIX "lib") -set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") -set(CMAKE_SHARED_MODULE_PREFIX "lib") -set(CMAKE_SHARED_MODULE_SUFFIX ".so") -set(CMAKE_C_COMPILER_ABI ELF) -set(CMAKE_CXX_COMPILER_ABI ELF) -set(CMAKE_C_HAS_ISYSROOT 1) -set(CMAKE_CXX_HAS_ISYSROOT 1) -set(CMAKE_MODULE_EXISTS 1) -set(CMAKE_DL_LIBS "") -set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") -set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") -set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") -set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") - -if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+") - set(CMAKE_C_SIZEOF_DATA_PTR 8) - set(CMAKE_CXX_SIZEOF_DATA_PTR 8) - if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+") - set(CMAKE_SYSTEM_PROCESSOR "aarch64") - else() - set(CMAKE_SYSTEM_PROCESSOR "x86_64") - endif() -else() - set(CMAKE_C_SIZEOF_DATA_PTR 4) - set(CMAKE_CXX_SIZEOF_DATA_PTR 4) - set(CMAKE_SYSTEM_PROCESSOR "arm") -endif() - -# Note that only Xcode 7+ supports the newer more specific: -# -m${SDK_NAME}-version-min flags, older versions of Xcode use: -# -m(ios/ios-simulator)-version-min instead. -if(${CMAKE_VERSION} VERSION_LESS "3.11") - if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64") - if(XCODE_VERSION VERSION_LESS 7.0) - set(SDK_NAME_VERSION_FLAGS - "-mios-version-min=${DEPLOYMENT_TARGET}") - else() - # Xcode 7.0+ uses flags we can build directly from SDK_NAME. - set(SDK_NAME_VERSION_FLAGS - "-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}") - endif() - elseif(PLATFORM_INT STREQUAL "TVOS") - set(SDK_NAME_VERSION_FLAGS - "-mtvos-version-min=${DEPLOYMENT_TARGET}") - elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") - set(SDK_NAME_VERSION_FLAGS - "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") - elseif(PLATFORM_INT STREQUAL "WATCHOS") - set(SDK_NAME_VERSION_FLAGS - "-mwatchos-version-min=${DEPLOYMENT_TARGET}") - elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") - set(SDK_NAME_VERSION_FLAGS - "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") - else() - # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min. - set(SDK_NAME_VERSION_FLAGS - "-mios-simulator-version-min=${DEPLOYMENT_TARGET}") - endif() -else() - # Newer versions of CMake sets the version min flags correctly - set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE STRING - "Set CMake deployment target" ${FORCE_CACHE}) -endif() - -if(ENABLE_BITCODE_INT) - set(BITCODE "-fembed-bitcode") - set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode" CACHE INTERNAL "") - set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES" CACHE INTERNAL "") -else() - set(BITCODE "") - set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" CACHE INTERNAL "") -endif() - -if(ENABLE_ARC_INT) - set(FOBJC_ARC "-fobjc-arc") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES" CACHE INTERNAL "") -else() - set(FOBJC_ARC "-fno-objc-arc") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "NO" CACHE INTERNAL "") -endif() - -if(NOT ENABLE_VISIBILITY_INT) - set(VISIBILITY "-fvisibility=hidden") - set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "YES" CACHE INTERNAL "") -else() - set(VISIBILITY "") - set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "NO" CACHE INTERNAL "") -endif() - -if(NOT IOS_TOOLCHAIN_HAS_RUN) - #Check if Xcode generator is used, since that will handle these flags automagically - if(USED_CMAKE_GENERATOR MATCHES "Xcode") - message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as generator.") - else() - set(CMAKE_C_FLAGS - "${SDK_NAME_VERSION_FLAGS} ${BITCODE} -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_C_FLAGS}") - # Hidden visibilty is required for C++ on iOS. - set(CMAKE_CXX_FLAGS - "${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} -fvisibility-inlines-hidden -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_CXX_FLAGS}") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g ${CMAKE_CXX_FLAGS_DEBUG}") - set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DNDEBUG -Os -ffast-math ${CMAKE_CXX_FLAGS_MINSIZEREL}") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -DNDEBUG -O2 -g -ffast-math ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -ffast-math ${CMAKE_CXX_FLAGS_RELEASE}") - set(CMAKE_C_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") - set(CMAKE_CXX_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") - set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") - - # In order to ensure that the updated compiler flags are used in try_compile() - # tests, we have to forcibly set them in the CMake cache, not merely set them - # in the local scope. - set(VARS_TO_FORCE_IN_CACHE - CMAKE_C_FLAGS - CMAKE_CXX_FLAGS - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELWITHDEBINFO - CMAKE_CXX_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS_RELEASE - CMAKE_C_LINK_FLAGS - CMAKE_CXX_LINK_FLAGS) - foreach(VAR_TO_FORCE ${VARS_TO_FORCE_IN_CACHE}) - set(${VAR_TO_FORCE} "${${VAR_TO_FORCE}}" CACHE STRING "" ${FORCE_CACHE}) - endforeach() - endif() - - ## Print status messages to inform of the current state - message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}") - message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT}") - message(STATUS "Using minimum deployment version: ${DEPLOYMENT_TARGET}" - " (SDK version: ${SDK_VERSION})") - if(MODERN_CMAKE) - message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!") - endif() - if(USED_CMAKE_GENERATOR MATCHES "Xcode") - message(STATUS "Using Xcode version: ${XCODE_VERSION}") - endif() - if(DEFINED SDK_NAME_VERSION_FLAGS) - message(STATUS "Using version flags: ${SDK_NAME_VERSION_FLAGS}") - endif() - message(STATUS "Using a data_ptr size of: ${CMAKE_CXX_SIZEOF_DATA_PTR}") - message(STATUS "Using install_name_tool: ${CMAKE_INSTALL_NAME_TOOL}") - if(ENABLE_BITCODE_INT) - message(STATUS "Enabling bitcode support.") - else() - message(STATUS "Disabling bitcode support.") - endif() - - if(ENABLE_ARC_INT) - message(STATUS "Enabling ARC support.") - else() - message(STATUS "Disabling ARC support.") - endif() - - if(NOT ENABLE_VISIBILITY_INT) - message(STATUS "Hiding symbols (-fvisibility=hidden).") - endif() -endif() - -set(CMAKE_PLATFORM_HAS_INSTALLNAME 1) -set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") -set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names") -set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names") -set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") -set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") -set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a") -set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name") - -# Set the find root to the iOS developer roots and to user defined paths. -set(CMAKE_FIND_ROOT_PATH ${CMAKE_OSX_SYSROOT_INT} ${CMAKE_PREFIX_PATH} CACHE STRING "Root path that will be prepended - to all search paths") -# Default to searching for frameworks first. -set(CMAKE_FIND_FRAMEWORK FIRST) -# Set up the default search directories for frameworks. -set(CMAKE_FRAMEWORK_PATH - ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks - ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks - ${CMAKE_FRAMEWORK_PATH} CACHE STRING "Frameworks search paths" ${FORCE_CACHE}) - -set(IOS_TOOLCHAIN_HAS_RUN TRUE CACHE BOOL "Has the CMake toolchain run already?" ${FORCE_CACHE}) - -# By default, search both the specified iOS SDK and the remainder of the host filesystem. -if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE STRING "" ${FORCE_CACHE}) -endif() -if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY CACHE STRING "" ${FORCE_CACHE}) -endif() -if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE STRING "" ${FORCE_CACHE}) -endif() -if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY CACHE STRING "" ${FORCE_CACHE}) -endif() - -# -# Some helper-macros below to simplify and beautify the CMakeFile -# - -# This little macro lets you set any Xcode specific property. -macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION) - set(XCODE_RELVERSION_I "${XCODE_RELVERSION}") - if(XCODE_RELVERSION_I STREQUAL "All") - set_property(TARGET ${TARGET} PROPERTY - XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}") - else() - set_property(TARGET ${TARGET} PROPERTY - XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}") - endif() -endmacro(set_xcode_property) -# This macro lets you find executable programs on the host system. -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) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) - set(IOS FALSE) - find_package(${ARGN}) - set(IOS TRUE) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) -endmacro(find_host_package) diff --git a/codegen-v2/Cargo.lock b/codegen-v2/Cargo.lock new file mode 100644 index 00000000000..098922255d8 --- /dev/null +++ b/codegen-v2/Cargo.lock @@ -0,0 +1,415 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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", + "regex", + "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.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[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.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +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..b1692860f20 --- /dev/null +++ b/codegen-v2/Cargo.toml @@ -0,0 +1,24 @@ +[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" +regex = "1.11.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/TWBech32.yaml b/codegen-v2/manifest/TWBech32.yaml new file mode 100644 index 00000000000..1d192712adb --- /dev/null +++ b/codegen-v2/manifest/TWBech32.yaml @@ -0,0 +1,78 @@ +name: TWBech32 +structs: +- name: TWBech32 + is_public: true + is_class: false +functions: +- name: TWBech32Encode + is_public: true + is_static: true + params: + - name: hrp + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - 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: TWBech32Decode + 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: TWBech32EncodeM + is_public: true + is_static: true + params: + - name: hrp + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - 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: TWBech32DecodeM + 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/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..b8d1cb6f0b9 --- /dev/null +++ b/codegen-v2/manifest/TWDerivation.yaml @@ -0,0 +1,29 @@ +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 + - name: stratisSegwit + value: 7 + - name: bitcoinTaproot + value: 8 + - name: pactusMainnet + value: 9 + - name: pactusTestnet + value: 10 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..8db92277e5b --- /dev/null +++ b/codegen-v2/manifest/TWEthereumAbi.yaml @@ -0,0 +1,98 @@ +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 +- name: TWEthereumAbiGetFunctionSignature + is_public: true + is_static: true + params: + - 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 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/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs new file mode 100644 index 00000000000..96af8f7c525 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -0,0 +1,572 @@ +use heck::ToLowerCamelCase; +use std::fmt::Write as _; +use std::fs; +use std::io::Write; + +use super::code_gen_types::*; +use crate::Error::BadFormat; +use crate::Result; + +static IN_DIR: &str = "../rust/bindings/"; +static HEADER_OUT_DIR: &str = "../include/TrustWalletCore/"; +static SOURCE_OUT_DIR: &str = "../src/Generated/"; + +fn generate_license(file: &mut std::fs::File) -> Result<()> { + writeln!(file, "// SPDX-License-Identifier: Apache-2.0")?; + writeln!(file, "//")?; + writeln!(file, "// Copyright © 2017 Trust Wallet.\n")?; + Ok(()) +} + +fn generate_header_guard(file: &mut std::fs::File) -> Result<()> { + writeln!(file, "#pragma once\n")?; + Ok(()) +} + +fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { + writeln!(file, "#include \"TWBase.h\"")?; + + // Include headers based on argument types + let mut included_headers = std::collections::HashSet::new(); + for (_, func) in info.functions(true) { + for ty in func.types() { + let tw_type = TWType::from(ty); + match tw_type { + TWType::Pointer(_, header_name) => { + if header_name == info.class { + continue; + } + if included_headers.insert(header_name.clone()) { + writeln!(file, "#include \"{}.h\"", header_name)?; + } + } + TWType::Standard(ty) => { + if ty.contains("TWFFICoinType") + && included_headers.insert("TWCoinType.h".to_string()) + { + // Need to handle this case separately because it's not a pointer type + writeln!(file, "#include \"TWCoinType.h\"")?; + } + } + } + } + } + + Ok(()) +} + +fn generate_class_declaration(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { + writeln!(file, "TW_EXPORT_CLASS\nstruct {};\n", info.class)?; + Ok(()) +} + +fn generate_function_signature( + class_name: &str, + func_type: TWFunctionType, + func: &TWFunction, + is_declaration: bool, +) -> Result { + let return_type = TWType::from(func.return_type.clone()).cpp_type(); + let whether_export = if is_declaration { + match func_type { + TWFunctionType::StaticFunction | TWFunctionType::Constructor => { + "TW_EXPORT_STATIC_METHOD " + } + TWFunctionType::Method | TWFunctionType::Destructor => "TW_EXPORT_METHOD ", + TWFunctionType::Property => "TW_EXPORT_PROPERTY ", + } + } else { + "" + }; + let mut signature = format!("{whether_export}{return_type} {class_name}{}", func.name); + signature += "("; + for (i, arg) in func.args.iter().enumerate() { + write!( + &mut signature, + "{} {}", + TWType::from(arg.ty.clone()).cpp_type(), + arg.name.to_lower_camel_case() + ) + .map_err(|e| BadFormat(e.to_string()))?; + if i < func.args.len() - 1 { + signature += ", "; + } + } + signature += ")"; + Ok(signature) +} + +fn generate_function_declaration( + file: &mut std::fs::File, + class_name: &str, + func_type: TWFunctionType, + func: &TWFunction, +) -> Result<()> { + let func_dec = generate_function_signature(class_name, func_type, func, true)?; + for doc in &func.docs { + writeln!(file, "/// {}", doc)?; + } + writeln!(file, "{func_dec};\n")?; + Ok(()) +} + +pub fn generate_header(info: &TWConfig) -> Result<()> { + let file_path = format!("{HEADER_OUT_DIR}/{}.h", info.class); + let mut file = std::fs::File::create(&file_path)?; + + generate_license(&mut file)?; + generate_header_guard(&mut file)?; + generate_header_includes(&mut file, info)?; + + writeln!(file, "\nTW_EXTERN_C_BEGIN\n")?; + + generate_class_declaration(&mut file, info)?; + for (func_type, func) in info.functions(true) { + generate_function_declaration(&mut file, &info.class, func_type, func)?; + } + + writeln!(file, "TW_EXTERN_C_END")?; + + file.flush()?; + + Ok(()) +} + +fn generate_wrapper_header(info: &TWConfig) -> Result<()> { + let class_name = &info.class; + let wrapper_class_name = class_name.replace("TW", ""); + let file_path = format!("{SOURCE_OUT_DIR}/{}.h", wrapper_class_name); + let mut file = std::fs::File::create(&file_path)?; + + generate_license(&mut file)?; + generate_header_guard(&mut file)?; + + writeln!(file, "#include \"rust/Wrapper.h\"\n")?; + + writeln!( + file, + "using {wrapper_class_name}Ptr = std::shared_ptr;\n", + )?; + + writeln!(file, "struct {} {{", wrapper_class_name)?; + + let Some(destructor) = &info.destructor else { + panic!("No destructor found for {}", wrapper_class_name); + }; + let destructor_name = &destructor.rust_name; + writeln!( + file, + "\texplicit {wrapper_class_name}(TW::Rust::{class_name}* raw_ptr): ptr(raw_ptr, TW::Rust::{destructor_name}) {{}}\n", + )?; + + writeln!(file, "\t{wrapper_class_name}Ptr ptr;")?; + writeln!(file, "}};\n")?; + + writeln!(file, "struct {} {{", class_name)?; + writeln!(file, "\t{wrapper_class_name} impl;")?; + writeln!(file, "}};\n")?; + + Ok(()) +} + +fn generate_source_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { + writeln!(file, "#include ", info.class)?; + writeln!(file, "#include \"rust/Wrapper.h\"")?; + + // Include headers based on argument types + let mut included_headers = std::collections::HashSet::new(); + for (_, func) in info.functions(true) { + for ty in func.types() { + let tw_type = TWType::from(ty); + let TWType::Pointer(_, header_name) = tw_type else { + continue; + }; + if header_name.contains("TWPrivateKey") + && included_headers.insert("TWPrivateKey.h".to_string()) + { + writeln!(file, "#include \"../PrivateKey.h\"")?; + } else if header_name.contains("TWPublicKey") + && included_headers.insert("TWPublicKey.h".to_string()) + { + writeln!(file, "#include \"../PublicKey.h\"")?; + } else if header_name.contains("TWDataVector") + && included_headers.insert("TWDataVector.h".to_string()) + { + writeln!(file, "#include \"../DataVector.h\"")?; + } else if !header_name.contains("TWString") && !header_name.contains("TWData") { + // Do not need wrapper headers for these types + let wrapper_header_name = header_name.replace("TW", ""); + if included_headers.insert(wrapper_header_name.clone()) { + writeln!(file, "#include \"{}.h\"", wrapper_header_name)?; + } + } + } + } + + Ok(()) +} + +fn generate_function_call(args: &Vec) -> Result { + let mut func_call = "(".to_string(); + for (i, arg) in args.iter().enumerate() { + write!(&mut func_call, "{arg}").map_err(|e| BadFormat(e.to_string()))?; + if i < args.len() - 1 { + func_call += ", "; + } + } + func_call += ");"; + Ok(func_call) +} + +fn generate_return_type(func: &TWFunction, converted_args: &Vec) -> Result { + let mut return_string = String::new(); + let tw_type = TWType::from(func.return_type.replace(" ", "").to_string()); + match tw_type { + TWType::Pointer(pointer_type, ty) => match (pointer_type, ty.as_str()) { + (TWPointerType::Nullable, "TWString") | (TWPointerType::NullableMut, "TWString") => { + write!( + &mut return_string, + "\tconst Rust::TWStringWrapper result = Rust::{}{}\n\ + \tif (!result) {{ return nullptr; }}\n\ + \treturn TWStringCreateWithUTF8Bytes(result.c_str());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWString") | (TWPointerType::Nonnull, "TWString") => { + write!( + &mut return_string, + "\tconst Rust::TWStringWrapper result = Rust::{}{}\n\ + \tconst auto resultString = result.toStringOrDefault();\n\ + \treturn TWStringCreateWithUTF8Bytes(resultString.c_str());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NullableMut, "TWData") | (TWPointerType::Nullable, "TWData") => { + write!( + &mut return_string, + "\tconst Rust::TWDataWrapper result = Rust::{}{}\n\ + \tif (!result.ptr) {{ return nullptr; }}\n\ + \tconst auto resultData = result.toDataOrDefault();\n\ + \treturn TWDataCreateWithBytes(resultData.data(), resultData.size());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWData") | (TWPointerType::Nonnull, "TWData") => { + write!( + &mut return_string, + "\tconst Rust::TWDataWrapper result = Rust::{}{}\n\ + \tconst auto resultData = result.toDataOrDefault();\n\ + \treturn TWDataCreateWithBytes(resultData.data(), resultData.size());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NullableMut, "TWPrivateKey") + | (TWPointerType::Nullable, "TWPrivateKey") => { + write!( + &mut return_string, + "\tconst auto result = Rust::{}{}\n\ + \tconst auto resultRustPrivateKey = Rust::wrapTWPrivateKey(result);\n\ + \tif (!resultRustPrivateKey.get()) {{ return nullptr; }}\n\ + \tconst auto resultData = Rust::tw_private_key_bytes(resultRustPrivateKey.get());\n\ + \tconst auto resultSize = Rust::tw_private_key_size(resultRustPrivateKey.get());\n\ + \tconst Data out(resultData, resultData + resultSize);\n\ + \treturn new TWPrivateKey {{ PrivateKey(out) }};\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWPrivateKey") + | (TWPointerType::Nonnull, "TWPrivateKey") => { + panic!("Nonnull TWPrivateKey is not supported"); + } + (TWPointerType::NullableMut, "TWPublicKey") + | (TWPointerType::Nullable, "TWPublicKey") => { + write!( + &mut return_string, + "\tconst auto result = Rust::{}{}\n\ + \tconst auto resultRustPublicKey = Rust::wrapTWPublicKey(result);\n\ + \tif (!resultRustPublicKey.get()) {{ return nullptr; }}\n\ + \tconst auto resultData = Rust::tw_public_key_data(resultRustPublicKey.get());\n\ + \tconst Data out(resultData.data, resultData.data + resultData.size);\n\ + \treturn new TWPublicKey {{ PublicKey(out, a->impl.type) }};\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWPublicKey") + | (TWPointerType::Nonnull, "TWPublicKey") => { + panic!("Nonnull TWPublicKey is not supported"); + } + (pointer_type, class_name) => { + let wrapper_class_name = class_name.replace("TW", ""); + let null_return = match pointer_type { + TWPointerType::Nullable | TWPointerType::NullableMut => { + "if (!resultRaw) {{ return nullptr; }}\n" + } + _ => "", + }; + write!( + &mut return_string, + "\tauto* resultRaw = Rust::{}{}\n\ + {null_return}\ + \tconst {wrapper_class_name} resultWrapped(resultRaw);\n\ + \treturn new {class_name} {{ resultWrapped }};\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + }, + TWType::Standard(ty) => match ty.as_str() { + "void" => { + write!( + &mut return_string, + "\tRust::{}{}\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + _ => { + write!( + &mut return_string, + "\treturn Rust::{}{}\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + }, + } + Ok(return_string) +} + +fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result<(String, String)> { + match tw_type { + TWType::Pointer(ref pointer_type, ref ty) => match (pointer_type, ty.as_str()) { + (TWPointerType::Nonnull, "TWString") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto& {name}String = *reinterpret_cast({name});\n\ + \tconst Rust::TWStringWrapper {name}RustStr = {name}String;" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustStr.get()", name))) + } + (TWPointerType::Nullable, "TWString") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tRust::TWStringWrapper {name}RustStr;\n\ + \tif ({name} != nullptr) {{\n\ + \t\t{name}RustStr = *reinterpret_cast({name});\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustStr.get()", name))) + } + (TWPointerType::Nonnull, "TWData") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto& {name}Data = *reinterpret_cast({name});\n\ + \tconst Rust::TWDataWrapper {name}RustData = {name}Data;" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustData.get()", name))) + } + (TWPointerType::Nullable, "TWData") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tRust::TWDataWrapper {name}RustData;\n\ + \tif ({name} != nullptr) {{\n\ + \t\t{name}RustData = *reinterpret_cast({name});\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustData.get()", name))) + } + (TWPointerType::Nonnull, "TWPrivateKey") | (TWPointerType::NonnullMut, "TWPrivateKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto &{name}PrivateKey = *reinterpret_cast({name});\n\ + \tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey.bytes.data(), {name}PrivateKey.bytes.size());\n\ + \tconst auto {name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) + } + (TWPointerType::Nullable, "TWPrivateKey") | (TWPointerType::NullableMut, "TWPrivateKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tstd::shared_ptr {name}RustPrivateKey;\n\ + \tif ({name} != nullptr) {{\n\ + \t\tconst auto& {name}PrivateKey = {name};\n\ + \t\tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey->impl.bytes.data(), {name}PrivateKey->impl.bytes.size());\n\ + \t\t{name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) + } + (TWPointerType::Nonnull, "TWPublicKey") | (TWPointerType::NonnullMut, "TWPublicKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto &{name}PublicKey = *reinterpret_cast({name});\n\ + \tconst auto {name}PublicKeyType = static_cast({name}PublicKey.type);\n\ + \tauto* {name}RustRaw = Rust::tw_public_key_create_with_data({name}PublicKey.bytes.data(), {name}PublicKey.bytes.size(), {name}PublicKeyType);\n\ + \tconst auto {name}RustPublicKey = Rust::wrapTWPublicKey({name}RustRaw);" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPublicKey.get()", name))) + } + (TWPointerType::Nullable, "TWPublicKey") | (TWPointerType::NullableMut, "TWPublicKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tstd::shared_ptr {name}RustPublicKey;\n\ + \tif ({name} != nullptr) {{\n\ + \t\tconst auto& {name}PublicKey = {name};\n\ + \t\tconst auto {name}PublicKeyType = static_cast({name}PublicKey->impl.type);\n\ + \t\tauto* {name}RustRaw = Rust::tw_public_key_create_with_data({name}PublicKey->impl.bytes.data(), {name}PublicKey->impl.bytes.size(), {name}PublicKeyType);\n\ + \t\t{name}RustPublicKey = Rust::wrapTWPublicKey({name}RustRaw);\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPublicKey.get()", name))) + } + (TWPointerType::Nonnull, "TWDataVector") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tconst Rust::TWDataVectorWrapper {name}RustDataVector = createFromTWDataVector({name});" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustDataVector.get()", name))) + } + (TWPointerType::Nullable, "TWDataVector") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tstd::shared_ptr {name}RustDataVector;\n\ + \tif ({name} != nullptr) {{\n\ + \t\t{name}RustDataVector = createFromTWDataVector({name});\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustDataVector.get()", name))) + } + _ if tw_type.cpp_type().starts_with("struct ") => { + Ok(("".to_string(), format!("{name}->impl.ptr.get()"))) + } + _ => panic!("Unsupported type: {}", tw_type.cpp_type()), + }, + TWType::Standard(_) => Ok(("".to_string(), name.to_string())), + } +} + +fn generate_function_definition( + file: &mut std::fs::File, + info: &TWConfig, + func_type: TWFunctionType, + func: &TWFunction, +) -> Result<()> { + let mut func_def = generate_function_signature(&info.class, func_type, func, false)?; + func_def += " {\n"; + let mut converted_args = vec![]; + for arg in func.args.iter() { + let func_type = TWType::from(arg.ty.clone()); + let (conversion_code, converted_arg) = + generate_conversion_code_with_var_name(func_type, &arg.name.to_lower_camel_case())?; + func_def += conversion_code.as_str(); + converted_args.push(converted_arg); + } + let return_string = generate_return_type(func, &converted_args)?; + func_def += return_string.as_str(); + func_def += "}\n"; + writeln!(file, "{}", func_def)?; + Ok(()) +} + +fn generate_destructor_definition( + file: &mut std::fs::File, + info: &TWConfig, + destructor: &TWFunction, +) -> Result<()> { + let function_signature = + generate_function_signature(&info.class, TWFunctionType::Destructor, destructor, false)?; + assert!( + destructor.args.len() == 1, + "Destructor must have exactly one argument" + ); + let arg_name = &destructor.args[0].name.to_lower_camel_case(); + writeln!( + file, + "{function_signature}{{\n\ + \tdelete {arg_name};\n\ + }}" + )?; + Ok(()) +} + +fn generate_source(info: &TWConfig) -> Result<()> { + let file_path = format!("{SOURCE_OUT_DIR}/{}.cpp", info.class); + let mut file = std::fs::File::create(&file_path)?; + + generate_license(&mut file)?; + generate_source_includes(&mut file, info)?; + + writeln!(file, "\nusing namespace TW;\n")?; + + for (func_type, func) in info.functions(false) { + generate_function_definition(&mut file, info, func_type, func)?; + } + if let Some(destructor) = &info.destructor { + generate_destructor_definition(&mut file, info, destructor)?; + } + + file.flush()?; + + Ok(()) +} + +pub fn generate_cpp_bindings() -> Result<()> { + std::fs::create_dir_all(HEADER_OUT_DIR)?; + std::fs::create_dir_all(SOURCE_OUT_DIR)?; + + let entries = fs::read_dir(IN_DIR)?; + for entry in entries { + let file_path = entry?.path(); + if file_path.is_dir() { + println!("Found unexpected directory: {}", file_path.display()); + continue; + } + + let file_contents = fs::read_to_string(&file_path)?; + let info: TWConfig = + serde_yaml::from_str(&file_contents).expect("Failed to parse YAML file"); + + if info.is_wrapped() { + generate_wrapper_header(&info)?; + } + + generate_header(&info)?; + generate_source(&info)?; + } + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/code_gen_types.rs b/codegen-v2/src/codegen/cpp/code_gen_types.rs new file mode 100644 index 00000000000..555fa8b8e88 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/code_gen_types.rs @@ -0,0 +1,152 @@ +use regex::Regex; +use serde::{Deserialize, Serialize}; + +pub enum TWFunctionType { + StaticFunction, + Constructor, + Destructor, + Method, + Property, +} + +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct TWConfig { + pub class: String, + pub static_functions: Vec, + pub constructors: Option>, + pub destructor: Option, + pub methods: Option>, + pub properties: Option>, +} + +impl TWConfig { + pub fn functions(&self, include_destructor: bool) -> Vec<(TWFunctionType, &TWFunction)> { + let mut functions = self + .static_functions + .iter() + .map(|f| (TWFunctionType::StaticFunction, f)) + .collect::>(); + if let Some(constructors) = &self.constructors { + functions.extend( + constructors + .iter() + .map(|f| (TWFunctionType::Constructor, f)), + ); + } + if let Some(methods) = &self.methods { + functions.extend(methods.iter().map(|f| (TWFunctionType::Method, f))); + } + if let Some(properties) = &self.properties { + functions.extend(properties.iter().map(|f| (TWFunctionType::Property, f))); + } + if include_destructor { + if let Some(destructor) = &self.destructor { + functions.push((TWFunctionType::Destructor, destructor)); + } + } + functions + } + + pub fn is_wrapped(&self) -> bool { + self.constructors.is_some() && self.destructor.is_some() + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct TWFunction { + pub name: String, + pub rust_name: String, + pub args: Vec, + pub return_type: String, + pub docs: Vec, +} + +impl TWFunction { + pub fn types(&self) -> Vec { + self.args + .iter() + .map(|arg| arg.ty.clone()) + .chain(std::iter::once(self.return_type.clone())) + .collect() + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TWArg { + pub name: String, + pub ty: String, +} + +#[derive(Debug)] +pub enum TWPointerType { + Nonnull, + NonnullMut, + Nullable, + NullableMut, +} + +#[derive(Debug)] +pub enum TWType { + Pointer(TWPointerType, String), + Standard(String), +} + +impl From for TWType { + fn from(ty: String) -> Self { + let trimmed = ty.replace(" ", ""); + if let Some(captures) = Regex::new(r"^Nonnull<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::Nonnull, captures[1].to_string()) + } else if let Some(captures) = Regex::new(r"^NonnullMut<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::NonnullMut, captures[1].to_string()) + } else if let Some(captures) = Regex::new(r"^Nullable<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::Nullable, captures[1].to_string()) + } else if let Some(captures) = Regex::new(r"^NullableMut<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::NullableMut, captures[1].to_string()) + } else { + TWType::Standard(trimmed) + } + } +} + +impl TWType { + pub fn cpp_type(&self) -> String { + match self { + TWType::Standard(ty) => match ty.as_str() { + "u8" => "uint8_t".to_string(), + "u16" => "uint16_t".to_string(), + "u32" => "uint32_t".to_string(), + "u64" => "uint64_t".to_string(), + "i8" => "int8_t".to_string(), + "i16" => "int16_t".to_string(), + "i32" => "int32_t".to_string(), + "i64" => "int64_t".to_string(), + "TWFFICoinType" => "enum TWCoinType".to_string(), + _ => ty.to_string(), + }, + TWType::Pointer(pointer_type, ty) => { + let ty = match ty.as_str() { + "TWString" | "TWData" => ty.to_string(), + _ => format!("struct {}", ty), + }; + match pointer_type { + TWPointerType::Nonnull => format!("{} *_Nonnull", ty), + TWPointerType::NonnullMut => format!("{} *_Nonnull", ty), + TWPointerType::Nullable => format!("{} *_Nullable", ty), + TWPointerType::NullableMut => format!("{} *_Nullable", ty), + } + } + } + } +} 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..d066709dbaf --- /dev/null +++ b/codegen-v2/src/codegen/cpp/mod.rs @@ -0,0 +1,53 @@ +// 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 code_gen; +pub mod code_gen_types; +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..c546f4cbcf2 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_crate.rs @@ -0,0 +1,96 @@ +// 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.hbs"); +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.hbs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml.hbs new file mode 100644 index 00000000000..9d5a7a7d04a --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml.hbs @@ -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..e26b696fa59 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs @@ -0,0 +1,91 @@ +// 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, 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..f3f6f2b5a99 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs @@ -0,0 +1,38 @@ +// 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, KeyType, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_{COIN_ID}_address_derive() { + test_address_derive( + CoinType::{COIN_TYPE}, + KeyType::PrivateKey("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..ec7888eca4f --- /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![]; + + // Initialize 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..44ba77ee0a9 --- /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![]; + + // Initialize 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..6fae696716d --- /dev/null +++ b/codegen-v2/src/codegen/template_generator.rs @@ -0,0 +1,87 @@ +// 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..f420701bd6c --- /dev/null +++ b/codegen-v2/src/main.rs @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use libparser::codegen::cpp::code_gen::generate_cpp_bindings; +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(), + "cpp" => generate_cpp_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/.rubocop.yml b/codegen/.rubocop.yml index c4410459c53..7e5aa453c4c 100644 --- a/codegen/.rubocop.yml +++ b/codegen/.rubocop.yml @@ -1,2 +1,13 @@ -Metrics/LineLength: +AllCops: + NewCops: enable + Exclude: + - 'vendor/**/*' + - 'spec/fixtures/**/*' + - 'tmp/**/*' + - '.git/**/*' + - 'bin/*' + TargetRubyVersion: 2.6 + SuggestExtensions: false + +Layout/LineLength: Enabled: false diff --git a/codegen/bin/codegen b/codegen/bin/codegen index d8b7a6f0873..f34931b21ab 100755 --- a/codegen/bin/codegen +++ b/codegen/bin/codegen @@ -20,6 +20,9 @@ options.swift = true options.java = true 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]' @@ -42,6 +45,15 @@ OptionParser.new do |opts| opts.on('-c', '--jnic', "Generate JNI code. Default: #{options.jni_c}") do |v| options.jni_c = v end + opts.on('-w', '--wasm-cpp', "Generate cpp code for Wasm(Emscripten). Default: #{options.wasm_cpp}") do |v| + options.wasm_cpp = v + end + 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 @@ -53,7 +65,8 @@ files = [] Dir.foreach(options.input) do |item| next if ['.', '..', '.DS_Store'].include?(item) - entity = Parser.new(path: File.expand_path(File.join(options.input, item))).parse + file_path = File.expand_path(File.join(options.input, item)) + entity = Parser.new(path: file_path).parse next if entity.nil? entities << entity @@ -73,3 +86,19 @@ end if options.jni_c generator.render_jni_c end +if options.wasm_cpp + generator.render_wasm_h + generator.render_wasm_cpp +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/coins b/codegen/bin/coins index 06fe37f07fb..c5a3690c1a4 100755 --- a/codegen/bin/coins +++ b/codegen/bin/coins @@ -4,14 +4,28 @@ require 'erb' require 'fileutils' require 'json' +CurrentDir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) +require 'derivation' + # Transforms a coin name to a C++ name def self.format_name(n) formatted = n formatted = formatted.sub(/^([a-z]+)/, &:upcase) + formatted = formatted.gsub(/-/, ' ') + formatted = formatted.gsub(/\./, ' ') formatted = formatted.gsub(/\s/, '') formatted end +def self.coin_name(coin) + coin['displayName'] || coin['name'] +end + +def self.camel_case(id) + id[0].upcase + id[1..].downcase +end + def self.coin_img(coin) "" end @@ -24,16 +38,23 @@ def self.explorer_account_url(c) path = c['explorer']['url'].to_s + c['explorer']['accountPath'].to_s end -json_string = File.read('coins.json') +json_string = File.read('registry.json') coins = JSON.parse(json_string).sort_by { |x| x['coinId'] } +# used in some cases for numbering enum values +enum_count = 0 + erbs = [ {'template' => 'CoinInfoData.cpp.erb', 'folder' => 'src/Generated', 'file' => 'CoinInfoData.cpp'}, - {'template' => 'coins.md.erb', 'folder' => 'docs', 'file' => 'coins.md'}, + {'template' => 'registry.md.erb', 'folder' => 'docs', 'file' => 'registry.md'}, {'template' => 'hrp.cpp.erb', 'folder' => 'src/Generated', 'file' => 'TWHRP.cpp'}, - {'template' => 'hrp.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWHRP.h'} + {'template' => 'hrp.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWHRP.h'}, + {'template' => 'TWEthereumChainID.h.erb', 'folder' => 'include/TrustWalletCore', 'file' => 'TWEthereumChainID.h'} ] +# Update coins derivations if changed. +update_derivation_enum(coins) + FileUtils.mkdir_p File.join('src', 'Generated') erbs.each do |erb| path = File.expand_path(erb['template'], File.join(File.dirname(__FILE__), '..', 'lib', 'templates')) diff --git a/codegen/bin/cointests b/codegen/bin/cointests index e6e0a765ede..a40f56b0250 100755 --- a/codegen/bin/cointests +++ b/codegen/bin/cointests @@ -1,26 +1,26 @@ #!/usr/bin/env ruby -# Sript for creating/updating CoinType unit tests, based on the coins.json file +# 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) @@ -58,7 +58,7 @@ def self.explorer_sample_account(c) end end -json_string = File.read('coins.json') +json_string = File.read('registry.json') coins = JSON.parse(json_string).sort_by { |x| x['name'] } erbs = [ @@ -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 732972c52e9..ee14b241f7a 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -1,91 +1,14 @@ #!/usr/bin/env ruby -# Sript for creating new skeleton files for a new coin -# 1. Add relevsant entry to coins.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' - -# Transforms a coin name to a C++ name -def self.format_name(coin) - formatted = coin['name'] - 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 - target_line = " TWBlockchain#{coin['blockchain']} = #{line_number - 17},\n" - insert_target_line(target_file, target_line, "};\n") -end - -def insert_coin_entry(coin) - target_file = "src/Coin.cpp" - target_line = "#include \"#{format_name(coin)}/Entry.h\"\n" - insert_target_line(target_file, target_line, "// end_of_coin_includes_marker_do_not_modify\n") - target_line = "#{format_name(coin)}::Entry #{format_name(coin)}DP;\n" - insert_target_line(target_file, target_line, "// end_of_coin_dipatcher_declarations_marker_do_not_modify\n") - target_line = " case TWCoinType#{format_name(coin)}: entry = &#{format_name(coin)}DP; break;\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 @@ -94,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('coins.json') -coins = JSON.parse(json_string).sort_by { |x| x['name'] } - -entity = EntityDecl.new(name: "New" + coin_id, is_struct: false) -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 56b49b892c6..d1fcf3881af 100644 --- a/codegen/lib/code_generator.rb +++ b/codegen/lib/code_generator.rb @@ -5,6 +5,10 @@ require 'java_helper' require 'jni_helper' require 'swift_helper' +require 'wasm_cpp_helper' +require 'ts_helper' +require 'kotlin_helper' +require 'kotlin_jni_helper' # Code generation class CodeGenerator @@ -20,7 +24,7 @@ def initialize(entities:, files:, output_folder:) # Renders an enum template def render_swift_enum_template(file:, header:, template:, output_subfolder:, extension:) # split Enum to Enum.swift and Enum+Extension.swift (easier to support cocoapods subspec) - output_enum_subfolder = "#{output_subfolder + '/Enums'}" + output_enum_subfolder = "#{output_subfolder}/Enums" FileUtils.mkdir_p File.join(output_folder, output_enum_subfolder) has_extension = entity.properties.length > 0 || entity.methods.length > 0 header = render(header) @@ -39,13 +43,13 @@ def render_swift_enum_template(file:, header:, template:, output_subfolder:, ext code = +'' code << header code << render('swift/enum_extension.erb') - path = File.expand_path(File.join(output_folder, output_subfolder, "#{file + '+Extension'}.#{extension}")) + path = File.expand_path(File.join(output_folder, output_subfolder, "#{file}+Extension.#{extension}")) File.write(path, code) end 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 @@ -60,8 +64,8 @@ def render_template(header:, template:, output_subfolder:, extension:) unless string.nil? || string.empty? 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 @@ -69,7 +73,7 @@ def render_template(header:, template:, output_subfolder:, extension:) end def render_swift - render_template(header: 'swift/header.erb', template: 'swift.erb', output_subfolder: 'swift/Sources/Generated', extension: 'swift') + render_template(header: 'copyright_header.erb', template: 'swift.erb', output_subfolder: 'swift/Sources/Generated', extension: 'swift') framework_header = render('swift/TrustWalletCore.h.erb') framework_header_path = File.expand_path(File.join(output_folder, 'swift/Sources/Generated', 'WalletCore.h')) @@ -81,17 +85,58 @@ def render_java end def render_jni_h - render_template(header: 'jni/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: 'jni/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 + render_template(header: 'copyright_header.erb', template: 'wasm_h.erb', output_subfolder: 'wasm/src/generated', extension: 'h') + end + + def render_wasm_cpp + render_template(header: 'copyright_header.erb', template: 'wasm_cpp.erb', output_subfolder: 'wasm/src/generated', extension: 'cpp') + end + + def render_ts_declaration + render_template(header: nil, template: 'wasm_d_ts.erb', output_subfolder: 'wasm/lib/generated', extension: 'd.ts') + 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')) - template = ERB.new(File.read(path), nil, '-') + template = ERB.new(File.read(path), trim_mode: '-') template.result(binding) end @@ -102,7 +147,7 @@ def should_return_data(method) end def should_return_string(method) - # Note: method with no parameters can also return string + # NOTE: method with no parameters can also return string method.return_type.name == :string end diff --git a/codegen/lib/coin_skeleton_gen.rb b/codegen/lib/coin_skeleton_gen.rb new file mode 100755 index 00000000000..feec8fef8e6 --- /dev/null +++ b/codegen/lib/coin_skeleton_gen.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require 'erb' +require 'fileutils' +require 'json' + +require 'entity_decl' +require 'code_generator' +require 'coin_test_gen' +require 'file_editor' + +# 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 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 f03a2869d77..0b831bfb277 100755 --- a/codegen/lib/coin_test_gen.rb +++ b/codegen/lib/coin_test_gen.rb @@ -1,4 +1,4 @@ -# Helper for creating/updating CoinType unit tests, based on the coins.json file +# Helper for creating/updating CoinType unit tests, based on the registry.json file require 'erb' require 'fileutils' @@ -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/derivation.rb b/codegen/lib/derivation.rb new file mode 100644 index 00000000000..1b9b8233e78 --- /dev/null +++ b/codegen/lib/derivation.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'file_editor' + +$derivation_file = "include/TrustWalletCore/TWDerivation.h" +$derivation_file_rust = "rust/tw_coin_registry/src/tw_derivation.rs" + +# Returns a derivation name if specified. +def derivation_name(deriv) + return "" if deriv['name'].nil? + deriv['name'].downcase +end + +# Returns a string of `` if derivation's name is specified, otherwise returns `Default`. +def derivation_enum_name_no_prefix(deriv, coin) + return "Default" if deriv['name'].nil? + format_name(coin['name']) + camel_case(deriv['name']) +end + +# Returns a string of `TWDerivation` if derivation's name is specified, otherwise returns `TWDerivationDefault`. +def derivation_enum_name(deriv, coin) + return "TWDerivation" + derivation_enum_name_no_prefix(deriv, coin) +end + +# Returns a derivation path. +def derivation_path(coin) + coin['derivation'][0]['path'] +end + +# Get the last `TWDerivation` enum variant ID. +def get_last_derivation(file_path) + last_derivation_id = nil + + File.open(file_path, "r") do |file| + file.each_line do |line| + # Match lines that define a TWDerivation enum value + if line =~ /TWDerivation\w+\s*=\s*(\d+),/ + last_derivation_id = $1.to_i + end + end + end + + last_derivation_id +end + +# Returns whether the TWDerivation enum contains the given `derivation` variant. +def find_derivation(file_path, derivation) + File.open(file_path, "r") do |file| + file.each_line do |line| + return true if line.include?(derivation) + end + end + return false +end + +# Insert a new `TWDerivation = N,` to the end of the enum. +def insert_derivation(file_path, derivation, derivation_id) + target_line = " #{derivation} = #{derivation_id}," + insert_target_line(file_path, target_line, " // end_of_derivation_enum - USED TO GENERATE CODE\n") +end + +# Update TWDerivation enum variants if new derivation appeared. +def update_derivation_enum(coins) + coins.each do |coin| + coin['derivation'].each_with_index do |deriv, index| + deriv_name = derivation_enum_name(deriv, coin) + if !find_derivation($derivation_file, deriv_name) + new_derivation_id = get_last_derivation($derivation_file) + 1 + insert_derivation($derivation_file, deriv_name, new_derivation_id) + + rust_deriv_name = derivation_enum_name_no_prefix(deriv, coin) + insert_derivation($derivation_file_rust, rust_deriv_name, new_derivation_id) + end + end + end +end diff --git a/codegen/lib/entity_decl.rb b/codegen/lib/entity_decl.rb index f22a0ea6587..aee285ef97b 100644 --- a/codegen/lib/entity_decl.rb +++ b/codegen/lib/entity_decl.rb @@ -2,16 +2,17 @@ # Class or struct declaration class EntityDecl - attr_reader :name + attr_reader :name, :comment attr_accessor :is_struct, :methods, :properties, :static_methods, :static_properties - def initialize(name:, is_struct:) + def initialize(name:, is_struct:, comment:) @name = name @is_struct = is_struct @methods = [] @properties = [] @static_methods = [] @static_properties = [] + @comment = comment end def struct? diff --git a/codegen/lib/enum_decl.rb b/codegen/lib/enum_decl.rb index 303909dec82..0fae5774f50 100644 --- a/codegen/lib/enum_decl.rb +++ b/codegen/lib/enum_decl.rb @@ -2,11 +2,11 @@ # Enum declaration. class EnumDecl - attr_reader :name + attr_reader :name, :comment attr_accessor :cases, :raw_type attr_accessor :methods, :properties, :static_methods, :static_properties - def initialize(name:, raw_type:) + def initialize(name:, raw_type:, comment:) @name = name @cases = [] @raw_type = raw_type @@ -14,6 +14,7 @@ def initialize(name:, raw_type:) @properties = [] @static_methods = [] @static_properties = [] + @comment = comment end def struct? diff --git a/codegen/lib/file_editor.rb b/codegen/lib/file_editor.rb new file mode 100644 index 00000000000..15e60726d67 --- /dev/null +++ b/codegen/lib/file_editor.rb @@ -0,0 +1,19 @@ +def 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 diff --git a/codegen/lib/function_decl.rb b/codegen/lib/function_decl.rb index cd5135f1231..de0a3f7853c 100644 --- a/codegen/lib/function_decl.rb +++ b/codegen/lib/function_decl.rb @@ -4,8 +4,9 @@ class FunctionDecl attr_reader :name, :entity 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) + def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], static: false, discardable_result: false, comment: '') @name = name @entity = entity @is_method = is_method @@ -13,6 +14,8 @@ def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], s @parameters = parameters @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..b99bf1a631d --- /dev/null +++ b/codegen/lib/kotlin_helper.rb @@ -0,0 +1,251 @@ +# 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) + if param.type.name == :data + "#{name}Data#{convert_calling_type_ios(param.type)}" + elsif param.type.name == :string + "#{name}String#{convert_calling_type_ios(param.type)}" + else + "#{name}#{convert_calling_type_ios(param.type)}" + end + 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) + 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 + + 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 fab69837507..e4e287614a0 100644 --- a/codegen/lib/parser.rb +++ b/codegen/lib/parser.rb @@ -9,7 +9,7 @@ # C header parser class Parser - attr_reader :path, :entity + attr_reader :path, :entity, :entity_comment def initialize(path:, string: nil) @path = path @@ -19,32 +19,49 @@ def initialize(path:, string: nil) # Parses a C header file for class/struct declarations def parse + clear_comment until @buffer.eos? - break if @buffer.skip_until(/\n/).nil? + @buffer.skip(/\s*/) + + if !@buffer.scan(/\/\//).nil? + @entity_comment = @entity_comment + '//' + @buffer.scan_until(/(\r\n|\r|\n)/) + next + end + + if !@buffer.scan(/TW_EXTERN_C_BEGIN/).nil? + # This is to ignore very first comments from the file + clear_comment + next + end + + @entity_comment = @entity_comment.strip # Look for TW_EXPORT statements - @buffer.skip(/\s*/) - next if @buffer.scan(/TW_EXPORT_[A-Z_]+/).nil? - - # Handle statements - case @buffer[0] - when 'TW_EXPORT_CLASS' - handle_class - when 'TW_EXPORT_STRUCT' - handle_struct - when 'TW_EXPORT_ENUM' - handle_enum - when 'TW_EXPORT_FUNC' - handle_func - when 'TW_EXPORT_METHOD' - handle_method - when 'TW_EXPORT_PROPERTY' - handle_property - when 'TW_EXPORT_STATIC_METHOD' - handle_static_method - when 'TW_EXPORT_STATIC_PROPERTY' - handle_static_property + if !@buffer.scan(/TW_EXPORT_[A-Z_]+/).nil? + # Handle statements + case @buffer[0] + when 'TW_EXPORT_CLASS' + handle_class + when 'TW_EXPORT_STRUCT' + handle_struct + when 'TW_EXPORT_ENUM' + handle_enum + when 'TW_EXPORT_FUNC' + handle_func + when 'TW_EXPORT_METHOD' + handle_method + when 'TW_EXPORT_PROPERTY' + handle_property + when 'TW_EXPORT_STATIC_METHOD' + handle_static_method + when 'TW_EXPORT_STATIC_PROPERTY' + handle_static_property + end + + clear_comment end + + break if @buffer.skip_until(/\n/).nil? end @entity @@ -80,7 +97,7 @@ def parse_func @buffer.skip(/\s*/) scan_or_fail(/\w+/, 'Invalid function name') - func = FunctionDecl.new(name: @buffer[0], entity: @entity, is_method: true, return_type: return_type) + func = FunctionDecl.new(name: @buffer[0], entity: @entity, is_method: true, return_type: return_type, comment: @entity_comment) @buffer.skip(/\s*/) scan_or_fail(/\(/, 'Invalid function declaration. Expected (') @@ -117,7 +134,7 @@ def handle_class @buffer.skip(/\s*/) report_error 'Invalid type name' if @buffer.scan(/struct TW(\w+)\s*;/).nil? report_error 'Found more than one class/struct in the same file' unless @entity.nil? - @entity = EntityDecl.new(name: @buffer[1], is_struct: false) + @entity = EntityDecl.new(name: @buffer[1], is_struct: false, comment: @entity_comment) puts "Found a class #{@entity.name}" end @@ -125,7 +142,7 @@ def handle_struct @buffer.skip(/\s*/) report_error 'Invalid type name at' if @buffer.scan(/struct TW(\w+)\s*\{?/).nil? report_error 'Found more than one class/struct in the same file' unless @entity.nil? - @entity = EntityDecl.new(name: @buffer[1], is_struct: true) + @entity = EntityDecl.new(name: @buffer[1], is_struct: true, comment: @entity_comment) puts "Found a struct #{@buffer[1]}" end @@ -136,7 +153,7 @@ def handle_enum @buffer.skip(/\s*/) report_error 'Invalid enum' if @buffer.scan(/enum TW(\w+)\s*\{/).nil? - @entity = EnumDecl.new(name: @buffer[1], raw_type: TypeDecl.fromPrimitive(type)) + @entity = EnumDecl.new(name: @buffer[1], raw_type: TypeDecl.fromPrimitive(type), comment: @entity_comment) incremental_value = 0 until @buffer.eos? @@ -277,4 +294,8 @@ def report_error(message) def current_line_number @buffer.string[0..@buffer.pos].count("\n") + 1 end + + def clear_comment + @entity_comment = '' + end end diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index c0b3e85fcb9..d2bbb5d6b71 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.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. // // This is a GENERATED FILE, changes made here WILL BE LOST. // @@ -21,21 +19,22 @@ static const CoinInfo defaultsForMissing = { TWBlockchainBitcoin, TWPurposeBIP44, TWCurveNone, - TWHDVersionNone, - TWHDVersionNone, - "", + {Derivation()}, TWPublicKeyTypeSECP256k1, 0, 0, 0, TWHRPUnknown, - Hash::sha256ripemd, - Hash::sha256d, + "", + Hash::HasherSha256ripemd, + Hash::HasherSha256d, + Hash::HasherSha256ripemd, "?", 2, "", "", 0, + 0 }; /// Get coin from map, if missing returns defaults (not to have contains-check in each accessor method) @@ -46,25 +45,34 @@ const CoinInfo getCoinInfo(TWCoinType coin) { case TWCoinType<%= format_name(coin['name']) %>: return CoinInfo { "<%= coin['id'] %>", - <% if coin['displayName'].nil? -%>"<%= coin['name'] %>"<% else -%>"<%= coin['displayName'] %>"<% end -%>, + "<%= coin_name(coin) %>", TWBlockchain<%= format_name(coin['blockchain']) %>, - TWPurposeBIP<%= /^m\/(\d+)'?(\/\d+'?)+$/.match(coin['derivationPath'])[1] %>, + TWPurposeBIP<%= /^m\/(\d+)'?(\/\d+'?)+$/.match(derivation_path(coin))[1] %>, TWCurve<%= format_name(coin['curve']) %>, - TWHDVersion<% if coin['xpub'].nil? -%>None<% else -%><%= format_name(coin['xpub']) %><% end -%>, - TWHDVersion<% if coin['xprv'].nil? -%>None<% else -%><%= format_name(coin['xprv']) %><% end -%>, - "<%= coin['derivationPath'] %>", + { + <% coin['derivation'].each do |deriv| -%>{ + <%= derivation_enum_name(deriv, coin) %>, + "<%= deriv['path'] %>", + "<%= derivation_name(deriv) %>", + TWHDVersion<% if deriv['xpub'].nil? -%>None<% else -%><%= format_name(deriv['xpub']) %><% end -%>, + TWHDVersion<% if deriv['xprv'].nil? -%>None<% else -%><%= format_name(deriv['xprv']) %><% end -%>, + }, + <% end -%>}, TWPublicKeyType<%= format_name(coin['publicKeyType']) %>, <% if coin['staticPrefix'].nil? -%>0<% else -%><%= coin['staticPrefix'] %><% end -%>, <% if coin['p2pkhPrefix'].nil? -%>0<% else -%><%= coin['p2pkhPrefix'] %><% end -%>, <% if coin['p2shPrefix'].nil? -%>0<% else -%><%= coin['p2shPrefix'] %><% end -%>, TWHRP<% if coin['hrp'].nil? -%>Unknown<% else -%><%= format_name(coin['name']) %><% end -%>, - Hash::<% if coin['publicKeyHasher'].nil? -%>sha256ripemd<% else -%><%= coin['publicKeyHasher'] %><% end -%>, - Hash::<% if coin['base58Hasher'].nil? -%>sha256d<% else -%><%= coin['base58Hasher'] %><% end -%>, + "<%= coin['chainId'] %>", + Hash::Hasher<% if coin['publicKeyHasher'].nil? -%>Sha256ripemd<% else -%><%= camel_case(coin['publicKeyHasher']) %><% end -%>, + Hash::Hasher<% if coin['base58Hasher'].nil? -%>Sha256d<% else -%><%= camel_case(coin['base58Hasher']) %><% end -%>, + Hash::Hasher<% if coin['addressHasher'].nil? -%>Sha256ripemd<% else -%><%= camel_case(coin['addressHasher']) %><% end -%>, "<%= coin['symbol'] %>", <%= coin['decimals'] %>, "<%= 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 22e4d92d434..bb8cbdadb79 100644 --- a/codegen/lib/templates/TWCoinTypeTests.cpp.erb +++ b/codegen/lib/templates/TWCoinTypeTests.cpp.erb @@ -1,34 +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. // // 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 TEST(TW<%= format_name(coin['name']) %>CoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinType<%= format_name(coin['name']) %>)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_tx(coin) %>")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinType<%= format_name(coin['name']) %>, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_account(coin) %>")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinType<%= format_name(coin['name']) %>, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinType<%= format_name(coin['name']) %>)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinType<%= format_name(coin['name']) %>)); + const auto coin = TWCoinType<%= format_name(coin['name']) %>; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); +<% if !coin['chainId'].nil? -%> + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); +<% end -%> + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_tx(coin) %>")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("<%= explorer_sample_account(coin) %>")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinType<%= format_name(coin['name']) %>), <%= coin['decimals'] %>); - ASSERT_EQ(TWBlockchain<%= coin['blockchain'] %>, TWCoinTypeBlockchain(TWCoinType<%= format_name(coin['name']) %>)); - ASSERT_EQ(0x<%= to_hex(coin['p2shPrefix']) %>, TWCoinTypeP2shPrefix(TWCoinType<%= format_name(coin['name']) %>)); - ASSERT_EQ(0x<%= to_hex(coin['staticPrefix']) %>, TWCoinTypeStaticPrefix(TWCoinType<%= format_name(coin['name']) %>)); + assertStringsEqual(id, "<%= coin['id'] %>"); + assertStringsEqual(name, "<%= display_name(coin) %>"); assertStringsEqual(symbol, "<%= coin['symbol'] %>"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), <%= coin['decimals'] %>); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchain<%= coin['blockchain'] %>); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x<%= to_hex(coin['p2shPrefix']) %>); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x<%= to_hex(coin['staticPrefix']) %>); +<% if !coin['chainId'].nil? -%> + assertStringsEqual(chainId, "<%= format_name(coin['chainId']) %>"); +<% end -%> assertStringsEqual(txUrl, "<%= explorer_tx_url(coin) %><%= explorer_sample_tx(coin) %>"); assertStringsEqual(accUrl, "<%= explorer_account_url(coin) %><%= explorer_sample_account(coin) %>"); - assertStringsEqual(id, "<%= coin['id'] %>"); - assertStringsEqual(name, "<%= display_name(coin) %>"); } diff --git a/codegen/lib/templates/TWDerivation.h.erb b/codegen/lib/templates/TWDerivation.h.erb new file mode 100644 index 00000000000..e2c9a53a494 --- /dev/null +++ b/codegen/lib/templates/TWDerivation.h.erb @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. +// + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Non-default coin address derivation names (default, unnamed derivations are not included). +TW_EXPORT_ENUM() +enum TWDerivation { + TWDerivationDefault = 0, // default, for any coin + TWDerivationCustom = 1, // custom, for any coin +<% enum_count += 1 -%> +<% coins.each do |coin| -%> +<% 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 -%> +}; + +TW_EXTERN_C_END diff --git a/codegen/lib/templates/TWEthereumChainID.h.erb b/codegen/lib/templates/TWEthereumChainID.h.erb new file mode 100644 index 00000000000..3e6385e1b77 --- /dev/null +++ b/codegen/lib/templates/TWEthereumChainID.h.erb @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. +// + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Chain identifiers for Ethereum-based blockchains, for convenience. Recommended to use the dynamic CoinType.ChainId() instead. +/// See also TWChainId. +TW_EXPORT_ENUM(uint32_t) +enum TWEthereumChainID { +<% chains = ['Ethereum', 'Ronin', 'Vechain'] -%> +<% coins.select{ |coin| chains.include?(coin['blockchain']) && coin['deprecated'] != true }.each do |coin| -%> + TWEthereumChainID<%= camel_case(coin['id']) %> = <%= coin['chainId'] %>, +<% end -%> +}; + +TW_EXTERN_C_END diff --git a/codegen/lib/templates/coins.md.erb b/codegen/lib/templates/coins.md.erb deleted file mode 100644 index d9f71d2fa4e..00000000000 --- a/codegen/lib/templates/coins.md.erb +++ /dev/null @@ -1,9 +0,0 @@ -# Full list - -This list is generated from [./coins.json](../coins.json) - -| Index | Name | Symbol | Logo | URL | -| ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -<% coins.select{ |c| c['deprecated'].nil? }.each do |coin| -%> -| <%= coin['coinId'].to_s.ljust(7, " ") %> | <%= coin['name'].ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | -<% end -%> diff --git a/codegen/lib/templates/copyright_header.erb b/codegen/lib/templates/copyright_header.erb new file mode 100644 index 00000000000..39239c27440 --- /dev/null +++ b/codegen/lib/templates/copyright_header.erb @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// diff --git a/codegen/lib/templates/cpp/class.erb b/codegen/lib/templates/cpp/class.erb new file mode 100644 index 00000000000..ced639f9ebf --- /dev/null +++ b/codegen/lib/templates/cpp/class.erb @@ -0,0 +1,37 @@ +#include "<%= entity.name %>.h" + +namespace TW::Wasm { +<%# Constructors -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> +<%= render('cpp/static_method.erb', { method: method }) -%> +<% end -%> +<%# Properties -%> +<%= render('cpp/class_properties.erb') -%> + +<%# Methods -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> +<%= render('cpp/method.erb', { method: method }) %> +<% end -%> + +<%# EMSCRIPTEN_BINDINGS -%> +EMSCRIPTEN_BINDINGS(Wasm_TW<%= entity.name %>) { + class_<<%= WasmCppHelper.class_name(entity: entity) %>>("<%= entity.name %>") +<% entity.static_methods.each do |method| -%> +<% function_name = WasmCppHelper.function_name(entity: entity, function: method) -%> + .class_function("<%= function_name %>", &<%= WasmCppHelper.class_name(entity: entity) %>::<%= function_name %>, allow_raw_pointers()) +<% end -%> +<%- entity.properties.each do |property| -%> +<% function_name = WasmCppHelper.format_name(property.name) -%> + .function("<%= function_name %>", &<%= WasmCppHelper.class_name(entity: entity) %>::<%= function_name %>, allow_raw_pointers()) +<%- end -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> +<% function_name = WasmCppHelper.format_name(method.name) -%> + .function("<%= function_name %>", &<%= WasmCppHelper.class_name(entity: entity) %>::<%= function_name %>, allow_raw_pointers()) +<% end -%> + ; +} + +} // namespace TW::Wasm diff --git a/codegen/lib/templates/cpp/class_properties.erb b/codegen/lib/templates/cpp/class_properties.erb new file mode 100644 index 00000000000..0648de25352 --- /dev/null +++ b/codegen/lib/templates/cpp/class_properties.erb @@ -0,0 +1,22 @@ +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>) -> <%= WasmCppHelper.class_name(entity: entity) %>* { +<%= render('cpp/parameter_access.erb', { parameters: method.parameters }) -%> + TW<%= entity.name %> *instance = TW<%= entity.name %><%= method.name %>(<%= WasmCppHelper.arguments(method.parameters).join(', ') %>); +<% if method.return_type.is_nullable -%> + if (instance == nullptr) { + return nullptr; + } +<% end -%> + return new <%= WasmCppHelper.class_name(entity: entity) %>(instance); +} + +<% end -%> + +<%# Properties -%> +<%- entity.properties.each do |property| -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.format_name(property.name) %>() { +<%= render('cpp/method_forward.erb', { method: property }) -%> +} + +<%- end -%> diff --git a/codegen/lib/templates/cpp/enum.erb b/codegen/lib/templates/cpp/enum.erb new file mode 100644 index 00000000000..398420d93eb --- /dev/null +++ b/codegen/lib/templates/cpp/enum.erb @@ -0,0 +1,32 @@ +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<% if has_string -%> +#include + +<% end -%> +namespace TW::Wasm { + +<% if has_string -%> +auto describe<%= entity.name %>(TW<%= entity.name %> value) -> std::string { + switch (value) { +<% entity.cases.each do |c| -%> + case TW<%= entity.name %><%= c.name %>: return <%= c.string %>; +<% end -%> + default: return ""; + } +} + +<% end -%> +EMSCRIPTEN_BINDINGS(Wasm_TW<%= entity.name %>) { + enum_>("<%= entity.name %>") +<%# Cases -%> +<% entity.cases.each do |c| -%> + .value("<%= WasmCppHelper.format_name(c.name) %>", TW<%= entity.name %>::TW<%= entity.name %><%= c.name %>) +<% end -%> + ; +<%# Description -%> +<% if has_string -%> + function("describe<%= entity.name %>", describe<%= entity.name %>); +<% end -%> +} + +} // namespace TW::Wasm diff --git a/codegen/lib/templates/cpp/header.erb b/codegen/lib/templates/cpp/header.erb new file mode 100644 index 00000000000..1132e73092b --- /dev/null +++ b/codegen/lib/templates/cpp/header.erb @@ -0,0 +1,41 @@ +#include + +#include "../WasmString.h" +#include "../WasmData.h" + +namespace TW::Wasm { + +class <%= WasmCppHelper.class_name(entity: entity) %> { +<% if not entity.is_struct -%> + public: + TW<%= entity.name %> *instance; + public: + <%= WasmCppHelper.class_name(entity: entity) %>(TW<%= entity.name %> *instance) : instance(instance) {} +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + ~<%= WasmCppHelper.class_name(entity: entity) %>() { + TW<%= entity.name %>Delete(instance); + } +<% end -%> +<% end -%> + public: +<%# Constructors -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> + static auto <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>); +<% end -%> +<%# Properties -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> + static auto <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>) -> <%= WasmCppHelper.class_name(entity: entity) %>*; +<% end -%> +<%- entity.properties.each do |property| -%> + auto <%= WasmCppHelper.format_name(property.name) %>(); +<%- end -%> +<%# Methods -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> + auto <%= WasmCppHelper.format_name(method.name) %>(<%= WasmCppHelper.parameters(method.parameters.drop(1)) %>); +<% end -%> +}; // class <%= entity.name %> + +} // namespace TW::Wasm diff --git a/codegen/lib/templates/cpp/includes.erb b/codegen/lib/templates/cpp/includes.erb new file mode 100644 index 00000000000..6c3d2e996c4 --- /dev/null +++ b/codegen/lib/templates/cpp/includes.erb @@ -0,0 +1,21 @@ +<%# Include entity headers -%> +<% require 'set' -%> +<% includes = Set.new([entity.name]) -%> +<% (entity.static_methods + entity.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 -%> + +<% includes.each do |include| -%> +<% next if include == entity.name -%> +#include "./<%= include %>.h" +<% end -%> + +#include + +using namespace emscripten; diff --git a/codegen/lib/templates/cpp/method.erb b/codegen/lib/templates/cpp/method.erb new file mode 100644 index 00000000000..f4b0a5954f1 --- /dev/null +++ b/codegen/lib/templates/cpp/method.erb @@ -0,0 +1,6 @@ +<% method = locals[:method] -%> +<% arguments = locals[:arguments] || ['instance'] + WasmCppHelper.arguments(method.parameters.drop(1)) -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.format_name(method.name) %>(<%= WasmCppHelper.parameters(method.parameters.drop(1)) %>) { +<%= render('cpp/parameter_access.erb', { parameters: method.parameters }) -%> +<%= render('cpp/method_forward.erb', { method: method, arguments: arguments }) -%> +} diff --git a/codegen/lib/templates/cpp/method_call.erb b/codegen/lib/templates/cpp/method_call.erb new file mode 100644 index 00000000000..9b24e549e30 --- /dev/null +++ b/codegen/lib/templates/cpp/method_call.erb @@ -0,0 +1,5 @@ +<% + method = locals[:method] + arguments = locals[:arguments] || ['instance'] + WasmCppHelper.arguments(method.parameters.drop(1)) +-%> +TW<%= entity.name %><%= method.name %>(<%= arguments.join(', ') %>) \ No newline at end of file diff --git a/codegen/lib/templates/cpp/method_forward.erb b/codegen/lib/templates/cpp/method_forward.erb new file mode 100644 index 00000000000..46a856a5118 --- /dev/null +++ b/codegen/lib/templates/cpp/method_forward.erb @@ -0,0 +1,29 @@ +<% + method = locals[:method] + arguments = locals[:arguments] || ['instance'] + WasmCppHelper.arguments(method.parameters.drop(1)) + call = render('cpp/method_call.erb', { method: method, arguments: arguments }) + + # Method returns data + if should_return_data(method) + if method.return_type.is_nullable -%> + return TWDataToVal(<%= call %>); +<% else -%> + return TWDataToVal(<%= call %>); +<% end + # Method returns a string + elsif should_return_string(method) + if method.return_type.is_nullable -%> + return TWStringToStd(<%= call %>); +<% else -%> + return TWStringToStd(<%= call %>); +<% end + # Method returns a class or struct + elsif method.return_type.is_class || method.return_type.is_struct + if method.return_type.is_nullable -%> + return new Wasm<%= method.return_type.name %>(<%= call %>); +<% else -%> + return new Wasm<%= method.return_type.name %>(<%= call %>); +<% end + else -%> + return <%= call %>; +<%end -%> diff --git a/codegen/lib/templates/cpp/parameter_access.erb b/codegen/lib/templates/cpp/parameter_access.erb new file mode 100644 index 00000000000..da6e41d367a --- /dev/null +++ b/codegen/lib/templates/cpp/parameter_access.erb @@ -0,0 +1,8 @@ +<% parameters = locals[:parameters] -%> +<% parameters.each do |param| -%> +<% if param.type.name == :data -%> + auto <%= param.name %>Data = TW::data(<%= param.name %>); +<% elsif param.type.name == :string -%> +<% elsif param.type.is_struct || param.type.is_class -%> +<% end -%> +<% end -%> diff --git a/codegen/lib/templates/cpp/static_method.erb b/codegen/lib/templates/cpp/static_method.erb new file mode 100644 index 00000000000..86f9bc2734e --- /dev/null +++ b/codegen/lib/templates/cpp/static_method.erb @@ -0,0 +1,6 @@ +<% method = locals[:method] -%> +<% arguments = WasmCppHelper.arguments(method.parameters) -%> +auto <%= WasmCppHelper.class_name(entity: entity) %>::<%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= WasmCppHelper.parameters(method.parameters) %>) { +<%= render('cpp/parameter_access.erb', { parameters: method.parameters }) -%> +<%= render('cpp/method_forward.erb', { method: method, arguments: arguments }) -%> +} diff --git a/codegen/lib/templates/hrp.cpp.erb b/codegen/lib/templates/hrp.cpp.erb index 771b3b5aac0..c3705bd5a9f 100644 --- a/codegen/lib/templates/hrp.cpp.erb +++ b/codegen/lib/templates/hrp.cpp.erb @@ -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. // -// This is a GENERATED FILE from \coins.json, changes made here WILL BE LOST. +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // #include diff --git a/codegen/lib/templates/hrp.h.erb b/codegen/lib/templates/hrp.h.erb index 0274fdf7597..4691fbafc5b 100644 --- a/codegen/lib/templates/hrp.h.erb +++ b/codegen/lib/templates/hrp.h.erb @@ -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. // -// This is a GENERATED FILE from \coins.json, changes made here WILL BE LOST. +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // #pragma once diff --git a/codegen/lib/templates/java/class.erb b/codegen/lib/templates/java/class.erb index 8f9d3367739..3b49d35a49d 100644 --- a/codegen/lib/templates/java/class.erb +++ b/codegen/lib/templates/java/class.erb @@ -1,12 +1,13 @@ import java.security.InvalidParameterException; -import java.util.HashSet; +import wallet.core.java.GenericPhantomReference; <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% 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; @@ -20,14 +21,14 @@ public class <%= entity.name %> { <%= entity.name %> instance = new <%= entity.name %>(); instance.nativeHandle = nativeHandle; <% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> - <%= entity.name %>PhantomReference.register(instance, nativeHandle); + GenericPhantomReference.register(instance, nativeHandle, <%= entity.name %>::nativeDelete); <% end -%> return instance; } - <%# Constructor declarations -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Create') -%> +<%= method.comment_with_indent %> static native long native<%= method.name %>(<%= JavaHelper.parameters(method.parameters) %>); <% end -%> <%# Destructor declarations -%> @@ -35,9 +36,9 @@ public class <%= entity.name %> { <% 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_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) -%> @@ -49,6 +50,7 @@ public class <%= entity.name %> { <%# Static method declarations -%> <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') -%> +<%= 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) -%> @@ -59,6 +61,7 @@ public class <%= entity.name %> { <% end -%> <%# Property declarations -%> <% entity.properties.each do |property| -%> +<%= 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) -%> @@ -70,6 +73,7 @@ public class <%= entity.name %> { <%# Method declarations -%> <% entity.methods.each do |method| -%> <% next if method.name.start_with?('Delete') -%> +<%= 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) -%> @@ -82,43 +86,18 @@ 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_with_indent %> public <%= entity.name %>(<%= JavaHelper.parameters(method.parameters) %>) { nativeHandle = native<%= method.name %>(<%= JavaHelper.arguments(method.parameters) %>); if (nativeHandle == 0) { throw new InvalidParameterException(); } - <%= entity.name %>PhantomReference.register(this, nativeHandle); + GenericPhantomReference.register(this, nativeHandle, <%= entity.name %>::nativeDelete); } <%- 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>(); - private static java.lang.ref.ReferenceQueue<<%= entity.name %>> queue = new java.lang.ref.ReferenceQueue<<%= entity.name %>>(); - private long nativeHandle; - - private <%= entity.name %>PhantomReference(<%= entity.name %> referent, long nativeHandle) { - super(referent, queue); - this.nativeHandle = nativeHandle; - } - - static void register(<%= entity.name %> referent, long nativeHandle) { - references.add(new <%= entity.name %>PhantomReference(referent, nativeHandle)); - } - - public static void doDeletes() { - <%= entity.name %>PhantomReference ref = (<%= entity.name %>PhantomReference) queue.poll(); - for (; ref != null; ref = (<%= entity.name %>PhantomReference) queue.poll()) { - <%= entity.name %>.nativeDelete(ref.nativeHandle); - references.remove(ref); - } - } -} -<% end -%> diff --git a/codegen/lib/templates/java/enum.erb b/codegen/lib/templates/java/enum.erb index 27947182cc8..354b7c145a3 100644 --- a/codegen/lib/templates/java/enum.erb +++ b/codegen/lib/templates/java/enum.erb @@ -1,4 +1,5 @@ <% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<%= entity.comment %> <% type = entity.raw_type ? JavaHelper.type(entity.raw_type) : 'int' -%> public enum <%= entity.name %> { <%# Cases -%> diff --git a/codegen/lib/templates/java/header.erb b/codegen/lib/templates/java/header.erb index 72cbfaf1a19..3c5da1f81ed 100644 --- a/codegen/lib/templates/java/header.erb +++ b/codegen/lib/templates/java/header.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. // // 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 5499705670d..6f09dda87d2 100644 --- a/codegen/lib/templates/java/struct.erb +++ b/codegen/lib/templates/java/struct.erb @@ -2,10 +2,11 @@ import java.security.InvalidParameterException; <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% 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; @@ -17,15 +18,15 @@ 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_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_with_indent %> <%- if should_return_data(property) -%> public static native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <%- else -%> @@ -35,6 +36,7 @@ public class <%= entity.name %> { <%# Static method declarations -%> <%- entity.static_methods.each do |method| -%> <%- next if method.name.start_with?('Init') -%> +<%= method.comment_with_indent %> <%- if should_return_data(method) -%> public static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <%- else -%> @@ -43,6 +45,7 @@ public class <%= entity.name %> { <%- end -%> <%# Property declarations -%> <%- entity.properties.each do |property| -%> +<%= property.comment_with_indent %> <%- if should_return_data(property) -%> public native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <%- else -%> @@ -52,6 +55,7 @@ public class <%= entity.name %> { <%# Method declarations -%> <%- entity.methods.each do |method| -%> <%- next if method.name.start_with?('Delete') -%> +<%= method.comment_with_indent %> <%- if should_return_data(method) -%> public native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <%- else -%> @@ -62,16 +66,15 @@ 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?('Init') -%> +<%= 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/compare_to.erb b/codegen/lib/templates/jni/compare_to.erb index fa3515d00bc..b2b5a4e21a1 100644 --- a/codegen/lib/templates/jni/compare_to.erb +++ b/codegen/lib/templates/jni/compare_to.erb @@ -1,7 +1,7 @@ <% less = locals[:less] -%> <% equal = locals[:equal] -%> <% compareMethod = JNIHelper.compareMethod(entity) -%> -<%= render('jni/method_prototype.erb', { method: compareMethod }) %> { +<%= render('jni/method_prototype.erb', { method: compareMethod, maybe_unused: true }) %> { <%= render('jni/instance_access.erb', { entity: entity }) %> <%= render('jni/parameter_access.erb', { method: compareMethod }) -%> <% if entity.struct? -%> diff --git a/codegen/lib/templates/jni/header.erb b/codegen/lib/templates/jni/header.erb deleted file mode 100644 index 0770a18cd04..00000000000 --- a/codegen/lib/templates/jni/header.erb +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 WILL BE LOST. -// diff --git a/codegen/lib/templates/jni/method.erb b/codegen/lib/templates/jni/method.erb index 5b33da3a226..dfd3962c74c 100644 --- a/codegen/lib/templates/jni/method.erb +++ b/codegen/lib/templates/jni/method.erb @@ -1,5 +1,5 @@ <% method = locals[:method] -%> -<%= render('jni/method_prototype.erb', { method: method }) %> { +<%= render('jni/method_prototype.erb', { method: method, maybe_unused: true }) %> { <% if !method.static -%> <%= render('jni/instance_access.erb', { entity: entity }) %> <% end -%> diff --git a/codegen/lib/templates/jni/method_prototype.erb b/codegen/lib/templates/jni/method_prototype.erb index 6aa64528c88..a8cda92705f 100644 --- a/codegen/lib/templates/jni/method_prototype.erb +++ b/codegen/lib/templates/jni/method_prototype.erb @@ -1,9 +1,16 @@ <% method = locals[:method] + maybe_unused = locals[:maybe_unused] + + maybe_unused_str = '' + if maybe_unused + maybe_unused_str = '[[maybe_unused]] ' + end + if method.static - parameters = 'jclass thisClass' + JNIHelper.parameters(method.parameters) + parameters = maybe_unused_str + 'jclass thisClass' + JNIHelper.parameters(method.parameters) else - parameters = 'jobject thisObject' + JNIHelper.parameters(method.parameters.drop(1)) + parameters = maybe_unused_str + 'jobject thisObject' + JNIHelper.parameters(method.parameters.drop(1)) end -%> -<%= JNIHelper.type(method.return_type) %> JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, <%= parameters %>)<% -%> +<%= JNIHelper.type(method.return_type) %> JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(<%= maybe_unused_str %>JNIEnv *env, <%= parameters %>)<% -%> diff --git a/codegen/lib/templates/jni/parameter_access.erb b/codegen/lib/templates/jni/parameter_access.erb index 71bc2641c41..ea9e3c13c5d 100644 --- a/codegen/lib/templates/jni/parameter_access.erb +++ b/codegen/lib/templates/jni/parameter_access.erb @@ -1,13 +1,29 @@ <% 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) end - parameters.each do |param| - if param.type.name == :data -%> + parameters.each do |param| -%> +<% if !param.type.is_nullable && (JNIHelper.type(param.type) == 'jobject' || JNIHelper.type(param.type) == 'jstring' || JNIHelper.type(param.type) == 'jbyteArray') + # In case of constructor (starts with Create), it always returns jlong type. + if method.name.start_with?('Create') -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'void' -%> + JNI_CHECK_NULL_AND_RETURN_VOID(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'jbyteArray' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'jstring' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'jobject' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% else -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% end -%> +<% end -%> +<% 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 %>); 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/jni_c.erb b/codegen/lib/templates/jni_c.erb index cf807ae79ad..5f7f56125bc 100644 --- a/codegen/lib/templates/jni_c.erb +++ b/codegen/lib/templates/jni_c.erb @@ -3,7 +3,7 @@ #include <% require 'set' -%> -<% includes = SortedSet.new([entity.name]) -%> +<% 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| -%> @@ -20,7 +20,7 @@ <%# Constructors -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Create') -%> -jlong JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass<%= JNIHelper.parameters(method.parameters) %>) { +jlong JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass thisClass<%= JNIHelper.parameters(method.parameters) %>) { <%= render('jni/parameter_access.erb', { method: method }) -%> struct TW<%= entity.name %> *instance = TW<%= entity.name %><%= method.name %>(<%= JNIHelper.arguments(method.parameters).join(', ') %>); <%= render('jni/parameter_release.erb', { method: method }) -%> @@ -31,7 +31,7 @@ jlong JNICALL <%= JNIHelper.function_name(entity: entity, function: method, nati <%# Destructors -%> <% entity.methods.each do |method| -%> <% next unless method.name.start_with?('Delete') -%> -void JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass, jlong handle) { +void JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass thisClass, jlong handle) { TW<%= entity.name %>Delete((struct TW<%= entity.name %> *) handle); } @@ -39,7 +39,7 @@ void JNICALL <%= JNIHelper.function_name(entity: entity, function: method, nativ <%# Initializers -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Init') -%> -jbyteArray JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, jclass thisClass<%= JNIHelper.parameters(method.parameters.drop(1)) %>) { +jbyteArray JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, [[maybe_unused]] jclass thisClass<%= JNIHelper.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; diff --git a/codegen/lib/templates/jni_h.erb b/codegen/lib/templates/jni_h.erb index 64ae676d449..0769b406880 100644 --- a/codegen/lib/templates/jni_h.erb +++ b/codegen/lib/templates/jni_h.erb @@ -30,34 +30,34 @@ jbyteArray JNICALL <%= JNIHelper.function_name(entity: entity, function: method) <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: property }) %>; +<%= render('jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; <% end -%> <%# Static method declarations -%> <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: method }) %>; +<%= render('jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; <% end -%> <%# Property declarations -%> <% entity.properties.each do |property| -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: property }) %>; +<%= render('jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; <% end -%> <%# Method declarations -%> <% entity.methods.each do |method| -%> <% next if method.name.start_with?('Delete') -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: method }) %>; +<%= render('jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; <% 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('jni/method_prototype.erb', { method: JNIHelper.compareMethod(entity) }) %>; +<%= render('jni/method_prototype.erb', { method: JNIHelper.compareMethod(entity), maybe_unused: false }) %>; <% end -%> diff --git a/codegen/lib/templates/kotlin/android_class.erb b/codegen/lib/templates/kotlin/android_class.erb new file mode 100644 index 00000000000..e853300300c --- /dev/null +++ b/codegen/lib/templates/kotlin/android_class.erb @@ -0,0 +1,72 @@ +<%= 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() +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + GenericPhantomReference.register(this, nativeHandle, ::delete) +<% end -%> + } +<%# 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) + +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + @JvmStatic + @JvmName("delete") + private external fun delete(handle: Long) +<%- end -%> +<%- 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..f782f3f8dc8 --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_class.erb @@ -0,0 +1,89 @@ +<%= render('kotlin/package.erb') %> + +import cnames.structs.TW<%= entity.name %> +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.toCValues + +<% 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') } -%> +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +actual class <%= entity.name %> constructor( + val pointer: CPointer>, +) { +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + @OptIn(kotlin.experimental.ExperimentalNativeApi::class) + private val cleaner = kotlin.native.ref.createCleaner(pointer) { ptr -> + TW<%= entity.name %>Delete(ptr) + } +<% end -%> +<%# Constructors -%> +<%- constructors.each do |constructor| -%> + +<% if constructor.return_type.is_nullable -%> + @Throws(IllegalArgumentException::class) +<% end -%> + actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this( + wrapperTW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.arguments(constructor.parameters) %>) + ) +<% 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) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method }) -%> + val result = <%= 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))})") %> +<%= render('kotlin/ios_parameter_release.erb', { method: method }) -%> + return result + } +<% 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 { +<%# Constructor wrappers -%> +<% if constructors.any? -%> +<% constructors.each do |constructor| -%> + +<% if constructor.return_type.is_nullable -%> + @Throws(IllegalArgumentException::class) +<% end -%> + private fun wrapperTW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.parameters(constructor.parameters) %>): CPointer> { +<%= render('kotlin/ios_parameter_access.erb', { method: constructor, more_index: 4 }) -%> + val result = TW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.calling_parameters_ios(constructor.parameters) %>) +<%= render('kotlin/ios_parameter_release.erb', { method: constructor, more_index: 4 }) -%> +<% if constructor.return_type.is_nullable -%> + if (result == null) { + throw IllegalArgumentException() + } + return result +<% else -%> + return result!! +<% end -%> + } +<% end -%> +<% end -%> +<%# 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) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method, more_index: 4 }) -%> + val result = <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> +<%= render('kotlin/ios_parameter_release.erb', { method: method, more_index: 4 }) -%> + return result + } +<% 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..28e0efe0ef7 --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_enum.erb @@ -0,0 +1,68 @@ +<%= 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}" -%> +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +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) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method }) -%> + val result = 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) %> +<%= render('kotlin/ios_parameter_release.erb', { method: method }) -%> + return result + } +<%- 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_parameter_access.erb b/codegen/lib/templates/kotlin/ios_parameter_access.erb new file mode 100644 index 00000000000..b16ce477107 --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_parameter_access.erb @@ -0,0 +1,27 @@ +<% + method = locals[:method] + more_index = locals[:more_index] || 0 + + method.parameters.each do |param| -%> +<% if param.type.name == :data -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>Data = if (<%= KotlinHelper.fix_name(param.name) %> == null) { +<%= ' ' * more_index %> null +<%= ' ' * more_index %> } else { +<%= ' ' * more_index %> TWDataCreateWithBytes(<%= KotlinHelper.fix_name(param.name) %>.toUByteArray().toCValues(), <%= KotlinHelper.fix_name(param.name) %>.size.toULong()) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>Data = TWDataCreateWithBytes(<%= KotlinHelper.fix_name(param.name) %>.toUByteArray().toCValues(), <%= KotlinHelper.fix_name(param.name) %>.size.toULong()) +<% end -%> +<% elsif param.type.name == :string -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>String = if (<%= KotlinHelper.fix_name(param.name) %> == null) { +<%= ' ' * more_index %> null +<%= ' ' * more_index %> } else { +<%= ' ' * more_index %> TWStringCreateWithUTF8Bytes(<%= KotlinHelper.fix_name(param.name) %>) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>String = TWStringCreateWithUTF8Bytes(<%= KotlinHelper.fix_name(param.name) %>) +<% end -%> +<% end -%> +<% end -%> diff --git a/codegen/lib/templates/kotlin/ios_parameter_release.erb b/codegen/lib/templates/kotlin/ios_parameter_release.erb new file mode 100644 index 00000000000..8f90bb6aeaf --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_parameter_release.erb @@ -0,0 +1,23 @@ +<% + method = locals[:method] + more_index = locals[:more_index] || 0 + + method.parameters.each do |param| -%> +<% if param.type.name == :data -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> if (<%= KotlinHelper.fix_name(param.name) %>Data != null) { +<%= ' ' * more_index %> TWDataDelete(<%= KotlinHelper.fix_name(param.name) %>Data) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> TWDataDelete(<%= KotlinHelper.fix_name(param.name) %>Data) +<% end -%> +<% elsif param.type.name == :string -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> if (<%= KotlinHelper.fix_name(param.name) %>String != null) { +<%= ' ' * more_index %> TWStringDelete(<%= KotlinHelper.fix_name(param.name) %>String) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> TWStringDelete(<%= KotlinHelper.fix_name(param.name) %>String) +<% end -%> +<% 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..7092e86757a --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_struct.erb @@ -0,0 +1,23 @@ +<%= render('kotlin/package.erb') %> + +import kotlinx.cinterop.toCValues + +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +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) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method }) -%> + val result = <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> +<%= render('kotlin/ios_parameter_release.erb', { method: method }) -%> + return result + } +<% 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..3e3577d4ead --- /dev/null +++ b/codegen/lib/templates/kotlin/js_accessors_class.erb @@ -0,0 +1,26 @@ +@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 -%> +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + fun delete() +<% 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..5241d394cb5 --- /dev/null +++ b/codegen/lib/templates/kotlin/js_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 %> constructor( + val jsValue: Js<%= entity.name %>, +) { +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + private val finalizationRegistry = + js( + """ + new FinalizationRegistry(function(heldValue) { + heldValue.delete(); + }) + """ + ) + + init { + finalizationRegistry.register(this, jsValue) + } +<% end -%> + +<%# 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..cd2d3dc5ab5 --- /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, maybe_unused: false }) %> { +<%= 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..646e6c56523 --- /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, maybe_unused: false }) %> { +<% 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..b46d90ef285 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/method_prototype.erb @@ -0,0 +1,16 @@ +<% + method = locals[:method] + maybe_unused = locals[:maybe_unused] + + maybe_unused_str = '' + if maybe_unused + maybe_unused_str = '[[maybe_unused]] ' + end + + if method.static + parameters = maybe_unused_str + 'jclass thisClass' + KotlinJniHelper.parameters(method.parameters) + else + parameters = maybe_unused_str + 'jobject thisObject' + KotlinJniHelper.parameters(method.parameters.drop(1)) + end +-%> +<%= KotlinJniHelper.type(method.return_type) %> JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method) %>(<%= maybe_unused_str %>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..790b3115d87 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/parameter_access.erb @@ -0,0 +1,39 @@ +<% + 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.is_nullable && (KotlinJniHelper.type(param.type) == 'jobject' || KotlinJniHelper.type(param.type) == 'jstring' || KotlinJniHelper.type(param.type) == 'jbyteArray') + # In case of constructor (starts with Create), it always returns jlong type. + if method.name.start_with?('Create') -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'void' -%> + JNI_CHECK_NULL_AND_RETURN_VOID(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'jbyteArray' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'jstring' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'jobject' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% else -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% end -%> +<% end -%> +<% 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..3e875035193 --- /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, maybe_unused: false }) %>; + +<% 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, maybe_unused: false }) %>; + +<% end -%> +<%# Property declarations -%> +<% entity.properties.each do |property| -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; + +<% end -%> +<%# Method declarations -%> +<% entity.methods.each do |method| -%> +<% next if method.name.start_with?('Delete') -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; + +<% 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), maybe_unused: false }) %>; + +<% 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 3398f196bf8..ea1c354db0e 100644 --- a/codegen/lib/templates/newcoin/Address.cpp.erb +++ b/codegen/lib/templates/newcoin/Address.cpp.erb @@ -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 "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 4100ee9b712..7fe392b92df 100644 --- a/codegen/lib/templates/newcoin/Address.h.erb +++ b/codegen/lib/templates/newcoin/Address.h.erb @@ -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 "../Data.h" -#include "../PublicKey.h" +#include "Data.h" +#include "PublicKey.h" #include @@ -36,8 +34,3 @@ inline bool operator==(const Address& lhs, const Address& rhs) { } } // namespace TW::<%= format_name(coin) %> - -/// Wrapper for C interface. -struct TW<%= format_name(coin) %>Address { - TW::<%= format_name(coin) %>::Address impl; -}; diff --git a/codegen/lib/templates/newcoin/AddressTests.cpp.erb b/codegen/lib/templates/newcoin/AddressTests.cpp.erb index 0b642ee1610..27c4c09ab89 100644 --- a/codegen/lib/templates/newcoin/AddressTests.cpp.erb +++ b/codegen/lib/templates/newcoin/AddressTests.cpp.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. #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__")); @@ -37,7 +34,7 @@ TEST(<%= format_name(coin) %>Address, FromPrivateKey) { TEST(<%= format_name(coin) %>Address, FromPublicKey) { // TODO: Check public key type, finalize implementation - auto publicKey = PublicKey(parse_hex("__PUBLIC_KEY_DATS__"), TWPublicKeyTypeED25519); + auto publicKey = PublicKey(parse_hex("__PUBLIC_KEY_DATA__"), TWPublicKeyTypeED25519); auto address = Address(publicKey); ASSERT_EQ(address.string(), "__ADD_RESULTING_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 d668f7fbee0..20bbd774cc9 100644 --- a/codegen/lib/templates/newcoin/AddressTests.kt.erb +++ b/codegen/lib/templates/newcoin/AddressTests.kt.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. 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 5a1ac2e2ce4..793f5bf8f3f 100644 --- a/codegen/lib/templates/newcoin/Entry.cpp.erb +++ b/codegen/lib/templates/newcoin/Entry.cpp.erb @@ -1,27 +1,34 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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 84c388e2490..32b48e0e42c 100644 --- a/codegen/lib/templates/newcoin/Entry.h.erb +++ b/codegen/lib/templates/newcoin/Entry.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. #pragma once @@ -12,14 +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 const std::vector coinTypes() const { return {TWCoinType<%= format_name(coin) %>}; } - 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 76dca97ef4b..bbd242d66be 100644 --- a/codegen/lib/templates/newcoin/Proto.erb +++ b/codegen/lib/templates/newcoin/Proto.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. syntax = "proto3"; diff --git a/codegen/lib/templates/newcoin/Signer.cpp.erb b/codegen/lib/templates/newcoin/Signer.cpp.erb index 2e7f8631d3f..aeedceda863 100644 --- a/codegen/lib/templates/newcoin/Signer.cpp.erb +++ b/codegen/lib/templates/newcoin/Signer.cpp.erb @@ -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 "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 b5060856b55..f77982f3ac3 100644 --- a/codegen/lib/templates/newcoin/Signer.h.erb +++ b/codegen/lib/templates/newcoin/Signer.h.erb @@ -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/<%= name %>.pb.h" @@ -19,12 +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 %> - -/// Wrapper for C interface. -struct TW<%= name %>Signer { - TW::<%= name %>::Signer impl; -}; diff --git a/codegen/lib/templates/newcoin/SignerTests.cpp.erb b/codegen/lib/templates/newcoin/SignerTests.cpp.erb index 4c0fd8ecea0..d88ad46994f 100644 --- a/codegen/lib/templates/newcoin/SignerTests.cpp.erb +++ b/codegen/lib/templates/newcoin/SignerTests.cpp.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. #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 4b95c270b5b..4add9add89f 100644 --- a/codegen/lib/templates/newcoin/SignerTests.kt.erb +++ b/codegen/lib/templates/newcoin/SignerTests.kt.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. 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 c93c49474ef..91a0cbf0729 100644 --- a/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb +++ b/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb @@ -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 #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; @@ -16,4 +14,11 @@ using namespace TW; TEST(TW<%= name %>, Address) { // TODO: Finalize test implementation + + auto string = STRING("__ADD_VALID_ADDRESS_HERE__"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinType<%= format_name(coin) %>)); + 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/lib/templates/newcoin/TWSignerTests.cpp.erb b/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb index ed0588e7e28..8e8193db07c 100644 --- a/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb +++ b/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb @@ -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 #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 08940c8c871..e28d0ec1bc2 100644 --- a/codegen/lib/templates/newcoin/Tests.swift.erb +++ b/codegen/lib/templates/newcoin/Tests.swift.erb @@ -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. -import TrustWalletCore +import WalletCore import XCTest class <%= name %>Tests: XCTestCase { 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/registry.md.erb b/codegen/lib/templates/registry.md.erb new file mode 100644 index 00000000000..e642b3bc7cd --- /dev/null +++ b/codegen/lib/templates/registry.md.erb @@ -0,0 +1,9 @@ +# Full list + +This list is generated from [./registry.json](../registry.json) + +| Index | Name | Symbol | Logo | URL | +| ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +<% coins.select{ |c| c['deprecated'].nil? }.each do |coin| -%> +| <%= coin['coinId'].to_s.ljust(7, " ") %> | <%= coin_name(coin).ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | +<% end -%> 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 4d090e7bc70..3404e02778d 100644 --- a/codegen/lib/templates/swift/class.erb +++ b/codegen/lib/templates/swift/class.erb @@ -1,9 +1,11 @@ import Foundation <% protocols = SwiftHelper.protocol(entity) -%> +<%= entity.comment %> public final class <%= entity.name %><% unless protocols.empty? %>: <%= protocols.join(', ') %><% end %> { <%# Static properties -%> <% entity.static_properties.each do |property| -%> +<%= 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 c36feb40cb7..25c9a7c4c9d 100644 --- a/codegen/lib/templates/swift/class_properties.erb +++ b/codegen/lib/templates/swift/class_properties.erb @@ -1,5 +1,6 @@ <%# Properties -%> <%- entity.properties.each do |property| -%> +<%= 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 e54c283f148..6c1165edab5 100644 --- a/codegen/lib/templates/swift/enum.erb +++ b/codegen/lib/templates/swift/enum.erb @@ -1,4 +1,5 @@ <% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<%= 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 133a6028953..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_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/header.erb b/codegen/lib/templates/swift/header.erb deleted file mode 100644 index 0770a18cd04..00000000000 --- a/codegen/lib/templates/swift/header.erb +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 WILL BE LOST. -// diff --git a/codegen/lib/templates/swift/method.erb b/codegen/lib/templates/swift/method.erb index d50b6a0629a..d9700123165 100644 --- a/codegen/lib/templates/swift/method.erb +++ b/codegen/lib/templates/swift/method.erb @@ -1,4 +1,5 @@ <% method = locals[:method] -%> +<%= 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 5a9638220cf..8ecd73229be 100644 --- a/codegen/lib/templates/swift/static_method.erb +++ b/codegen/lib/templates/swift/static_method.erb @@ -1,4 +1,5 @@ <% method = locals[:method] -%> +<%= 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 e7c3b2ca7ea..518588bd0ac 100644 --- a/codegen/lib/templates/swift/struct.erb +++ b/codegen/lib/templates/swift/struct.erb @@ -1,9 +1,11 @@ import Foundation <% protocols = SwiftHelper.protocol(entity) -%> +<%= entity.comment %> public struct <%= entity.name %><% unless protocols.empty? %>: <%= protocols.join(', ') %><% end %> { <%# Static properties -%> <% entity.static_properties.each do |property| -%> +<%= 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 bc45586f450..10d896df1e4 100644 --- a/codegen/lib/templates/swift/struct_properties.erb +++ b/codegen/lib/templates/swift/struct_properties.erb @@ -1,14 +1,13 @@ - var rawValue: TW<%= entity.name %> <%# Properties -%> <%- entity.properties.each do |property| -%> +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property }) -%> } <%- end -%> - init(rawValue: TW<%= entity.name %>) { - self.rawValue = rawValue + init() { } <%# Initializers -%> @@ -31,4 +30,4 @@ <% end -%> } -<% end -%> \ No newline at end of file +<% end -%> diff --git a/codegen/lib/templates/ts/class_d.erb b/codegen/lib/templates/ts/class_d.erb new file mode 100644 index 00000000000..87591ca7978 --- /dev/null +++ b/codegen/lib/templates/ts/class_d.erb @@ -0,0 +1,22 @@ +export class <%= entity.name %> { +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> + static <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= TsHelper.parameters(method.parameters) %>): <%= TsHelper.type(method.return_type) %>; +<% end -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> + static <%= WasmCppHelper.function_name(entity: entity, function: method) %>(<%= TsHelper.parameters(method.parameters) %>): <%= TsHelper.type(method.return_type) %>; +<% end -%> +<%- entity.properties.each do |property| -%> + <%= WasmCppHelper.format_name(property.name) %>(): <%= TsHelper.type(property.return_type) %>; +<%- end -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> + <%= WasmCppHelper.format_name(method.name) %>(<%= TsHelper.parameters(method.parameters.drop(1)) %>): <%= TsHelper.type(method.return_type) %>; +<% end -%> +<% if not entity.is_struct -%> +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + delete(): void; +<% end -%> +<% end -%> +} diff --git a/codegen/lib/templates/ts/enum_d.erb b/codegen/lib/templates/ts/enum_d.erb new file mode 100644 index 00000000000..18b5b2c973d --- /dev/null +++ b/codegen/lib/templates/ts/enum_d.erb @@ -0,0 +1,12 @@ +export class <%= entity.name %> { + value: number; +<% entity.cases.each do |c| -%> + static <%= WasmCppHelper.format_name(c.name) %>: <%= entity.name %>; +<% end -%> +} +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<% if has_string -%> + +declare function describe<%= entity.name %>(value: <%= entity.name %>): string; + +<% end -%> diff --git a/codegen/lib/templates/wasm_cpp.erb b/codegen/lib/templates/wasm_cpp.erb new file mode 100644 index 00000000000..2d3338e94fb --- /dev/null +++ b/codegen/lib/templates/wasm_cpp.erb @@ -0,0 +1,7 @@ +<% if entity.is_a?(EnumDecl) -%> +<%= render('cpp/includes.erb') -%> + +<%= render('cpp/enum.erb') -%> +<% else -%> +<%= render('cpp/class.erb') -%> +<% end -%> diff --git a/codegen/lib/templates/wasm_d_ts.erb b/codegen/lib/templates/wasm_d_ts.erb new file mode 100644 index 00000000000..7600b54197b --- /dev/null +++ b/codegen/lib/templates/wasm_d_ts.erb @@ -0,0 +1,5 @@ +<% if entity.is_a?(EnumDecl) -%> +<%= render('ts/enum_d.erb') -%> +<% else -%> +<%= render('ts/class_d.erb') -%> +<% end -%> diff --git a/codegen/lib/templates/wasm_h.erb b/codegen/lib/templates/wasm_h.erb new file mode 100644 index 00000000000..38215a86f2b --- /dev/null +++ b/codegen/lib/templates/wasm_h.erb @@ -0,0 +1,7 @@ +#pragma once + +<%= render('cpp/includes.erb') -%> + +<% if not entity.is_a?(EnumDecl) -%> +<%= render('cpp/header.erb') -%> +<% end -%> diff --git a/codegen/lib/ts_helper.rb b/codegen/lib/ts_helper.rb new file mode 100644 index 00000000000..a75027b8999 --- /dev/null +++ b/codegen/lib/ts_helper.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module TsHelper + def self.parameters(params) + names = params.map do |param| + type = type(param.type) + if param.type.name == :data + type += ' | Buffer' + end + "#{param.name}: #{type}" + end + names.join(', ') + end + + def self.primitive_type(t) + case t.name + when :bool + 'boolean' + when :int, :uint8, :uint16, :uint32, :uint64, :size + 'number' + when :data + 'Uint8Array | Buffer' + when :string + 'string' + else + raise "Invalid type #{t.name}" + end + end + + def self.type(t) + case t.name + when :void + 'void' + when :bool + 'boolean' + when :int, :uint8, :int8, :uint16, :int16, :uint32, :int32, :uint64, :int64, :size + 'number' + when :data + 'Uint8Array' + when :string + 'string' + else + t.name + end + end + + 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(combined_path, 'w') + # append header + combined.write(File.read(header)) + + # append .d.ts in src + Dir.glob("#{wasm_src}/src/*.d.ts").each do |file| + combined.write(File.read(file)) + end + + # append .d.ts in generated + Dir.glob("#{wasm_src}/lib/generated/*.d.ts").each do |file| + combined.write(File.read(file)) + 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/lib/wasm_cpp_helper.rb b/codegen/lib/wasm_cpp_helper.rb new file mode 100644 index 00000000000..98ba4b07b46 --- /dev/null +++ b/codegen/lib/wasm_cpp_helper.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module WasmCppHelper + # Transforms an interface name to a cpp method name + def self.format_name(name) + result = name + match = /^([A-Z]+)/.match(name) + result = name.sub(match[1], match[1].downcase) unless match.nil? + result + end + + # Transforms a method/property name to a cpp function name + + def self.class_name(entity:) + "Wasm" + entity.name + end + + def self.function_name(entity:, function:) + "#{format_name(function.name)}" + end + + def self.parameters(params) + names = params.map do |param| + "#{type(param.type)} #{param.name}" + end + names.join(', ') + end + + def self.arguments(params) + params.map do |param| + if param.type.name == :data + "&#{param.name}Data" + elsif param.type.name == :string + '&' + param.name + elsif param.type.is_struct || param.type.is_class + param.name + '->instance' + else + param.name + end + end + end + + def self.primitive_type(t) + case t.name + when :bool + 'bool' + when :int + 'int' + when :uint8 + 'uint8_t' + when :size + 'size_t' + when :uint16 + 'uint16_t' + when :uint32 + 'uint32_t' + when :uint64 + 'uint64_t' + when :string + 'std::string' + else + raise "Invalid type #{t.name}" + end + end + + def self.type(t) + case t.name + when :void + 'void' + when :bool + 'bool' + when :int + 'int' + when :uint8 + 'uint8_t' + when :uint16 + 'uint16_t' + when :uint32 + 'uint32_t' + when :uint64 + 'uint64_t' + when :int8 + 'int8_t' + when :int16 + 'int16_t' + when :int32 + 'int32_t' + when :int64 + 'int64_t' + when :size + 'size_t' + when :data + 'const std::string&' + when :string + 'const std::string&' + else + if t.is_enum + "TW#{t.name}" + elsif t.is_struct || t.is_class + "Wasm#{t.name}*" + else + 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/test/test_jni_helper.rb b/codegen/test/test_jni_helper.rb index 766c8dfdcf7..595ff1e53e2 100644 --- a/codegen/test/test_jni_helper.rb +++ b/codegen/test/test_jni_helper.rb @@ -8,7 +8,7 @@ def test_format_name end def test_function_name - entity = EntityDecl.new(name: 'Test', is_struct: false) + entity = EntityDecl.new(name: 'Test', is_struct: false, comment: '') method = FunctionDecl.new(name: 'Function', entity: entity, is_method: true) name = JNIHelper.function_name(entity: entity, function: method) assert_equal(name, 'Java_wallet_core_jni_Test_function') diff --git a/codegen/test/test_parser.rb b/codegen/test/test_parser.rb index 6f51a726695..7ec0af335e8 100644 --- a/codegen/test/test_parser.rb +++ b/codegen/test/test_parser.rb @@ -44,22 +44,26 @@ def test_parse_invalid_method def test_parse_method_discardable_result parser = Parser.new(path: '', string: ' + // This is a sample file TW_EXTERN_C_BEGIN struct TWEthereumAbiFunction; + // Ethereuem ABI helpers TW_EXPORT_CLASS struct TWEthereumAbiEncoder; - /// Encode function to Eth ABI binary + // Encode function to Eth ABI binary TW_EXPORT_STATIC_METHOD TW_METHOD_DISCARDABLE_RESULT TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnull func_in); TW_EXTERN_C_END ') parser.parse + assert_equal(parser.entity.name, 'EthereumAbiEncoder') method = parser.entity.static_methods[0] 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') end def test_init diff --git a/coins.json b/coins.json deleted file mode 100644 index 774cfbcee1d..00000000000 --- a/coins.json +++ /dev/null @@ -1,1572 +0,0 @@ -[ - { - "id": "bitcoin", - "name": "Bitcoin", - "coinId": 0, - "symbol": "BTC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/0'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 0, - "p2shPrefix": 5, - "hrp": "bc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/bitcoin/transaction/", - "accountPath": "/bitcoin/address/", - "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", - "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" - }, - "info": { - "url": "https://bitcoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "litecoin", - "name": "Litecoin", - "coinId": 2, - "symbol": "LTC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/2'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 48, - "p2shPrefix": 50, - "hrp": "ltc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/litecoin/transaction/", - "accountPath": "/litecoin/address/" - }, - "info": { - "url": "https://litecoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "doge", - "name": "Dogecoin", - "coinId": 3, - "symbol": "DOGE", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/3'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 30, - "p2shPrefix": 22, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "dgub", - "xprv": "dgpv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/dogecoin/transaction/", - "accountPath": "/dogecoin/address/" - }, - "info": { - "url": "https://dogecoin.com", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "dash", - "name": "Dash", - "coinId": 5, - "symbol": "DASH", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/5'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 76, - "p2shPrefix": 16, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/dash/transaction/", - "accountPath": "/dash/address/" - }, - "info": { - "url": "https://dash.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "viacoin", - "name": "Viacoin", - "coinId": 14, - "symbol": "VIA", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/14'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 71, - "p2shPrefix": 33, - "hrp": "via", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://explorer.viacoin.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://viacoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "groestlcoin", - "name": "Groestlcoin", - "coinId": 17, - "symbol": "GRS", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/17'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 36, - "p2shPrefix": 5, - "hrp": "grs", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "groestl512d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/groestlcoin/transaction/", - "accountPath": "/groestlcoin/address/" - }, - "info": { - "url": "https://www.groestlcoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "digibyte", - "name": "DigiByte", - "coinId": 20, - "symbol": "DGB", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/20'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 30, - "p2shPrefix": 63, - "hrp": "dgb", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://digiexplorer.info", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://www.digibyte.io", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "monacoin", - "name": "Monacoin", - "coinId": 22, - "symbol": "MONA", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/22'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 50, - "p2shPrefix": 55, - "hrp": "mona", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockbook.electrum-mona.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://monacoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "decred", - "name": "Decred", - "coinId": 42, - "symbol": "DCR", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/42'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 7, - "p2pkhPrefix": 63, - "p2shPrefix": 26, - "publicKeyHasher": "blake256ripemd", - "base58Hasher": "blake256d", - "xpub": "dpub", - "xprv": "dprv", - "explorer": { - "url": "https://dcrdata.decred.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://decred.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "ethereum", - "name": "Ethereum", - "coinId": 60, - "symbol": "ETH", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://etherscan.io", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f", - "sampleAccount": "0x5bb497e8d9fe26e92dd1be01e32076c8e024d167" - }, - "info": { - "url": "https://ethereum.org", - "client": "https://github.com/ethereum/go-ethereum", - "clientPublic": "https://mainnet.infura.io", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "classic", - "name": "Ethereum Classic", - "coinId": 61, - "symbol": "ETC", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/61'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://blockscout.com/etc/mainnet", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://ethereumclassic.org", - "client": "https://github.com/ethereumclassic/go-ethereum", - "clientPublic": "https://www.ethercluster.com/etc", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "icon", - "name": "ICON", - "coinId": 74, - "symbol": "ICX", - "decimals": 18, - "blockchain": "Icon", - "derivationPath": "m/44'/74'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://tracker.icon.foundation", - "txPath": "/transaction/", - "accountPath": "/address/" - }, - "info": { - "url": "https://icon.foundation", - "client": "https://github.com/icon-project/icon-rpc-server", - "clientPublic": "http://ctz.icxstation.com:9000/api/v3", - "clientDocs": "https://www.icondev.io/docs/icon-json-rpc-v3" - } - }, - { - "id": "cosmos", - "name": "Cosmos", - "coinId": 118, - "symbol": "ATOM", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/118'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "cosmos", - "explorer": { - "url": "https://www.mintscan.io", - "txPath": "/txs/", - "accountPath": "/account/" - }, - "info": { - "url": "https://cosmos.network", - "client": "https://github.com/cosmos/cosmos-sdk", - "clientPublic": "https://stargate.cosmos.network", - "clientDocs": "https://cosmos.network/rpc" - } - }, - { - "id": "zcash", - "name": "Zcash", - "coinId": 133, - "symbol": "ZEC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/133'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 28, - "p2pkhPrefix": 184, - "p2shPrefix": 189, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockchair.com/zcash", - "txPath": "/transaction/", - "accountPath": "/address/" - }, - "info": { - "url": "https://z.cash", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "zcoin", - "name": "Zcoin", - "displayName": "Firo", - "coinId": 136, - "symbol": "FIRO", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/136'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 82, - "p2shPrefix": 7, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://explorer.firo.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://firo.org/", - "client": "https://github.com/firoorg/firo", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "ripple", - "name": "XRP", - "coinId": 144, - "symbol": "XRP", - "decimals": 6, - "blockchain": "Ripple", - "derivationPath": "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", - "client": "https://github.com/ripple/rippled", - "clientPublic": "https://s2.ripple.com:51234", - "clientDocs": "https://xrpl.org/rippled-api.html" - } - }, - { - "id": "bitcoincash", - "name": "Bitcoin Cash", - "coinId": 145, - "symbol": "BCH", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/145'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 0, - "p2shPrefix": 5, - "hrp": "bitcoincash", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/bitcoin-cash/transaction/", - "accountPath": "/bitcoin-cash/address/" - }, - "info": { - "url": "https://bitcoincash.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "stellar", - "name": "Stellar", - "coinId": 148, - "symbol": "XLM", - "decimals": 7, - "blockchain": "Stellar", - "derivationPath": "m/44'/148'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://blockchair.com/stellar", - "txPath": "/transaction/", - "accountPath": "/account/" - }, - "info": { - "url": "https://stellar.org", - "client": "https://github.com/stellar/go", - "clientPublic": "https://horizon.stellar.org", - "clientDocs": "https://www.stellar.org/developers/horizon/reference" - } - }, - { - "id": "bitcoingold", - "name": "Bitcoin Gold", - "coinId": 156, - "symbol": "BTG", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/156'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 38, - "p2shPrefix": 23, - "hrp": "btg", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://explorer.bitcoingold.org/insight", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://bitcoingold.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "nano", - "name": "Nano", - "coinId": 165, - "symbol": "NANO", - "decimals": 30, - "blockchain": "Nano", - "derivationPath": "m/44'/165'/0'", - "curve": "ed25519Blake2bNano", - "publicKeyType": "ed25519Blake2b", - "url": "https://nano.org", - "explorer": { - "url": "https://nanocrawler.cc", - "txPath": "/explorer/block/", - "accountPath": "/explorer/account/", - "sampleTx": "C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F", - "sampleAccount": "nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf" - }, - "info": { - "url": "https://nano.org", - "client": "https://github.com/nanocurrency/nano-node", - "clientPublic": "", - "clientDocs": "https://docs.nano.org/commands/rpc-protocol/" - } - }, - { - "id": "ravencoin", - "name": "Ravencoin", - "coinId": 175, - "symbol": "RVN", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/175'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 60, - "p2shPrefix": 122, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://ravencoin.network", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://ravencoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "poa", - "name": "POA Network", - "coinId": 178, - "symbol": "POA", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/178'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://blockscout.com", - "txPath": "/poa/core/tx/", - "accountPath": "/poa/core/address/" - }, - "info": { - "url": "https://poa.network", - "client": "https://github.com/poanetwork/parity-ethereum", - "clientPublic": "https://core.poa.network", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "eos", - "name": "EOS", - "coinId": 194, - "symbol": "EOS", - "decimals": 4, - "blockchain": "EOS", - "derivationPath": "m/44'/194'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "explorer": { - "url": "https://bloks.io", - "txPath": "/transaction/", - "accountPath": "/account/" - }, - "info": { - "url": "http://eos.io", - "client": "https://github.com/eosio/eos", - "clientPublic": "", - "clientDocs": "https://developers.eos.io/eosio-nodeos/reference" - } - }, - { - "id": "tron", - "name": "Tron", - "coinId": 195, - "symbol": "TRX", - "decimals": 6, - "blockchain": "Tron", - "derivationPath": "m/44'/195'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://tronscan.org", - "txPath": "/#/transaction/", - "accountPath": "/#/address/" - }, - "info": { - "url": "https://tron.network", - "client": "https://github.com/tronprotocol/java-tron", - "clientPublic": "https://api.trongrid.io", - "clientDocs": "https://developers.tron.network/docs/tron-wallet-rpc-api" - } - }, - { - "id": "fio", - "name": "FIO", - "coinId": 235, - "symbol": "FIO", - "decimals": 9, - "blockchain": "FIO", - "derivationPath": "m/44'/235'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "url": "https://fioprotocol.io/", - "explorer": { - "url": "https://explorer.fioprotocol.io", - "txPath": "/transaction/", - "accountPath": "/account/" - }, - "info": { - "url": "https://fioprotocol.io", - "client": "https://github.com/fioprotocol/fio", - "clientPublic": "https://mainnet.fioprotocol.io", - "clientDocs": "https://developers.fioprotocol.io" - } - }, - { - "id": "nimiq", - "name": "Nimiq", - "coinId": 242, - "symbol": "NIM", - "decimals": 5, - "blockchain": "Nimiq", - "derivationPath": "m/44'/242'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://nimiq.watch", - "txPath": "/#", - "accountPath": "/#" - }, - "info": { - "url": "https://nimiq.com", - "client": "https://github.com/nimiq/core-rs", - "clientPublic": "", - "clientDocs": "https://github.com/nimiq/core-js/wiki/JSON-RPC-API" - } - }, - { - "id": "algorand", - "name": "Algorand", - "coinId": 283, - "symbol": "ALGO", - "decimals": 6, - "blockchain": "Algorand", - "derivationPath": "m/44'/283'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://algoexplorer.io", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A", - "sampleAccount": "J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM" - }, - "info": { - "url": "https://www.algorand.com/", - "client": "https://github.com/algorand/go-algorand", - "clientPublic": "https://indexer.algorand.network", - "clientDocs": "https://developer.algorand.org/docs/algod-rest-paths" - } - }, - { - "id": "iotex", - "name": "IoTeX", - "coinId": 304, - "symbol": "IOTX", - "decimals": 18, - "blockchain": "IoTeX", - "derivationPath": "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", - "client": "https://github.com/iotexproject/iotex-core", - "clientPublic": "", - "clientDocs": "https://docs.iotex.io/#api" - } - }, - { - "id": "zilliqa", - "name": "Zilliqa", - "coinId": 313, - "symbol": "ZIL", - "decimals": 12, - "blockchain": "Zilliqa", - "derivationPath": "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", - "client": "https://github.com/Zilliqa/Zilliqa", - "clientPublic": "https://api.zilliqa.com", - "clientDocs": "https://apidocs.zilliqa.com" - } - }, - { - "id": "terra", - "name": "Terra", - "coinId": 330, - "symbol": "LUNA", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/330'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "terra", - "explorer": { - "url": "https://terra.stake.id", - "txPath": "/#/tx/", - "accountPath": "/#/address/" - }, - "info": { - "url": "https://terra.money", - "client": "https://github.com/terra-project/core", - "clientPublic": "https://rpc.terra.dev", - "clientDocs": "https://docs.terra.money" - } - }, - { - "id": "polkadot", - "name": "Polkadot", - "coinId": 354, - "symbol": "DOT", - "decimals": 10, - "blockchain": "Polkadot", - "derivationPath": "m/44'/354'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://polkadot.subscan.io", - "txPath": "/extrinsic/", - "accountPath": "/account/" - }, - "info": { - "url": "https://polkadot.network/", - "client": "https://github.com/paritytech/polkadot", - "clientPublic": "", - "clientDocs": "https://polkadot.js.org/api/substrate/rpc.html" - } - }, - { - "id": "ton", - "name": "TON", - "coinId": 396, - "symbol": "GRAM", - "decimals": 9, - "blockchain": "TON", - "derivationPath": "m/44'/396'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://test.ton.org", - "txPath": "/testnet/transaction?hash=", - "accountPath": "/testnet/account?account=" - }, - "info": { - "url": "https://test.ton.org", - "client": "https://github.com/ton-blockchain/ton", - "clientPublic": "", - "clientDocs": "https://test.ton.org/" - } - }, - { - "id": "near", - "name": "NEAR", - "coinId": 397, - "symbol": "NEAR", - "decimals": 24, - "blockchain": "NEAR", - "derivationPath": "m/44'/397'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://explorer.near.org", - "txPath": "/transactions/", - "accountPath": "/accounts/" - }, - "info": { - "url": "https://nearprotocol.com", - "client": "https://github.com/nearprotocol/nearcore", - "clientPublic": "https://rpc.nearprotocol.com", - "clientDocs": "https://docs.nearprotocol.com" - } - }, - { - "id": "aion", - "name": "Aion", - "coinId": 425, - "symbol": "AION", - "decimals": 18, - "blockchain": "Aion", - "derivationPath": "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", - "client": "https://github.com/aionnetwork/aion", - "clientPublic": "", - "clientDocs": "https://github.com/aionnetwork/aion/wiki/JSON-RPC-API-Docs" - } - }, - { - "id": "kusama", - "name": "Kusama", - "coinId": 434, - "symbol": "KSM", - "decimals": 12, - "blockchain": "Polkadot", - "derivationPath": "m/44'/434'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://kusama.subscan.io", - "txPath": "/extrinsic/", - "accountPath": "/account/", - "sampleTx": "0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd", - "sampleAccount": "DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk" - }, - "info": { - "url": "https://kusama.network", - "client": "https://github.com/paritytech/polkadot", - "clientPublic": "wss://kusama-rpc.polkadot.io/", - "clientDocs": "https://polkadot.js.org/api/substrate/rpc.html" - } - }, - { - "id": "aeternity", - "name": "Aeternity", - "coinId": 457, - "symbol": "AE", - "decimals": 18, - "blockchain": "Aeternity", - "derivationPath": "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", - "client": "https://github.com/aeternity/aeternity", - "clientPublic": "https://sdk-mainnet.aepps.com", - "clientDocs": "http://aeternity.com/api-docs/" - } - }, - { - "id": "kava", - "name": "Kava", - "coinId": 459, - "symbol": "KAVA", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/459'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "kava", - "explorer": { - "url": "https://kava.mintscan.io", - "txPath": "/txs/", - "accountPath": "/account/", - "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", - "sampleAccount": "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" - }, - "info": { - "url": "https://kava.io", - "client": "https://github.com/kava-labs/kava", - "clientPublic": "https://data.kava.io", - "clientDocs": "https://rpc.kava.io" - } - }, - { - "id": "filecoin", - "name": "Filecoin", - "coinId": 461, - "symbol": "FIL", - "decimals": 18, - "blockchain": "Filecoin", - "derivationPath": "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/", - "client": "https://github.com/filecoin-project/lotus", - "clientPublic": "", - "clientDocs": "https://docs.lotu.sh" - } - }, - { - "id": "band", - "name": "BandChain", - "symbol": "BAND", - "coinId": 494, - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/494'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "band", - "explorer": { - "url": "https://scan-wenchang-testnet2.bandchain.org/", - "txPath": "/tx/", - "accountPath": "/account/", - "sampleTx": "473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173", - "sampleAccount": "band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp" - }, - "info": { - "url": "https://bandprotocol.com/", - "client": "https://github.com/bandprotocol/bandchain", - "clientPublic": "https://api-wt2-lb.bandchain.org", - "clientDocs": "https://docs.bandchain.org/" - } - }, - { - "id": "theta", - "name": "Theta", - "coinId": 500, - "symbol": "THETA", - "decimals": 18, - "blockchain": "Theta", - "derivationPath": "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", - "client": "https://github.com/thetatoken/theta-protocol-ledger", - "clientPublic": "", - "clientDocs": "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", - "derivationPath": "m/44'/501'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://explorer.solana.com", - "txPath": "/transactions/", - "accountPath": "/accounts/" - }, - "info": { - "url": "https://solana.com", - "client": "https://github.com/solana-labs/solana", - "clientPublic": "https://api.mainnet-beta.solana.com", - "clientDocs": "https://docs.solana.com" - } - }, - { - "id": "elrond", - "name": "Elrond", - "coinId": 508, - "symbol": "eGLD", - "decimals": 18, - "blockchain": "ElrondNetwork", - "derivationPath": "m/44'/508'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "hrp": "erd", - "explorer": { - "url": "https://explorer.elrond.com", - "txPath": "/transactions/", - "accountPath": "/address/" - }, - "info": { - "url": "https://elrond.com/", - "client": "https://github.com/ElrondNetwork/elrond-go", - "clientPublic": "https://api.elrond.com", - "clientDocs": "https://docs.elrond.com" - } - }, - { - "id": "binance", - "name": "Binance", - "displayName": "BNB", - "coinId": 714, - "symbol": "BNB", - "decimals": 8, - "blockchain": "Binance", - "derivationPath": "m/44'/714'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "bnb", - "explorer": { - "url": "https://explorer.binance.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB", - "sampleAccount": "bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz" - }, - "info": { - "url": "https://binance.org", - "client": "https://github.com/binance-chain/node-binary", - "clientPublic": "https://dex.binance.org", - "clientDocs": "https://docs.binance.org/api-reference/dex-api/paths.html" - } - }, - { - "id": "vechain", - "name": "VeChain", - "coinId": 818, - "symbol": "VET", - "decimals": 18, - "blockchain": "Vechain", - "derivationPath": "m/44'/818'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explore.vechain.org", - "txPath": "/transactions/", - "accountPath": "/accounts/" - }, - "info": { - "url": "https://vechain.org", - "client": "https://github.com/vechain/thor", - "clientPublic": "", - "clientDocs": "https://doc.vechainworld.io/docs" - } - }, - { - "id": "callisto", - "name": "Callisto", - "coinId": 820, - "symbol": "CLO", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/820'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer2.callisto.network", - "txPath": "/tx/", - "accountPath": "/addr/" - }, - "info": { - "url": "https://callisto.network", - "client": "https://github.com/EthereumCommonwealth/go-callisto", - "clientPublic": "https://clo-geth.0xinfra.com", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "neo", - "name": "NEO", - "coinId": 888, - "symbol": "NEO", - "decimals": 8, - "blockchain": "NEO", - "derivationPath": "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", - "client": "https://github.com/neo-project/neo", - "clientPublic": "http://seed1.ngd.network:10332", - "clientDocs": "https://neo.org/eco" - } - }, - { - "id": "tomochain", - "name": "TomoChain", - "coinId": 889, - "symbol": "TOMO", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/889'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://scan.tomochain.com", - "txPath": "/txs/", - "accountPath": "/address/" - }, - "info": { - "url": "https://tomochain.com", - "client": "https://github.com/tomochain/tomochain", - "clientPublic": "https://rpc.tomochain.com", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "thundertoken", - "name": "Thunder Token", - "coinId": 1001, - "symbol": "TT", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/1001'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://scan.thundercore.com", - "txPath": "/transactions/", - "accountPath": "/address/" - }, - "info": { - "url": "https://thundercore.com", - "client": "https://github.com/thundercore/pala", - "clientPublic": "https://mainnet-rpc.thundercore.com", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "harmony", - "name": "Harmony", - "coinId": 1023, - "symbol": "ONE", - "decimals": 18, - "blockchain": "Harmony", - "derivationPath": "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", - "client": "https://github.com/harmony-one/go-sdk", - "clientPublic": "", - "clientDocs": "https://docs.harmony.one/home/harmony-networks/harmony-network-overview/mainnet" - } - }, - { - "id": "oasis", - "name": "Oasis", - "coinId": 474, - "symbol": "ROSE", - "decimals": 9, - "blockchain": "OasisNetwork", - "derivationPath": "m/44'/474'/0'/0'/0'", - "curve": "ed25519HD", - "publicKeyType": "ed25519", - "hrp": "oasis", - "explorer": { - "url": "https://oasisscan.com", - "txPath": "/transactions/", - "accountPath": "/accounts/detail/", - "sampleTx": "0b9bd4983f1c88a1c71bf33562b6ba02b3064e01697d15a0de4bfe1922ec74b8", - "sampleAccount": "oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4" - }, - "info": { - "url": "https://www.oasislabs.com/", - "client": "https://github.com/oasisprotocol/oasis-core", - "clientPublic": "https://rosetta.oasis.dev/api/", - "clientDocs": "https://github.com/oasisprotocol/oasis-core/blob/master/docs/oasis-node/rpc.md" - } - }, - { - "id": "ontology", - "name": "Ontology", - "coinId": 1024, - "symbol": "ONT", - "decimals": 0, - "blockchain": "Ontology", - "derivationPath": "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", - "client": "https://github.com/ontio/ontology", - "clientPublic": "http://dappnode1.ont.io:20336", - "clientDocs": "https://github.com/ontio/ontology/blob/master/docs/specifications/rpc_api.md" - } - }, - { - "id": "tezos", - "name": "Tezos", - "coinId": 1729, - "symbol": "XTZ", - "decimals": 6, - "blockchain": "Tezos", - "derivationPath": "m/44'/1729'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://tzstats.com", - "txPath": "/", - "accountPath": "/" - }, - "info": { - "url": "https://tezos.com", - "client": "https://gitlab.com/tezos/tezos", - "clientPublic": "https://rpc.tulip.tools/mainnet", - "clientDocs": "https://tezos.gitlab.io/tezos/api/rpc.html" - } - }, - { - "id": "cardano", - "name": "Cardano", - "coinId": 1815, - "symbol": "ADA", - "decimals": 6, - "blockchain": "Cardano", - "derivationPath": "m/1852'/1815'/0'/0/0", - "curve": "ed25519Extended", - "publicKeyType": "ed25519Extended", - "hrp": "addr", - "explorer": { - "url": "https://shelleyexplorer.cardano.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", - "sampleAccount": "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j" - }, - "info": { - "url": "https://www.cardano.org", - "client": "https://github.com/input-output-hk/cardano-sl", - "clientPublic": "", - "clientDocs": "https://cardanodocs.com/introduction/" - } - }, - { - "id": "kin", - "name": "Kin", - "coinId": 2017, - "symbol": "KIN", - "decimals": 5, - "blockchain": "Stellar", - "derivationPath": "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", - "client": "https://github.com/kinecosystem/go", - "clientPublic": "https://horizon.kinfederation.com", - "clientDocs": "https://www.stellar.org/developers/horizon/reference" - }, - "deprecated": true - }, - { - "id": "qtum", - "name": "Qtum", - "coinId": 2301, - "symbol": "QTUM", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/2301'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 58, - "p2shPrefix": 50, - "hrp": "qc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://qtum.info", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://qtum.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "nebulas", - "name": "Nebulas", - "coinId": 2718, - "symbol": "NAS", - "decimals": 18, - "blockchain": "Nebulas", - "derivationPath": "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", - "client": "https://github.com/nebulasio/go-nebulas", - "clientPublic": "https://mainnet.nebulas.io", - "clientDocs": "https://wiki.nebulas.io/en/latest/dapp-development/rpc/rpc.html" - } - }, - { - "id": "gochain", - "name": "GoChain", - "coinId": 6060, - "symbol": "GO", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/6060'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer.gochain.io", - "txPath": "/tx/", - "accountPath": "/addr/" - }, - "info": { - "url": "https://gochain.io", - "client": "https://github.com/gochain-io/gochain", - "clientPublic": "https://rpc.gochain.io", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "nuls", - "name": "NULS", - "coinId": 8964, - "symbol": "NULS", - "decimals": 8, - "blockchain": "NULS", - "derivationPath": "m/44'/8964'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "explorer": { - "url": "https://nulscan.io", - "txPath": "/transaction/info?hash=", - "accountPath": "/address/info?address=" - }, - "info": { - "url": "https://nuls.io", - "client": "https://github.com/nuls-io/nuls-v2", - "clientPublic": "https://public1.nuls.io/", - "clientDocs": "https://docs.nuls.io/" - } - }, - { - "id": "zelcash", - "name": "Zelcash", - "displayName": "Flux", - "coinId": 19167, - "symbol": "FLUX", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/19167'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 28, - "p2pkhPrefix": 184, - "p2shPrefix": 189, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://explorer.runonflux.io", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://runonflux.io", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "https://blockbook.runonflux.io", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } - }, - { - "id": "wanchain", - "name": "Wanchain", - "coinId": 5718350, - "symbol": "WAN", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/5718350'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://www.wanscan.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", - "sampleAccount": "0xc6D3DBf8dF90BA3f957A9634677805eee0e43bBe" - }, - "info": { - "url": "https://wanchain.org", - "client": "https://github.com/wanchain/go-wanchain", - "clientPublic": "", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - }, - { - "id": "waves", - "name": "Waves", - "coinId": 5741564, - "symbol": "WAVES", - "decimals": 8, - "blockchain": "Waves", - "derivationPath": "m/44'/5741564'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "curve25519", - "explorer": { - "url": "https://wavesexplorer.com", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://wavesplatform.com", - "client": "https://github.com/wavesplatform/Waves", - "clientPublic": "https://nodes.wavesnodes.com", - "clientDocs": "https://nodes.wavesnodes.com/api-docs/index.html" - } - }, - { - "id": "bsc", - "name": "Smart Chain Legacy", - "coinId": 10000714, - "slip44": 714, - "symbol": "BNB", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/714'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://bscscan.com", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", - "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" - }, - "info": { - "url": "https://www.binance.org/en/smartChain", - "client": "https://github.com/binance-chain/bsc", - "clientPublic": "https://data-seed-prebsc-1-s1.binance.org:8545", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - }, - "deprecated": true - }, - { - "id": "smartchain", - "name": "Smart Chain", - "coinId": 20000714, - "slip44": 714, - "symbol": "BNB", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://bscscan.com", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", - "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" - }, - "info": { - "url": "https://www.binance.org/en/smartChain", - "client": "https://github.com/binance-chain/bsc", - "clientPublic": "https://bsc-dataseed1.binance.org", - "clientDocs": "https://eth.wiki/json-rpc/API" - } - }, - { - "id": "polygon", - "name": "Polygon", - "coinId": 966, - "symbol": "MATIC", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer.matic.network/", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e", - "sampleAccount": "0x720E1fa107A1Df39Db4E78A3633121ac36Bec132" - }, - "info": { - "url": "https://polygon.technology", - "client": "https://github.com/maticnetwork/contracts", - "clientPublic": "https://rpc-mainnet.matic.network", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } - } -] diff --git a/coverage.stats b/coverage.stats index 717ab40a58e..8b3541cc00d 100644 --- a/coverage.stats +++ b/coverage.stats @@ -1 +1 @@ -95.0 +93.0 diff --git a/docs/coins.md b/docs/coins.md deleted file mode 100644 index aeb5bd39832..00000000000 --- a/docs/coins.md +++ /dev/null @@ -1,68 +0,0 @@ -# Full list - -This list is generated from [./coins.json](../coins.json) - -| Index | Name | Symbol | Logo | URL | -| ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| 0 | Bitcoin | BTC | | | -| 2 | Litecoin | LTC | | | -| 3 | Dogecoin | DOGE | | | -| 5 | Dash | DASH | | | -| 14 | Viacoin | VIA | | | -| 17 | Groestlcoin | GRS | | | -| 20 | DigiByte | DGB | | | -| 22 | Monacoin | MONA | | | -| 42 | Decred | DCR | | | -| 60 | Ethereum | ETH | | | -| 61 | Ethereum Classic | ETC | | | -| 74 | ICON | ICX | | | -| 118 | Cosmos | ATOM | | | -| 133 | Zcash | ZEC | | | -| 136 | Zcoin | FIRO | | | -| 144 | XRP | XRP | | | -| 145 | Bitcoin Cash | BCH | | | -| 148 | Stellar | XLM | | | -| 156 | Bitcoin Gold | BTG | | | -| 165 | Nano | NANO | | | -| 175 | Ravencoin | RVN | | | -| 178 | POA Network | POA | | | -| 194 | EOS | EOS | | | -| 195 | Tron | TRX | | | -| 235 | FIO | FIO | | | -| 242 | Nimiq | NIM | | | -| 283 | Algorand | ALGO | | | -| 304 | IoTeX | IOTX | | | -| 313 | Zilliqa | ZIL | | | -| 330 | Terra | LUNA | | | -| 354 | Polkadot | DOT | | | -| 396 | TON | GRAM | | | -| 397 | NEAR | NEAR | | | -| 425 | Aion | AION | | | -| 434 | Kusama | KSM | | | -| 457 | Aeternity | AE | | | -| 459 | Kava | KAVA | | | -| 461 | Filecoin | FIL | | | -| 474 | Oasis | ROSE | | | -| 494 | BandChain | BAND | | | -| 500 | Theta | THETA | | | -| 501 | Solana | SOL | | | -| 508 | Elrond | eGLD | | | -| 714 | Binance | BNB | | | -| 818 | VeChain | VET | | | -| 820 | Callisto | CLO | | | -| 888 | NEO | NEO | | | -| 889 | TomoChain | TOMO | | | -| 966 | Polygon | MATIC | | | -| 1001 | Thunder Token | TT | | | -| 1023 | Harmony | ONE | | | -| 1024 | Ontology | ONT | | | -| 1729 | Tezos | XTZ | | | -| 1815 | Cardano | ADA | | | -| 2301 | Qtum | QTUM | | | -| 2718 | Nebulas | NAS | | | -| 6060 | GoChain | GO | | | -| 8964 | NULS | NULS | | | -| 19167 | Zelcash | FLUX | | | -| 5718350 | Wanchain | WAN | | | -| 5741564 | Waves | WAVES | | | -| 20000714 | Smart Chain | BNB | | | diff --git a/docs/registry-fields.md b/docs/registry-fields.md new file mode 100644 index 00000000000..ff6b0e539e6 --- /dev/null +++ b/docs/registry-fields.md @@ -0,0 +1,196 @@ +# Documentation for registry.json + +The file `registry.json` contains meta info about supported blockchains. +It is the input for some generated source files and documentation ([registry.md](registry.md)), and values from it are used during runtime. + +## Fields + +**`id`** +Internal ID of the chain. Lowercase letters only. Should be never changed. +Ex.: `'bitcoin'`. + +**`name`** +More readable name, can include lower/uppercase, space. +Ex.: `'Bitcoin'`. + +**`displayName`** +Optional, if present, overrides **name** for places where it is visible to the user. +Ex.: `'BNB Beacon Chain'`. + +**`coinId`** +Internal numerical ID for the chain. In most cases it is the ID used in BIP-44 address derivation. +See quasi-standard repository here: https://github.com/satoshilabs/slips/blob/master/slip-0044.md +Ex.: `0` for Bitcoin, `60` for Ethereum. + +Some typical special cases: + +- Multiple chains/coins with the same ID: adding value `10000000` to distinguish. Possible multiple times. +Ex.: `10000118` for Osmosis, `118` for Cosmos; `20000714` for BNB Smart Chain. +- Ethereum-clone chains with no own BIP-44 ID: use the `10000000 + chainID` as coinID. + +See also: `slip44` and `chainId`. + +**`slip44`** +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`** +Symbol of the native coin. Typically a short, upper-case-only string. +Ex.: `BTC`, `ETH`. + +**`decimals`** +Number of decimals in coin amounts. Amounts are typically expressed as (large) integer values, with all decimal values, like `100000` for `0.00100000` BTC (decimals=8). +Ex.: `8` for Bitcoin, `18` for Ethereum. + +**`blockchain`** +Some chains are very similar and share the implementation code. +This flag is used to direct logic to the right implementation. +Ex. `'Cosmos'` for Oasis, as Oasis is handled by Cosmos implementation. + +**`derivation`** +Defines properties for address derivation, most importantly derivation paths. + +Typically only one derivation is supported per chain, in this case the definition looks like: + +``` + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], +``` + +It contains the derivation path used to derive private key (and address) from the wallet mnemonic (HD wallet). +Derivation path is usually well known for a chain implementation, and different wallet implementations use the same path (so cross-import is possible). +Note that the second number, the BIP-44 ID, usually matches the coinId. + +Some blockchains may support additional alternative derivations. These have: + +- a name +- 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. + +Examples: +Bitcoin uses Segwit address by default, but also supports earlier P2PKH addresses: + +``` + "derivation": [ + { + "name": "segwit", + "path": "m/84'/0'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/0'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], +``` + +Solana supports two derivations, which differ in derivation path: + +``` + "derivation": [ + { + "path": "m/44'/501'/0'" + }, + { + "name": "solana", + "path": "m/44'/501'/0'/0'" + } + ], +``` + +**`xpub` and `xprv`** +Defines the XPub and XPriv format used, Bitcoin-style. Defined inside the derivation section (as they may differ per derivation). + +**`curve`** +Defines the elliptic curve used in private-public key generation and signing. +Ex.: `'secp256k1'` for Bitcoin and Ethereum, `'ed25519'` for Polkadot. + +**`publicKeyType`** +The type of public key used. +Ex.: `'secp256k1'` for Bitcoin, `'secp256k1Extended'` for Ethereum. + +**`staticPrefix`** +Optional byte prefix, used in some Bitcoin-like chains. +Ex.: `7` for Decred. + +**`p2pkhPrefix` and `p2shPrefix`** +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 minimize risk of accidental address mix-up across chains. +Ex. `'bc'` for Bitcoin, `'cosmos'` for Cosmos. + +**`chainId`** +Chain identifier, used by forks, e.g. in case of Ethereum (a decimal number), or Cosomos (a string ID). +Chain identifier, in case of Ethereum it's a constant decimal number; +for Cosmos, it's a dynamic string network id (usually changes with network upgrades). + +Please note the chain id might not be always latest in registry. In transaction building current value has to be supplied each time. + +Ex.: `'1'` for Ethereum, `'61'` for Ethereum Classic, `'osmosis-1'`for Osmosis. + +**`publicKeyHasher`** +Hash method used in XPub derivation. +Default is `sha256ripemd`. +Ex.: `'sha256ripemd'` for Bitcoin, `'blake256ripemd'` for Decred. + +**`base58Hasher`** +Hash method used in extended private and public key derivation, for checksumming within Base58 addresses. +Default is `sha256d`. +Ex.: `'sha256d'` for Bitcoin, `'blake256d'` for Decred. + +**`addressHasher`** +Hash method used in the publicKey -> address generation. +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. + +**`explorer`** +Explorer web service for this chain. Sub-fields are used so that full URLs can be built for any address or transactions. +Note that the sample values should include existing IDs, so that the resulting full URL is valid. + +Example: + +``` + "explorer": { + "url": "https://blockchair.com", + "txPath": "/bitcoin/transaction/", + "accountPath": "/bitcoin/address/", + "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", + "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" + }, +``` + +This results in the full URL for the sample address: +https://blockchair.com/bitcoin/address/17A16QmavnUfCW11DAApiJxp7ARnxN5pGX +which is a working URL. + +Beware of the starting-ending slashes used. + +**`info`** +Section with project info: + +**`info/url`** +Main project website. + +**`info/source`** +Link to the default implementation of the node or RPC gateway that can be used by a wallet. + +**`info/rpc`** +Optional URL to an available public RPC service. + +**`info/documentation`** +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. +Ex. `'true'` for Kin. diff --git a/docs/registry.md b/docs/registry.md new file mode 100644 index 00000000000..2fcea9b71a2 --- /dev/null +++ b/docs/registry.md @@ -0,0 +1,165 @@ +# Full list + +This list is generated from [./registry.json](../registry.json) + +| Index | Name | Symbol | Logo | URL | +| ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| 0 | Bitcoin | BTC | | | +| 2 | Litecoin | LTC | | | +| 3 | Dogecoin | DOGE | | | +| 5 | Dash | DASH | | | +| 14 | Viacoin | VIA | | | +| 17 | Groestlcoin | GRS | | | +| 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 | | | +| 457 | Aeternity | AE | | | +| 459 | Kava | KAVA | | | +| 461 | Filecoin | FIL | | | +| 474 | Oasis | ROSE | | | +| 483 | Bluzelle | BLZ | | | +| 494 | BandChain | BAND | | | +| 500 | Theta | THETA | | | +| 501 | Solana | SOL | | | +| 508 | MultiversX | eGLD | | | +| 529 | Secret | SCRT | | | +| 564 | Agoric | BLD | | | +| 595 | Polymesh | POLYX | | | +| 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 | Viction | VIC | | | +| 899 | eCash | XEC | | | +| 931 | THORChain | RUNE | | | +| 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 | | | +| 21888 | Pactus | PAC | | | +| 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 | | | +| 10000060 | Native Injective | INJ | | | +| 10000070 | OP Mainnet | ETH | | | +| 10000100 | Gnosis Chain | xDAI | | | +| 10000118 | Osmosis | OSMO | | | +| 10000145 | Smart Bitcoin Cash | BCH | | | +| 10000146 | Sonic | S | | | +| 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 | | | +| 10002222 | KavaEvm | KAVA | | | +| 10004689 | IoTeX EVM | IOTX | | | +| 10007000 | NativeZetaChain | ZETA | | | +| 10007700 | NativeCanto | CANTO | | | +| 10008217 | Kaia | KAIA | | | +| 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/flutter/.gitignore b/flutter/.gitignore new file mode 100644 index 00000000000..c21255d8683 --- /dev/null +++ b/flutter/.gitignore @@ -0,0 +1,5 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +*.dylib +wallet_core_bindings.dart diff --git a/flutter/CHANGELOG.md b/flutter/CHANGELOG.md new file mode 100644 index 00000000000..effe43c82c8 --- /dev/null +++ b/flutter/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/flutter/README.md b/flutter/README.md new file mode 100644 index 00000000000..60f642dfe32 --- /dev/null +++ b/flutter/README.md @@ -0,0 +1,24 @@ +Wallet Core Bindings for Flutter + +## Installation + +1. Install Dart SDK: + - Visit [Dart SDK installation page](https://dart.dev/get-dart) + - Follow the instructions for your operating system + +2. Install dependencies: + ```bash + dart pub get + ``` + +## Usage + +### Running the App +```bash +dart run +``` + +### Test +```bash +dart test +``` diff --git a/flutter/analysis_options.yaml b/flutter/analysis_options.yaml new file mode 100644 index 00000000000..dee8927aafe --- /dev/null +++ b/flutter/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/flutter/bin/flutter.dart b/flutter/bin/flutter.dart new file mode 100644 index 00000000000..deac69f6fe1 --- /dev/null +++ b/flutter/bin/flutter.dart @@ -0,0 +1,139 @@ +import 'package:flutter/wallet_core.dart'; +import 'package:flutter/wallet_core_bindings.dart'; + +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void main(List arguments) { + final walletCore = load(); + + final passwordTwString = walletCore.TWStringCreateWithUTF8Bytes( + "".toNativeUtf8().cast(), + ); + + print("Creating a new HD wallet ... "); + final walletNew = walletCore.TWHDWalletCreate(128, passwordTwString); + print("done."); + print( + "Secret mnemonic for new wallet: '${walletCore.TWStringUTF8Bytes(walletCore.TWHDWalletMnemonic(walletNew)).cast().toDartString()}'.", + ); + walletCore.TWHDWalletDelete(walletNew); + + // Alternative: Import wallet with existing recovery phrase (mnemonic) + print("Importing an HD wallet from earlier ... "); + final secretMnemonic = walletCore.TWStringCreateWithUTF8Bytes( + "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal" + .toNativeUtf8() + .cast(), + ); + final walletImp = walletCore.TWHDWalletCreateWithMnemonic( + secretMnemonic, + walletCore.TWStringCreateWithUTF8Bytes("".toNativeUtf8().cast()), + ); + walletCore.TWStringDelete(secretMnemonic); + print("done."); + print( + "Secret mnemonic for imported wallet: '${walletCore.TWStringUTF8Bytes(walletCore.TWHDWalletMnemonic(walletImp)).cast().toDartString()}'.", + ); + + // coin type: we use Ethereum + const coinType = TWCoinType.TWCoinTypeEthereum; + print( + "Working with coin: ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeConfigurationGetName(coinType)).cast().toDartString()} ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeConfigurationGetSymbol(coinType)).cast().toDartString()}", + ); + + // Derive default address + print("Obtaining default address ... "); + final address = walletCore.TWStringUTF8Bytes( + walletCore.TWHDWalletGetAddressForCoin(walletImp, coinType), + ).cast().toDartString(); + print("done."); + print("Default address: '$address'"); + + // Alternative: Derive address using default derivation path + // Done in 2 steps: derive private key, then address from private key + // Note that private key is passed around between the two calls by the wallet -- be always cautious when handling secrets, avoid the risk of leaking secrets + print( + "Default derivation path: ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeDerivationPath(coinType)).cast().toDartString()}", + ); + final secretPrivateKeyDefault = walletCore.TWHDWalletGetKeyForCoin( + walletImp, + coinType, + ); + final addressDefault = walletCore.TWStringUTF8Bytes( + walletCore.TWCoinTypeDeriveAddress(coinType, secretPrivateKeyDefault), + ).cast().toDartString(); + print("Address from default key: '$addressDefault'"); + + // Alternative: Derive address using custom derivation path + final customDerivationPath = walletCore.TWStringCreateWithUTF8Bytes( + "m/44'/60'/1'/0/0".toNativeUtf8().cast(), + ); + final secretPrivateKeyCustom = walletCore.TWHDWalletGetKey( + walletImp, + coinType, + customDerivationPath, + ); + walletCore.TWStringDelete(customDerivationPath); + final addressCustom = walletCore.TWStringUTF8Bytes( + walletCore.TWCoinTypeDeriveAddress(coinType, secretPrivateKeyCustom), + ).cast().toDartString(); + print("Custom-derived address: '$addressCustom'"); + print(""); + + print( + "RECEIVE funds: Perform send from somewhere else to this address: $address", + ); + print(""); + + // Steps for sending: + // 1. put together a send message (contains sender and receiver address, amount, gas price, etc.) + // 2. sign this message + // 3. broadcast this message to the P2P network -- not done in this sample + print("SEND funds:"); + const dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986"; + final secretPrivKey = walletCore.TWPrivateKeyData(secretPrivateKeyDefault); + + print("preparing transaction (using AnySigner) ... "); + const chainIdB64 = "AQ=="; // base64(parse_hex("01")) + const gasPriceB64 = + "1pOkAA=="; // base64(parse_hex("d693a4")) decimal 3600000000 + const gasLimitB64 = "Ugg="; // base64(parse_hex("5208")) decimal 21000 + const amountB64 = + "A0i8paFgAA=="; // base64(parse_hex("0348bca5a160")) 924400000000000 + final transaction = + "{" + "\"chainId\":\"$chainIdB64" + "\",\"gasPrice\":\"$gasPriceB64" + "\",\"gasLimit\":\"$gasLimitB64" + "\",\"toAddress\":\"$dummyReceiverAddress" + "\",\"transaction\":{\"transfer\":{\"amount\":\"$amountB64" + "\"}}}"; + print("transaction: $transaction"); + + print("signing transaction ... "); + final json = walletCore.TWStringCreateWithUTF8Bytes( + transaction.toNativeUtf8().cast(), + ); + final result = walletCore.TWAnySignerSignJSON( + json, + secretPrivKey, + TWCoinType.TWCoinTypeEthereum, + ); + final signedTransaction = walletCore.TWStringUTF8Bytes( + result, + ).cast().toDartString(); + print("done"); + print( + "Signed transaction data (to be broadcast to network): (len ${signedTransaction.length}) '$signedTransaction'", + ); + // see e.g. https://github.com/flightwallet/decode-eth-tx for checking binary output content + print(""); + walletCore.TWStringDelete(json); + walletCore.TWStringDelete(result); + + print("Bye!"); + walletCore.TWHDWalletDelete(walletImp); + + walletCore.TWStringDelete(passwordTwString); +} diff --git a/flutter/config.yaml b/flutter/config.yaml new file mode 100644 index 00000000000..49f89948405 --- /dev/null +++ b/flutter/config.yaml @@ -0,0 +1,7 @@ +output: 'lib/wallet_core_bindings.dart' +headers: + entry-points: + - 'include/**.h' +name: 'WalletCore' +description: 'Bindings to WalletCore' +silence-enum-warning: true diff --git a/flutter/include b/flutter/include new file mode 120000 index 00000000000..f5030fe8899 --- /dev/null +++ b/flutter/include @@ -0,0 +1 @@ +../include \ No newline at end of file diff --git a/flutter/lib/wallet_core.dart b/flutter/lib/wallet_core.dart new file mode 100644 index 00000000000..e4f25a6bbf3 --- /dev/null +++ b/flutter/lib/wallet_core.dart @@ -0,0 +1,31 @@ +import 'dart:ffi'; +import 'dart:io'; +import 'package:path/path.dart' as path; + +import 'package:flutter/wallet_core_bindings.dart'; + +WalletCore load() { + var libraryPath = path.join( + Directory.current.path, + 'lib', + 'libTrustWalletCore.so', + ); + + if (Platform.isMacOS) { + libraryPath = path.join( + Directory.current.path, + 'lib', + 'libTrustWalletCore.dylib', + ); + } + + if (Platform.isWindows) { + libraryPath = path.join( + Directory.current.path, + 'lib', + 'libTrustWalletCore.dll', + ); + } + + return WalletCore(DynamicLibrary.open(libraryPath)); +} diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock new file mode 100644 index 00000000000..6a3de8ad0dd --- /dev/null +++ b/flutter/pubspec.lock @@ -0,0 +1,469 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + url: "https://pub.dev" + source: hosted + version: "82.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + url: "https://pub.dev" + source: hosted + version: "7.4.5" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" + url: "https://pub.dev" + source: hosted + version: "1.14.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + ffi: + dependency: "direct main" + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + ffigen: + dependency: "direct dev" + description: + name: ffigen + sha256: cb3edbfb68ac5283102a2deb7057913d3a1fb16552dacda0c07eb144497e4891 + url: "https://pub.dev" + source: hosted + version: "19.0.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: "direct dev" + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + url: "https://pub.dev" + source: hosted + version: "1.26.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + test_core: + dependency: transitive + description: + name: test_core + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + url: "https://pub.dev" + source: hosted + version: "0.6.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "6f82e9ee8e7339f5d8b699317f6f3afc17c80a68ebef1bc0d6f52a678c14b1e6" + url: "https://pub.dev" + source: hosted + version: "15.0.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 + url: "https://pub.dev" + source: hosted + version: "2.2.2" +sdks: + dart: ">=3.8.1 <4.0.0" diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml new file mode 100644 index 00000000000..b0ca629c361 --- /dev/null +++ b/flutter/pubspec.yaml @@ -0,0 +1,18 @@ +name: flutter +description: A sample command-line application. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.8.1 + +# Add regular dependencies here. +dependencies: + ffi: ^2.1.4 + path: ^1.9.1 + # path: ^1.8.0 + +dev_dependencies: + ffigen: ^19.0.0 + lints: ^5.0.0 + test: ^1.24.0 diff --git a/flutter/test/coin_address_derivation_test.dart b/flutter/test/coin_address_derivation_test.dart new file mode 100644 index 00000000000..88e5ac20d99 --- /dev/null +++ b/flutter/test/coin_address_derivation_test.dart @@ -0,0 +1,63 @@ +import 'package:flutter/wallet_core.dart'; +import 'package:flutter/wallet_core_bindings.dart'; +import 'package:test/test.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void main() { + test('derive addresses from phrase', () { + final walletCore = load(); + final mnemonic = + 'shoot island position soft burden budget tooth cruel issue economy destroy above'; + + final mnemonicTwString = walletCore.TWStringCreateWithUTF8Bytes( + mnemonic.toNativeUtf8().cast(), + ); + final passwordTwString = walletCore.TWStringCreateWithUTF8Bytes( + "".toNativeUtf8().cast(), + ); + + final wallet = walletCore.TWHDWalletCreateWithMnemonic( + mnemonicTwString, + passwordTwString, + ); + + // Test a few key coins + final coinsAndAddresses = [ + ( + TWCoinType.TWCoinTypeBitcoin, + 'bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d', + ), + ( + TWCoinType.TWCoinTypeEthereum, + '0x8f348F300873Fd5DA36950B2aC75a26584584feE', + ), + ( + TWCoinType.TWCoinTypeBinance, + 'bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw', + ), + ( + TWCoinType.TWCoinTypeCosmos, + 'cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn', + ), + ]; + + for (final coinAndAddress in coinsAndAddresses) { + final coin = coinAndAddress.$1; + final expectedAddress = coinAndAddress.$2; + + final address = walletCore.TWHDWalletGetAddressForCoin(wallet, coin); + final addressStr = walletCore.TWStringUTF8Bytes( + address, + ).cast().toDartString(); + + expect(addressStr, expectedAddress); + + walletCore.TWStringDelete(address); + } + + walletCore.TWHDWalletDelete(wallet); + walletCore.TWStringDelete(mnemonicTwString); + walletCore.TWStringDelete(passwordTwString); + }); +} diff --git a/flutter/test/tw_string_test.dart b/flutter/test/tw_string_test.dart new file mode 100644 index 00000000000..79a84a2976a --- /dev/null +++ b/flutter/test/tw_string_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter/wallet_core.dart'; +import 'package:test/test.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void main() { + test('twstring', () { + final walletCore = load(); + + final twString = walletCore.TWStringCreateWithUTF8Bytes( + 'Hello, World!'.toNativeUtf8().cast(), + ); + final bytes = walletCore.TWStringUTF8Bytes(twString); + expect(bytes.cast().toDartString(), 'Hello, World!'); + + final size = walletCore.TWStringSize(twString); + expect(size, 13); + + walletCore.TWStringDelete(twString); + }); +} diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h index 510d2f32291..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 @@ -12,40 +10,47 @@ TW_EXTERN_C_BEGIN +/// AES encryption/decryption methods. TW_EXPORT_STRUCT 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 c1cb6ef04a8..a5cc0fc0e2f 100644 --- a/include/TrustWalletCore/TWAccount.h +++ b/include/TrustWalletCore/TWAccount.h @@ -1,37 +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, TWString *_Nonnull derivationPath, 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 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 +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 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 3771b80e762..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 representaion. +/// 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/TWBase.h b/include/TrustWalletCore/TWBase.h index a7e9a5cdc8b..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) @@ -14,6 +12,9 @@ #endif #endif +// Marker for default visibility +#define TW_VISIBILITY_DEFAULT __attribute__((visibility("default"))) + // Marker for exported classes #define TW_EXPORT_CLASS @@ -52,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 5c6ca64ddac..a35ae178865 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,24 +10,35 @@ TW_EXTERN_C_BEGIN +/// Base58 encode / decode functions TW_EXPORT_STRUCT -struct TWBase58 { - uint8_t unused; // C doesn't allow zero-sized 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, null 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, null 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/TWBech32.h b/include/TrustWalletCore/TWBech32.h new file mode 100644 index 00000000000..53c0ab8973b --- /dev/null +++ b/include/TrustWalletCore/TWBech32.h @@ -0,0 +1,47 @@ +// 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 + +/// Bech32 encode / decode functions +TW_EXPORT_STRUCT +struct TWBech32; + +/// Encodes data as a Bech32 string. +/// +/// \param hrp The human-readable part. +/// \param data The data part. +/// \return the encoded Bech32 string. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBech32Encode(TWString* _Nonnull hrp, TWData *_Nonnull data); + +/// Decodes a Bech32 string. Returns null if the string is not a valid Bech32 string. +/// +/// \param string The Bech32 string to decode. +/// \return the decoded data, null if the string is not a valid Bech32 string. Note that the human-readable part is not returned. +TW_EXPORT_STATIC_METHOD +TWData *_Nullable TWBech32Decode(TWString *_Nonnull string); + +/// Encodes data as a Bech32m string. +/// +/// \param hrp The human-readable part. +/// \param data The data part. +/// \return the encoded Bech32m string. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBech32EncodeM(TWString* _Nonnull hrp, TWData *_Nonnull data); + +/// Decodes a Bech32m string. Returns null if the string is not a valid Bech32m string. +/// +/// \param string The Bech32m string to decode. +/// \return the decoded data, null if the string is not a valid Bech32m string. Note that the human-readable part is not returned. +TW_EXPORT_STATIC_METHOD +TWData *_Nullable TWBech32DecodeM(TWString *_Nonnull string); + +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/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index baa1be7eef5..b49e65ae1f8 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -1,121 +1,213 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, 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); /// 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 f37ce330857..519cf9c4429 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.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 +/// Blockchain enum type TW_EXPORT_ENUM(uint32_t) enum TWBlockchain { TWBlockchainBitcoin = 0, @@ -39,13 +38,37 @@ enum TWBlockchain { TWBlockchainHarmony = 25, TWBlockchainNEAR = 26, TWBlockchainAlgorand = 27, - TWBlockchainTON = 28, + TWBlockchainIOST = 28, TWBlockchainPolkadot = 29, TWBlockchainCardano = 30, TWBlockchainNEO = 31, TWBlockchainFilecoin = 32, - TWBlockchainElrondNetwork = 33, + TWBlockchainMultiversX = 33, TWBlockchainOasisNetwork = 34, + TWBlockchainDecred = 35, // Bitcoin + TWBlockchainZcash = 36, // Bitcoin + TWBlockchainGroestlcoin = 37, // Bitcoin + 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 + TWBlockchainBitcoinCash = 55, + TWBlockchainPactus = 56, + TWBlockchainKomodo = 57, + TWBlockchainPolymesh = 58, // Substrate }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCardano.h b/include/TrustWalletCore/TWCardano.h new file mode 100644 index 00000000000..2fd283e68d4 --- /dev/null +++ b/include/TrustWalletCore/TWCardano.h @@ -0,0 +1,50 @@ +// 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 + +/// Cardano helper functions +TW_EXPORT_STRUCT +struct TWCardano; + +/// 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 7e7645a1eec..b81e22582ea 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -1,25 +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 "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, @@ -58,92 +66,269 @@ enum TWCoinType { TWCoinTypeXRP = 144, TWCoinTypeSolana = 501, TWCoinTypeStellar = 148, - TWCoinTypeTON = 396, TWCoinTypeTezos = 1729, TWCoinTypeTheta = 500, - TWCoinTypeThunderToken = 1001, + TWCoinTypeThunderCore = 1001, TWCoinTypeNEO = 888, - TWCoinTypeTomoChain = 889, + TWCoinTypeViction = 889, TWCoinTypeTron = 195, TWCoinTypeVeChain = 818, TWCoinTypeViacoin = 14, TWCoinTypeWanchain = 5718350, TWCoinTypeZcash = 133, - TWCoinTypeZcoin = 136, + TWCoinTypeFiro = 136, TWCoinTypeZilliqa = 313, 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, + TWCoinTypeXDai = 10000100, + TWCoinTypeFantom = 10000250, + TWCoinTypeCryptoOrg = 394, + TWCoinTypeCelo = 52752, + TWCoinTypeRonin = 10002020, + TWCoinTypeOsmosis = 10000118, + TWCoinTypeECash = 899, + TWCoinTypeIOST = 291, + TWCoinTypeCronosChain = 10000025, + TWCoinTypeSmartBitcoinCash = 10000145, + TWCoinTypeKuCoinCommunityChain = 10000321, + TWCoinTypeBitcoinDiamond = 999, + TWCoinTypeBoba = 10000288, + TWCoinTypeSyscoin = 57, + TWCoinTypeVerge = 77, + TWCoinTypeZen = 121, + TWCoinTypeMetis = 10001088, + TWCoinTypeAurora = 1323161554, + TWCoinTypeEvmos = 10009001, + TWCoinTypeNativeEvmos = 20009001, + TWCoinTypeMoonriver = 10001285, + TWCoinTypeMoonbeam = 10001284, + 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, + TWCoinTypePactus = 21888, + TWCoinTypeSonic = 10000146, + TWCoinTypePolymesh = 595, + // 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); -/// Static prefix for this coin type +/// 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); + TW_EXTERN_C_END 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/TWCurve.h b/include/TrustWalletCore/TWCurve.h index eb536604712..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 @@ -15,11 +13,11 @@ TW_EXPORT_ENUM() enum TWCurve { TWCurveSECP256k1 /* "secp256k1" */, TWCurveED25519 /* "ed25519" */, - TWCurveED25519HD /* "ed25519-hd" */, 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 6ebe32aff78..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. -TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size); +/// +/// \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. -TWData *_Nonnull TWDataCreateWithSize(size_t 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. -TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull 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). -TWData *_Nullable TWDataCreateWithHexString(const TWString *_Nonnull hex); +/// +/// \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. -size_t TWDataSize(TWData *_Nonnull data); +/// +/// \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. -uint8_t *_Nonnull TWDataBytes(TWData *_Nonnull 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. -uint8_t TWDataGet(TWData *_Nonnull data, size_t 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. -void TWDataSet(TWData *_Nonnull data, size_t index, uint8_t byte); +/// +/// \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. -void TWDataCopyBytes(TWData *_Nonnull data, size_t start, size_t size, uint8_t *_Nonnull output); +/// +/// \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. -void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const uint8_t *_Nonnull bytes); +/// +/// \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. -void TWDataAppendBytes(TWData *_Nonnull data, const uint8_t *_Nonnull bytes, size_t size); +/// +/// \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. -void TWDataAppendByte(TWData *_Nonnull data, uint8_t 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. -void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append); +/// +/// \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. -void TWDataReverse(TWData *_Nonnull data); +/// 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. -void TWDataReset(TWData *_Nonnull data); +/// +/// \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. -void TWDataDelete(TWData *_Nonnull data); +/// +/// \param data A non-null valid block of data +void TWDataDelete(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Determines whether two data blocks are equal. -bool TWDataEqual(TWData *_Nonnull lhs, TWData *_Nonnull rhs); +/// +/// \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 new file mode 100644 index 00000000000..fbfae776ad2 --- /dev/null +++ b/include/TrustWalletCore/TWDataVector.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +/// 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(); + +/// 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); + +/// Delete/Deallocate a Vector of Data +/// +/// \param dataVector A non-null Vector of data +TW_EXPORT_METHOD +void TWDataVectorDelete(struct TWDataVector* _Nonnull dataVector); + +/// 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); + +/// 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. +/// +/// \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); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDerivation.h b/include/TrustWalletCore/TWDerivation.h new file mode 100644 index 00000000000..263f2710f96 --- /dev/null +++ b/include/TrustWalletCore/TWDerivation.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. +// + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Non-default coin address derivation names (default, unnamed derivations are not included). +/// Note the enum variant must be sync with `TWDerivation` enum in Rust: +/// https://github.com/trustwallet/wallet-core/blob/master/rust/tw_coin_registry/src/tw_derivation.rs +TW_EXPORT_ENUM() +enum TWDerivation { + TWDerivationDefault = 0, // default, for any coin + TWDerivationCustom = 1, // custom, for any coin + TWDerivationBitcoinSegwit = 2, + TWDerivationBitcoinLegacy = 3, + TWDerivationBitcoinTestnet = 4, + TWDerivationLitecoinLegacy = 5, + TWDerivationSolanaSolana = 6, + TWDerivationStratisSegwit = 7, + TWDerivationBitcoinTaproot = 8, + TWDerivationPactusMainnet = 9, + TWDerivationPactusTestnet = 10, + // end_of_derivation_enum - USED TO GENERATE CODE +}; + +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/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h index 9ba69247609..638acde0367 100644 --- a/include/TrustWalletCore/TWEthereumAbi.h +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -1,34 +1,123 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, 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_CLASS +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); +/// 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" +/// } +/// })"); +/// 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); + +/// Get function signature from Ethereum ABI json +/// +/// \param abi The function ABI json string, for example: {"inputs":[{"internalType":"bool","name":"arg1","type":"bool"}],"name":"fun1","outputs":[],"stateMutability":"nonpayable","type":"function"} +/// \return the function type signature, of the form "baz(int32,uint256)", null if the abi is invalid. +TW_EXPORT_STATIC_METHOD +TWString* _Nullable TWEthereumAbiGetFunctionSignature(TWString* _Nonnull abi); + TW_EXTERN_C_END 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 09d266c3a34..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 -TW_EXPORT_CLASS +/// 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/TWEthereumChainID.h b/include/TrustWalletCore/TWEthereumChainID.h deleted file mode 100644 index 207fbde1d87..00000000000 --- a/include/TrustWalletCore/TWEthereumChainID.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 "TWBase.h" - -TW_EXTERN_C_BEGIN - -/// Chain identifier for Ethereum-based blockchains. -TW_EXPORT_ENUM(uint32_t) -enum TWEthereumChainID { - TWEthereumChainIDEthereum = 1, - TWEthereumChainIDGo = 60, - TWEthereumChainIDPOA = 99, - TWEthereumChainIDCallisto = 820, - TWEthereumChainIDEthereumClassic = 61, - TWEthereumChainIDVeChain = 74, - TWEthereumChainIDThunderToken = 108, - TWEthereumChainIDTomoChain = 88, - TWEthereumChainIDBinanceSmartChain = 56, - TWEthereumChainIDMatic = 137, - TWEthereumChainIDWanchain = 888, -}; - -TW_EXTERN_C_END 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 d5509ac6f9d..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,69 +19,245 @@ TW_EXTERN_C_BEGIN +/// Hierarchical Deterministic (HD) Wallet TW_EXPORT_CLASS struct TWHDWallet; -/// Deprecated; use TWMnemonicIsValid(). Determines if a mnemonic phrase is valid. +/// Creates a new HDWallet with a new random mnemonic with the provided strength in bits. +/// +/// \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 -bool TWHDWalletIsValid(TWString *_Nonnull mnemonic); +struct TWHDWallet* _Nullable TWHDWalletCreate(int strength, TWString* _Nonnull passphrase); -/// Creates a new random HDWallet with the provided strength in bits. Returned object needs to be deleted. +/// Creates an HDWallet from a valid BIP39 English mnemonic and a passphrase. +/// +/// \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 *_Nonnull TWHDWalletCreate(int strength, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nonnull passphrase); -/// Creates an HDWallet from a mnemonic seed. Returned object needs to be deleted. +/// Creates an HDWallet from a BIP39 mnemonic, a passphrase and validation flag. +/// +/// \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 *_Nonnull TWHDWalletCreateWithMnemonic(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreateWithMnemonicCheck(TWString* _Nonnull mnemonic, TWString* _Nonnull passphrase, bool check); -/// Creates an HDWallet from a seed. Returned object needs to be deleted. +/// Creates an HDWallet from entropy (corresponding to a mnemonic). +/// +/// \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 *_Nonnull TWHDWalletCreateWithData(TWData *_Nonnull data, 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); + +/// Wallet entropy +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet entropy as a non-null block of data. TW_EXPORT_PROPERTY -TWString *_Nonnull TWHDWalletMnemonic(struct TWHDWallet *_Nonnull wallet); +TWData* _Nonnull TWHDWalletEntropy(struct TWHDWallet* _Nonnull wallet); -/// Returns master key. Returned object needs to be deleted. +/// 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); +struct TWPrivateKey* _Nonnull TWHDWalletGetMasterKey(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve); -/// Generates the default private key for the specified coin. Returned object needs to be deleted. +/// 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 -struct TWPrivateKey *_Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin); +TWString* _Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin); -/// Generates the default address for the specified coin (without exposing intermediary private key). +/// 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 -TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin); +TWString* _Nonnull TWHDWalletGetAddressDerivation(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. +/// +/// \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 TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, TWString *_Nonnull derivationPath); +struct TWPrivateKey* _Nonnull TWHDWalletGetKey(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, TWString* _Nonnull derivationPath); -/// Generates the private key for the specified BIP44 path. Returned object needs to be deleted. +/// Generates the private key for the specified derivation. /// -/// @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// \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 -struct TWPrivateKey *_Nonnull TWHDWalletGetKeyBIP44(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address); +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, enum TWDerivation derivation); -/// Returns the extended private key. +/// 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 -TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyByCurve(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve, TWString* _Nonnull derivationPath); -/// Returns the exteded public key. 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 +/// +/// \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 -TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); +struct TWPrivateKey* _Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address); -/// Computes the public key from an exteded public key representation. 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); + +/// 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); + +/// 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 TWDerivation derivation, enum TWHDVersion version, uint32_t account); + +/// 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 TWHDWalletGetExtendedPrivateKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version); + +/// 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 280dc5f9b7f..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,63 +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 TWHashGroestl512(TWData *_Nonnull data); - -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWHashXXHash64(TWData *_Nonnull data, uint64_t seed); +TWData *_Nonnull TWHashBlake2bPersonal(TWData *_Nonnull data, TWData * _Nonnull personal, size_t outlen); TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWHashTwoXXHash64Concat(TWData *_Nonnull data); +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 48713fb2151..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 -TW_EXPORT_CLASS +/// Mnemonic validate / lookup functions +TW_EXPORT_STRUCT struct TWMnemonic; -/// Determines whether a mnemonic phrase is valid. +/// 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 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. +/// 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 new file mode 100644 index 00000000000..86c6cca6801 --- /dev/null +++ b/include/TrustWalletCore/TWPBKDF2.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +/// Password-Based Key Derivation Function 2 +TW_EXPORT_STRUCT +struct TWPBKDF2; + +/// Derives a key from a password and a salt using PBKDF2 + Sha256. +/// +/// \param password is the master password from which a derived key is generated +/// \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); + +/// Derives a key from a password and a salt using PBKDF2 + Sha512. +/// +/// \param password is the master password from which a derived key is generated +/// \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); + +TW_EXTERN_C_END 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 220672d2a1e..7187ac564af 100644 --- a/include/TrustWalletCore/TWPublicKey.h +++ b/include/TrustWalletCore/TWPublicKey.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,42 +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 TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +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 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 e16727754df..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, + TWPublicKeyTypeED25519Cardano = 7, + TWPublicKeyTypeStarkex = 8, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPurpose.h b/include/TrustWalletCore/TWPurpose.h index ad4d9206cc7..027a5a76844 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,14 +10,15 @@ 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, TWPurposeBIP49 = 49, // Derivation scheme for P2WPKH-nested-in-P2SH TWPurposeBIP84 = 84, // Derivation scheme for P2WPKH + TWPurposeBIP86 = 86, // Derivation scheme for P2TR TWPurposeBIP1852 = 1852, // Derivation scheme used by Cardano-Shelley }; diff --git a/include/TrustWalletCore/TWRippleXAddress.h b/include/TrustWalletCore/TWRippleXAddress.h deleted file mode 100644 index 8b41daf1a82..00000000000 --- a/include/TrustWalletCore/TWRippleXAddress.h +++ /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. - -#pragma once - -#include "TWBase.h" -#include "TWString.h" -#include "TWData.h" -#include "TWHRP.h" - -TW_EXTERN_C_BEGIN - -struct TWPublicKey; - -/// Represents a Ripple X-address. -TW_EXPORT_CLASS -struct TWRippleXAddress; - -/// Compares two addresses for equality. -TW_EXPORT_STATIC_METHOD -bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippleXAddress *_Nonnull rhs); - -/// Determines if the string is a valid Ripple address. -TW_EXPORT_STATIC_METHOD -bool TWRippleXAddressIsValidString(TWString *_Nonnull string); - -/// Creates an address from a string representaion. -TW_EXPORT_STATIC_METHOD -struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_Nonnull string); - -/// Creates an address from a public key and destination tag. -TW_EXPORT_STATIC_METHOD -struct TWRippleXAddress *_Nonnull TWRippleXAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint32_t tag); - -TW_EXPORT_METHOD -void TWRippleXAddressDelete(struct TWRippleXAddress *_Nonnull address); - -/// Returns the address string representation. -TW_EXPORT_PROPERTY -TWString *_Nonnull TWRippleXAddressDescription(struct TWRippleXAddress *_Nonnull address); - -/// Returns the destination tag. -TW_EXPORT_PROPERTY -uint32_t TWRippleXAddressTag(struct TWRippleXAddress *_Nonnull address); - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWSS58AddressType.h b/include/TrustWalletCore/TWSS58AddressType.h deleted file mode 100644 index 13ca9769e39..00000000000 --- a/include/TrustWalletCore/TWSS58AddressType.h +++ /dev/null @@ -1,23 +0,0 @@ - -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "TWBase.h" - -TW_EXTERN_C_BEGIN - -/// Substrate based chains Address Type -/// -/// - See Also: https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)#address-type -TW_EXPORT_ENUM(uint8_t) -enum TWSS58AddressType { - TWSS58AddressTypePolkadot = 0, - TWSS58AddressTypeKusama = 2, -}; - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWSegwitAddress.h b/include/TrustWalletCore/TWSegwitAddress.h index 57fbd3fed5a..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,33 +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 an address from a public key. +/// 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/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 c5b6484d3cb..20615e776d4 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -1,16 +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 "TWBase.h" #include "TWCoinType.h" #include "TWData.h" +#include "TWDerivation.h" #include "TWHDWallet.h" #include "TWPrivateKey.h" +#include "TWStoredKeyEncryptionLevel.h" +#include "TWStoredKeyEncryption.h" #include "TWString.h" TW_EXTERN_C_BEGIN @@ -19,90 +20,357 @@ 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 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 +/// \param derivation derivation of the given coin type +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption, enum TWDerivation derivation); + +/// Imports an encoded private key. +/// +/// \param privateKey Non-null encoded private key +/// \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 TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); + +/// Imports an encoded private key. +/// +/// \param privateKey Non-null encoded 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 TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + +/// Imports an encoded private key. +/// +/// \param privateKey Non-null encoded 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 +/// \param derivation derivation of the given 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 TWStoredKeyImportPrivateKeyEncodedWithEncryptionAndDerivation(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption, enum TWDerivation derivation); + +/// 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. 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 TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel); + +/// 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 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 uniqie 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. +/// +/// \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). +/// +/// \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); + +/// 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); -/// Adds a new account. +/// 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 TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey); +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 encoded 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 encoded private key as a string if success, null pointer otherwise +TW_EXPORT_METHOD +TWString* _Nullable TWStoredKeyDecryptPrivateKeyEncoded(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); + +/// Whether the private key is encoded. +/// +/// \param key Non-null pointer to a stored key +/// \return true if the private key is encoded, false otherwise +TW_EXPORT_PROPERTY +bool TWStoredKeyHasPrivateKeyEncoded(struct TWStoredKey* _Nonnull key); + /// 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); -/// Dercrypts and returns the HD Wallet for mnemonic phrase keys. Returned object needs to be deleted. +/// 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); + TW_EXTERN_C_END 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 new file mode 100644 index 00000000000..6a7ceb7bcbc --- /dev/null +++ b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Preset encryption parameter with different security strength, for key store +TW_EXPORT_ENUM(uint32_t) +enum TWStoredKeyEncryptionLevel { + /// Default, which is one of the below values, determined by the implementation. + TWStoredKeyEncryptionLevelDefault = 0, + /// Minimal sufficient level of encryption strength (scrypt 4096) + TWStoredKeyEncryptionLevelMinimal = 1, + /// Weak encryption strength (scrypt 16k) + TWStoredKeyEncryptionLevelWeak = 2, + /// Standard level of encryption strength (scrypt 262k) + TWStoredKeyEncryptionLevelStandard = 3, +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWString.h b/include/TrustWalletCore/TWString.h index b62332f7e95..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); +/// 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); +/// 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); +/// +/// \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); +/// +/// \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); +/// +/// \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); +/// +/// \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); +/// 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); +/// +/// \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 new file mode 100644 index 00000000000..11844425bbe --- /dev/null +++ b/include/TrustWalletCore/TWTHORChainSwap.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 TWTHORChainSwap; + +/// 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); + +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 new file mode 100644 index 00000000000..67dfc4f0124 --- /dev/null +++ b/include/TrustWalletCore/TWTransactionCompiler.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWDataVector.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Non-core transaction utility methods, like building a transaction using an external signature. +TW_EXPORT_STRUCT +struct TWTransactionCompiler; + +/// 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 TWTransactionCompilerCompileWithSignatures( + enum TWCoinType coinType, TWData* _Nonnull txInputData, + const struct TWDataVector* _Nonnull signatures, const struct TWDataVector* _Nonnull publicKeys); + +TW_EXPORT_STATIC_METHOD +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/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..b02c28c9941 --- /dev/null +++ b/jni/android/AnySigner.c @@ -0,0 +1,43 @@ +// 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) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); + 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) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, json, "json"); + TWString *jsonString = TWStringCreateWithJString(env, json); + JNI_CHECK_NULL_AND_RETURN_NULL(env, key, "key"); + 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) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); + 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..27eb18f72ef 100644 --- a/jni/cpp/Random.cpp +++ b/jni/cpp/Random.cpp @@ -1,20 +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 #include +#include +#include -static JavaVM* cachedJVM; +static JavaVM* cachedJVM = nullptr; extern "C" { uint32_t random32(); void random_buffer(uint8_t *buf, size_t len); } -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, [[maybe_unused]] void *reserved) { cachedJVM = jvm; return JNI_VERSION_1_2; } @@ -26,8 +26,25 @@ uint32_t random32() { } void random_buffer(uint8_t *buf, size_t len) { + // Check whether the JVM instance has been set at `JNI_OnLoad`. + // https://github.com/trustwallet/wallet-core/pull/3984 + if (cachedJVM == nullptr) { + std::ifstream randomData("/dev/urandom", std::ios::in | std::ios::binary); + if (!randomData.is_open()) { + throw std::runtime_error("Error opening '/dev/urandom'"); + } + + randomData.read(reinterpret_cast(buf), len); + randomData.close(); + return; + } + JNIEnv *env; - cachedJVM->AttachCurrentThread(&env, NULL); +#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"); diff --git a/jni/cpp/TWJNI.h b/jni/cpp/TWJNI.h index 29bf14ba4df..938b691c5b3 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 @@ -21,3 +19,36 @@ #include #include "TWJNIData.h" #include "TWJNIString.h" + +#define JNI_CHECK_NULL_AND_RETURN_VOID(env, param, paramName) \ + do { \ + if (param == NULL) { \ + jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); \ + if (exceptionClass != NULL) { \ + (*env)->ThrowNew(env, exceptionClass, paramName " parameter cannot be null"); \ + } \ + return; \ + } \ + } while(0) + +#define JNI_CHECK_NULL_AND_RETURN_ZERO(env, param, paramName) \ + do { \ + if (param == NULL) { \ + jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); \ + if (exceptionClass != NULL) { \ + (*env)->ThrowNew(env, exceptionClass, paramName " parameter cannot be null"); \ + } \ + return 0; \ + } \ + } while(0) + +#define JNI_CHECK_NULL_AND_RETURN_NULL(env, param, paramName) \ + do { \ + if (param == NULL) { \ + jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); \ + if (exceptionClass != NULL) { \ + (*env)->ThrowNew(env, exceptionClass, paramName " parameter cannot be null"); \ + } \ + return NULL; \ + } \ + } while(0) diff --git a/jni/cpp/TWJNIData.cpp b/jni/cpp/TWJNIData.cpp index c0577f31442..0859a37671e 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); @@ -20,5 +18,7 @@ jbyteArray TWDataJByteArray(TWData *_Nonnull data, JNIEnv *env) { TWData *_Nonnull TWDataCreateWithJByteArray(JNIEnv *env, jbyteArray _Nonnull array) { jsize size = env->GetArrayLength(array); jbyte *bytes = env->GetByteArrayElements(array, nullptr); - return TWDataCreateWithBytes((uint8_t *) bytes, size); + const auto *twdata = TWDataCreateWithBytes((uint8_t *) bytes, size); + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + return twdata; } 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/java/wallet/core/java/GenericPhantomReference.java b/jni/java/wallet/core/java/GenericPhantomReference.java new file mode 100644 index 00000000000..ef7808b902e --- /dev/null +++ b/jni/java/wallet/core/java/GenericPhantomReference.java @@ -0,0 +1,52 @@ +package wallet.core.java; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.Set; +import java.util.HashSet; +import java.util.Collections; + +public class GenericPhantomReference extends PhantomReference { + private final long nativeHandle; + private final OnDeleteCallback onDeleteCallback; + + private static final Set references = Collections.synchronizedSet(new HashSet<>()); + private static final ReferenceQueue queue = new ReferenceQueue<>(); + + static { + Thread finalizingDaemon = new Thread(() -> { + try { + doDeletes(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + finalizingDaemon.setName("WCFinalizingDaemon"); + finalizingDaemon.setDaemon(true); + finalizingDaemon.setPriority(Thread.NORM_PRIORITY); + finalizingDaemon.start(); + } + + private GenericPhantomReference(Object referent, long handle, OnDeleteCallback onDelete) { + super(referent, queue); + this.nativeHandle = handle; + this.onDeleteCallback = onDelete; + } + + public static void register(Object referent, long handle, OnDeleteCallback onDelete) { + references.add(new GenericPhantomReference(referent, handle, onDelete)); + } + + private static void doDeletes() throws InterruptedException { + GenericPhantomReference ref = (GenericPhantomReference) queue.remove(); + for (; ref != null; ref = (GenericPhantomReference) queue.remove()) { + ref.onDeleteCallback.nativeDelete(ref.nativeHandle); + references.remove(ref); + } + } + + @FunctionalInterface + public interface OnDeleteCallback { + void nativeDelete(long handle); + } +} diff --git a/jni/kotlin/AnySigner.c b/jni/kotlin/AnySigner.c new file mode 100644 index 00000000000..6d64fa4ebf4 --- /dev/null +++ b/jni/kotlin/AnySigner.c @@ -0,0 +1,63 @@ +// 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) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, coin, "coin"); + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); + 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) { + JNI_CHECK_NULL_AND_RETURN_ZERO(env, coin, "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) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, coin, "coin"); + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + + JNI_CHECK_NULL_AND_RETURN_NULL(env, json, "json"); + TWString *jsonString = TWStringCreateWithJString(env, json); + + JNI_CHECK_NULL_AND_RETURN_NULL(env, key, "key"); + 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) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, coin, "coin"); + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); + 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..900b4bd953c --- /dev/null +++ b/kotlin/build.gradle.kts @@ -0,0 +1,22 @@ +// 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) + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } +} 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..66e19c38b8b --- /dev/null +++ b/kotlin/gradle/libs.versions.toml @@ -0,0 +1,18 @@ +[versions] +android-sdk-tools = "35.0.0" +android-sdk-min = "24" +android-sdk-compile = "35" +android-cmake = "3.22.1" +android-ndk = "28.0.12674087" + +kotlin = "2.1.0" +agp = "8.8.0" +wire = "4.5.6" + +androidx-test-runner = "1.5.2" + +[libraries] +wire-runtime = { module = "com.squareup.wire:wire-runtime", version.ref = "wire" } +wire-compiler = { module = "com.squareup.wire:wire-compiler", version.ref = "wire" } + +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } diff --git a/kotlin/gradle/wrapper/gradle-wrapper.jar b/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..2c3521197d7 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..df97d72b8b9 --- /dev/null +++ b/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/kotlin/gradlew b/kotlin/gradlew new file mode 100755 index 00000000000..46695364f36 --- /dev/null +++ b/kotlin/gradlew @@ -0,0 +1,252 @@ +#!/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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# 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/platforms/jvm/plugins-application/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##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || 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 -s )" 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +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=SC2039,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=SC2039,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, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +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..9b42019c791 --- /dev/null +++ b/kotlin/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +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..b943d1869cb --- /dev/null +++ b/kotlin/settings.gradle.kts @@ -0,0 +1,28 @@ +@file:Suppress("UnstableApiUsage") + +rootProject.name = "WalletCoreKotlin" + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +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..9d49f0c003d --- /dev/null +++ b/kotlin/wallet-core-kotlin/build.gradle.kts @@ -0,0 +1,159 @@ +@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() + + androidTarget { + publishLibraryVariants = listOf("release") + } + + jvm { + testRuns.named("test") { + executionTask.configure { + useJUnitPlatform() + } + } + } + jvmToolchain(17) + + 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) + } + } + + val commonTest by getting { + 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")) + } + } + + getByName("androidInstrumentedTest") { + dependsOn(commonTest) + dependencies { + implementation(libs.androidx.test.runner) + } + } + } + + nativeTargets.forEach { nativeTarget -> + nativeTarget.apply { + val main by compilations.getting + main.cinterops.create("WalletCore") { + packageName = "com.trustwallet.core" + includeDirs( + rootDir.parentFile.resolve("include"), + rootDir.parentFile.resolve("include/TrustWalletCore"), + ) + headers(rootDir.parentFile.resolve("include/TrustWalletCore").listFiles()!!) + } + } + } + + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} + +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") + } + } + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildFeatures { + aidl = false + compose = false + buildConfig = false + prefab = false + renderScript = false + resValues = false + shaders = false + viewBinding = false + } + + 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/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..e37b1e33897 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + System.loadLibrary("TrustWalletCore") + } +} 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..e37b1e33897 --- /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() { + System.loadLibrary("TrustWalletCore") + } +} 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/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt new file mode 100644 index 00000000000..0e2ffdd5b8f --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt @@ -0,0 +1,48 @@ +package com.trustwallet.core + +import java.lang.ref.PhantomReference +import java.lang.ref.ReferenceQueue +import java.util.Collections + +internal class GenericPhantomReference private constructor( + referent: Any, + private val handle: Long, + private val onDelete: (Long) -> Unit, +) : PhantomReference(referent, queue) { + + companion object { + private val references: MutableSet = Collections.synchronizedSet(HashSet()) + private val queue: ReferenceQueue = ReferenceQueue() + + init { + Thread { + try { + doDeletes() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } + }.apply { + name = "WCFinalizingDaemon" + isDaemon = true + priority = Thread.NORM_PRIORITY + start() + } + } + + fun register( + referent: Any, + handle: Long, + onDelete: (Long) -> Unit, + ) { + references.add(GenericPhantomReference(referent, handle, onDelete)) + } + + private fun doDeletes() { + while (true) { + val ref = queue.remove() as GenericPhantomReference + ref.onDelete(ref.handle) + references.remove(ref) + } + } + } +} diff --git a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt new file mode 100644 index 00000000000..7c2b43eed0e --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/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/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..44a3417d139 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -0,0 +1,153 @@ +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, Sonic, + -> "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" + Polymesh -> "2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw" + 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" + Pactus -> "pc1r7ys2g5a4xc2qtm0t4q987m4mvs57w5g0v4pvzg" + } +} 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..a5a55bc2eb3 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import kotlinx.cinterop.toCValues + +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +actual object AnySigner { + + actual fun sign(input: ByteArray, coin: CoinType): ByteArray { + val inputData = TWDataCreateWithBytes(input.toUByteArray().toCValues(), input.size.toULong()) + val result = TWAnySignerSign(inputData, coin.value)!!.readTwBytes()!! + TWDataDelete(inputData) + return result + } + + actual fun supportsJson(coin: CoinType): Boolean = + TWAnySignerSupportsJSON(coin.value) + + actual fun signJson(json: String, key: ByteArray, coin: CoinType): String { + val jsonString = TWStringCreateWithUTF8Bytes(json) + val keyData = TWDataCreateWithBytes(key.toUByteArray().toCValues(), key.size.toULong()) + val result = TWAnySignerSignJSON(jsonString, keyData, coin.value).fromTwString()!! + TWStringDelete(jsonString) + TWDataDelete(keyData) + return result + } + + actual fun plan(input: ByteArray, coin: CoinType): ByteArray { + val inputData = TWDataCreateWithBytes(input.toUByteArray().toCValues(), input.size.toULong()) + val result = TWAnySignerPlan(inputData, coin.value)?.readTwBytes()!! + TWDataDelete(inputData) + return result + } +} 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..b6e500d75bd --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt @@ -0,0 +1,18 @@ +// 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 + +// Build ByteArray from TWData, and then delete TWData +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +internal fun COpaquePointer?.readTwBytes(): ByteArray? = + this?.let { + val result = TWDataBytes(it)?.readBytes(TWDataSize(it).toInt()) + TWDataDelete(it) + result + } 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..708228d9f63 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import kotlinx.cinterop.CValuesRef +import kotlinx.cinterop.toKString + +// Build String from TWString, and then delete TWString +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +internal fun CValuesRef<*>?.fromTwString(): String? = + this?.let { + val result = TWStringUTF8Bytes(it)?.toKString() + TWStringDelete(it) + result + } 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/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 7375fc0efda..237789be7be 100644 --- a/protobuf-plugin/CMakeLists.txt +++ b/protobuf-plugin/CMakeLists.txt @@ -1,26 +1,26 @@ -cmake_minimum_required(VERSION 3.2 FATAL_ERROR) +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(TrustWalletCoreProtobufPlugin) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) +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 REQUIRED PATH ${PREFIX}/lib/pkgconfig) -include_directories(${Protobuf_INCLUDE_DIRS}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +find_package(Protobuf CONFIG REQUIRED PATH ${PREFIX}/lib/pkgconfig) -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-c-typedef c_typedef.cc) +target_link_libraries(protoc-gen-c-typedef protobuf::libprotobuf protobuf::libprotoc) -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 a5877616af2..3d0983c2ca3 100644 --- a/protobuf-plugin/swift_typealias.cc +++ b/protobuf-plugin/swift_typealias.cc @@ -4,6 +4,8 @@ #include #include #include +#include +#include using namespace google::protobuf; @@ -14,43 +16,58 @@ 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" ); + + std::vector names; + std::vector> aliases; + for (int i = 0; i < file->message_type_count(); i += 1) { - auto message = file->message_type(i); - auto parts = Generator::getParts(message->full_name()); + const auto* message = file->message_type(i); + names.emplace_back(message->full_name()); + } + + for (int i = 0; i < file->enum_type_count(); i += 1) { + const auto* enum_type = file->enum_type(i); + names.emplace_back(enum_type->full_name()); + } + + for (auto& name : names) { + auto parts = Generator::getParts(name); if (parts.size() < 3 || parts[0] != "TW") { - std::cerr << "Invalid proto name '" << message->full_name() << "'" << std::endl; + std::cerr << "Invalid proto name '" << name << "'" << std::endl; continue; } - std::string def = "public typealias "; + + std::string alias = ""; for (auto i = 0; i < parts.size(); i += 1) { if (i == 0 || i == 2) { continue; } - def += parts[i]; + alias += parts[i]; } - def += " = "; - + std::string type = ""; for (auto& part : parts) { - def += part + "_"; + type += part + "_"; } - def = def.substr(0, def.size() - 1); - def += ";\n"; - printer.Print(def.c_str()); + type = type.substr(0, type.size() - 1); + + aliases.emplace_back(std::make_tuple(alias, type)); } + for (auto& alias : aliases) { + std::string line = "public typealias " + std::get<0>(alias) + " = " + std::get<1>(alias) + "\n"; + printer.Print(line.c_str()); + } return true; } diff --git a/registry.json b/registry.json new file mode 100644 index 00000000000..faca84e65ad --- /dev/null +++ b/registry.json @@ -0,0 +1,4883 @@ +[ + { + "id": "bitcoin", + "name": "Bitcoin", + "coinId": 0, + "symbol": "BTC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "name": "segwit", + "path": "m/84'/0'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/0'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + }, + { + "name": "testnet", + "path": "m/84'/1'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "taproot", + "path": "m/86'/0'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://mempool.space", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", + "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" + }, + "info": { + "url": "https://bitcoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "litecoin", + "name": "Litecoin", + "coinId": 2, + "symbol": "LTC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/84'/2'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/2'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 48, + "p2shPrefix": 50, + "hrp": "ltc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/litecoin/transaction/", + "accountPath": "/litecoin/address/" + }, + "info": { + "url": "https://litecoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "doge", + "name": "Dogecoin", + "coinId": 3, + "symbol": "DOGE", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/3'/0'/0/0", + "xpub": "dgub", + "xprv": "dgpv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 22, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/dogecoin/transaction/", + "accountPath": "/dogecoin/address/" + }, + "info": { + "url": "https://dogecoin.com", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "dash", + "name": "Dash", + "coinId": 5, + "symbol": "DASH", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/5'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 76, + "p2shPrefix": 16, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/dash/transaction/", + "accountPath": "/dash/address/" + }, + "info": { + "url": "https://dash.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "viacoin", + "name": "Viacoin", + "coinId": 14, + "symbol": "VIA", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/84'/14'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 71, + "p2shPrefix": 33, + "hrp": "via", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.viacoin.org", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://viacoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "groestlcoin", + "name": "Groestlcoin", + "coinId": 17, + "symbol": "GRS", + "decimals": 8, + "blockchain": "Groestlcoin", + "derivation": [ + { + "path": "m/84'/17'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 36, + "p2shPrefix": 5, + "hrp": "grs", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "groestl512d", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/groestlcoin/transaction/", + "accountPath": "/groestlcoin/address/" + }, + "info": { + "url": "https://www.groestlcoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "digibyte", + "name": "DigiByte", + "coinId": 20, + "symbol": "DGB", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/84'/20'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 63, + "hrp": "dgb", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://digiexplorer.info", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://www.digibyte.io", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "monacoin", + "name": "Monacoin", + "coinId": 22, + "symbol": "MONA", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/22'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 50, + "p2shPrefix": 55, + "hrp": "mona", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockbook.electrum-mona.org", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://monacoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "decred", + "name": "Decred", + "coinId": 42, + "symbol": "DCR", + "decimals": 8, + "blockchain": "Decred", + "derivation": [ + { + "path": "m/44'/42'/0'/0/0", + "xpub": "dpub", + "xprv": "dprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 7, + "p2pkhPrefix": 63, + "p2shPrefix": 26, + "publicKeyHasher": "blake256ripemd", + "base58Hasher": "blake256d", + "explorer": { + "url": "https://dcrdata.decred.org", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://decred.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "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", + "coinId": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1", + "addressHasher": "keccak256", + "explorer": { + "url": "https://etherscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f", + "sampleAccount": "0x5bb497e8d9fe26e92dd1be01e32076c8e024d167" + }, + "info": { + "url": "https://ethereum.org", + "source": "https://github.com/ethereum/go-ethereum", + "rpc": "https://mainnet.infura.io", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "classic", + "name": "Ethereum Classic", + "coinId": 61, + "symbol": "ETC", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/61'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "61", + "addressHasher": "keccak256", + "explorer": { + "url": "https://blockscout.com/etc/mainnet", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997", + "sampleAccount": "0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d" + }, + "info": { + "url": "https://ethereumclassic.org", + "source": "https://github.com/ethereumclassic/go-ethereum", + "rpc": "https://www.ethercluster.com/etc", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "icon", + "name": "ICON", + "coinId": 74, + "symbol": "ICX", + "decimals": 18, + "blockchain": "Icon", + "derivation": [ + { + "path": "m/44'/74'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://tracker.icon.foundation", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://icon.foundation", + "source": "https://github.com/icon-project/icon-rpc-server", + "rpc": "http://ctz.icxstation.com:9000/api/v3", + "documentation": "https://www.icondev.io/docs/icon-json-rpc-v3" + } + }, + { + "id": "verge", + "name": "Verge", + "coinId": 77, + "symbol": "XVG", + "decimals": 6, + "blockchain": "Verge", + "derivation": [ + { + "path": "m/84'/77'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 33, + "hrp": "vg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://verge-blockchain.info", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581", + "sampleAccount": "DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6" + }, + "info": { + "url": "https://vergecurrency.com", + "source": "https://github.com/vergecurrency/verge", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "pivx", + "name": "Pivx", + "coinId": 119, + "symbol": "PIVX", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/119'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 13, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://pivx.ccore.online", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://pivx.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "zen", + "name": "Zen", + "coinId": 121, + "symbol": "ZEN", + "decimals": 8, + "blockchain": "Zen", + "derivation": [ + { + "path": "m/44'/121'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 32, + "p2pkhPrefix": 137, + "p2shPrefix": 150, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.horizen.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430", + "sampleAccount": "znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j" + }, + "info": { + "url": "https://www.horizen.io", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "aptos", + "name": "Aptos", + "displayName": "Aptos", + "coinId": 637, + "symbol": "APT", + "decimals": 8, + "chainId": "1", + "blockchain": "Aptos", + "derivation": [ + { + "path": "m/44'/637'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://explorer.aptoslabs.com", + "txPath": "/txn/", + "accountPath": "/account/", + "sampleTx": "0xedc88058e27f6c065fd6607e262cb2a83a65f74301df90c61923014c59f9d465", + "sampleAccount": "0x60ad80e8cdadb81399e8a738014bc9ec865cef842f7c2cf7d84fbf7e40d065" + }, + "info": { + "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": "sui", + "name": "Sui", + "coinId": 784, + "symbol": "SUI", + "decimals": 9, + "blockchain": "Sui", + "derivation": [ + { + "path": "m/44'/784'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://suiscan.xyz/mainnet", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "68wBKsZyYXmCUydDmabQ71kTcFWTfDG7tFmTLk1HgNdN", + "sampleAccount": "0x54e80d76d790c277f5a44f3ce92f53d26f5894892bf395dee6375988876be6b2" + }, + "info": { + "url": "https://sui.io/", + "source": "https://github.com/MystenLabs/sui", + "rpc": "https://fullnode.testnet.sui.io", + "documentation": "https://docs.sui.io/" + } + }, + { + "id": "cosmos", + "name": "Cosmos", + "displayName": "Cosmos Hub", + "coinId": 118, + "symbol": "ATOM", + "decimals": 6, + "chainId": "cosmoshub-4", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "cosmos", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://mintscan.io/cosmos", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790", + "sampleAccount": "cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz" + }, + "info": { + "url": "https://cosmos.network", + "source": "https://github.com/cosmos/cosmos-sdk", + "rpc": "https://stargate.cosmos.network", + "documentation": "https://cosmos.network/rpc" + } + }, + { + "id": "stargaze", + "name": "Stargaze", + "displayName": "Stargaze", + "coinId": 20000118, + "symbol": "STARS", + "decimals": 6, + "chainId": "stargaze-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "stars", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/stargaze", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "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": "juno", + "name": "Juno", + "displayName": "Juno", + "coinId": 30000118, + "symbol": "JUNO", + "decimals": 6, + "chainId": "juno-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "juno", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/juno", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "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": "stride", + "name": "Stride", + "displayName": "Stride", + "coinId": 40000118, + "symbol": "STRD", + "decimals": 6, + "chainId": "stride-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "stride", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/stride", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "url": "https://stride.zone/", + "source": "https://github.com/Stride-Labs/stride", + "rpc": "https://stride-rpc.polkachu.com/", + "documentation": "https://docs.stride.zone/docs" + } + }, + { + "id": "axelar", + "name": "Axelar", + "displayName": "Axelar", + "coinId": 50000118, + "symbol": "AXL", + "decimals": 6, + "chainId": "axelar-dojo-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "axelar", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/axelar", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "url": "https://axelar.network/", + "source": "https://github.com/axelarnetwork/axelar-core", + "rpc": "https://axelar-rpc.polkachu.com", + "documentation": "https://docs.axelar.dev/" + } + }, + { + "id": "crescent", + "name": "Crescent", + "displayName": "Crescent", + "coinId": 60000118, + "symbol": "CRE", + "decimals": 6, + "chainId": "crescent-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "cre", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/crescent", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "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": "kujira", + "name": "Kujira", + "displayName": "Kujira", + "coinId": 70000118, + "symbol": "KUJI", + "decimals": 6, + "chainId": "kaiyo-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "kujira", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/kujira", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "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": "comdex", + "name": "Comdex", + "displayName": "Comdex", + "coinId": 80000118, + "symbol": "CMDX", + "decimals": 6, + "chainId": "comdex-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "comdex", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/comdex", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "04C790D09A40EE958DBDD385B679B5EB60C10F9BC1389CC8F896DC9193A5ED6C", + "sampleAccount": "comdex1jz7av7cq45gh5hhrugtak7lkps2ga5v0u64nz6" + }, + "info": { + "url": "https://comdex.one/", + "documentation": "https://docs.comdex.one/" + } + }, + { + "id": "neutron", + "name": "Neutron", + "displayName": "Neutron", + "coinId": 90000118, + "symbol": "NTRN", + "decimals": 6, + "chainId": "neutron-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "neutron", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/neutron", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "E18BA087009A05EB6A15A22FE30BA99379B909F74A74120E6F92B9882C45F0D7", + "sampleAccount": "neutron1pm4af8pcurxssdxztqw9rexx5f8zfq7uzqfmy8" + }, + "info": { + "url": "https://neutron.org/", + "documentation": "https://docs.neutron.org/" + } + }, + { + "id": "sommelier", + "name": "Sommelier", + "displayName": "Sommelier", + "coinId": 11000118, + "symbol": "SOMM", + "decimals": 6, + "chainId": "sommelier-3", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "somm", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/sommelier", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "E73A9E5E534777DDADF7F69A5CB41972894B862D1763FA4081FE913D8D3A5E80", + "sampleAccount": "somm10d5wmqvezwtj20u5hg3wuvwucce2nhsy0tzqgn" + }, + "info": { + "url": "https://www.sommelier.finance/" + } + }, + { + "id": "fetchai", + "name": "FetchAI", + "displayName": "Fetch AI", + "coinId": 12000118, + "symbol": "FET", + "decimals": 6, + "chainId": "fetchhub-4", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "fetch", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/fetchai", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "7EB4F6C26809BA047F81CEFD0889775AC8522B7B8AF559B436083BE7039C5EA6", + "sampleAccount": "fetch1t3qet68dr0qkmrjtq89lrx837qa2t05265qy6s" + }, + "info": { + "url": "https://fetch.ai/", + "documentation": "https://docs.fetch.ai/" + } + }, + { + "id": "mars", + "name": "Mars", + "displayName": "Mars Hub", + "coinId": 13000118, + "symbol": "MARS", + "decimals": 6, + "chainId": "mars-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "mars", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/mars-protocol", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "C12120760C71189A678739E0F1FD4EFAF2C29EA660B57A359AC728F89FAA7528", + "sampleAccount": "mars1nnjy6nct405pzfaqjm3dsyw0pf0kyw72vhw4pr" + }, + "info": { + "url": "https://marsprotocol.io/", + "documentation": "https://docs.marsprotocol.io/" + } + }, + { + "id": "umee", + "name": "Umee", + "displayName": "Umee", + "coinId": 14000118, + "symbol": "UMEE", + "decimals": 6, + "chainId": "umee-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "umee", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/umee", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "65B4B52C5F324F2287540847A114F645D89D544D99F793879FB3DBFF2CFEFC84", + "sampleAccount": "umee16934q0qf4akw8qruy5y8v748rvtxxjckgsecq4" + }, + "info": { + "url": "https://umee.cc/", + "documentation": "https://umeeversity.umee.cc/developers/" + } + }, + { + "id": "noble", + "name": "Noble", + "displayName": "Noble", + "coinId": 18000118, + "symbol": "USDC", + "decimals": 6, + "chainId": "noble-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "noble", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/noble", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "EA231079975A058FEC28EF372B445763918C098DE033E868E2E035F3F98C59C7", + "sampleAccount": "noble1y2egevq0nyzm7w6a9kpxkw86eqytcvxpwsp6d9" + }, + "info": { + "url": "https://nobleassets.xyz/" + } + }, + { + "id": "sei", + "name": "Sei", + "displayName": "Sei", + "coinId": 19000118, + "symbol": "SEI", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "pacific-1", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "sei", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/sei", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "4A2114EE45317439690F3BEA9C8B6CFA11D42CF978F9487754902D372EEB488C", + "sampleAccount": "sei155hqv2rsypqzq0zpjn72frsxx4l6tcmplw63m2" + }, + "info": { + "url": "https://sei.io/", + "documentation": "https://docs.sei.io/" + } + }, + { + "id": "tia", + "name": "Tia", + "displayName": "Celestia", + "coinId": 21000118, + "symbol": "TIA", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "celestia", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "celestia", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/celestia", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B", + "sampleAccount": "celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2" + }, + "info": { + "url": "https://celestia.org/", + "documentation": "https://docs.celestia.org/" + } + }, + { + "id": "coreum", + "name": "Coreum", + "displayName": "Coreum", + "coinId": 10000990, + "symbol": "CORE", + "decimals": 6, + "chainId": "coreum-mainnet-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/990'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "core", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/coreum", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "32A4AE2AE6AAE31E75EDDADE0AB9F1499ABD5AD8D3F261ADEF2805CD46FF74E7", + "sampleAccount": "core1zmwdnfpwuymwn0fkwnj2aaje34npd5sqgjxq9v" + }, + "info": { + "url": "https://www.coreum.com/", + "documentation": "https://www.coreum.com/developers" + } + }, + { + "id": "quasar", + "name": "Quasar", + "displayName": "Quasar", + "coinId": 15000118, + "symbol": "QSR", + "decimals": 6, + "chainId": "quasar-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "quasar", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/quasar", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "2898B89C98FE1E8CF1E05A37E4EE5EE5ED83FD957B0CAEE53DE39FC82BF1A033", + "sampleAccount": "quasar1cqu6w425slheul3jsmyt6q0ec0rs0w0ugkst3k" + }, + "info": { + "url": "https://www.quasar.fi/", + "documentation": "https://docs.quasar.fi/" + } + }, + { + "id": "persistence", + "name": "Persistence", + "displayName": "Persistence", + "coinId": 16000118, + "symbol": "XPRT", + "decimals": 6, + "chainId": "core-1", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "persistence", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/persistence", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "BBD9DEE03A8D7538D8E7398217467F4A2B5690D15773E8A6442E6AEEEFA21E64", + "sampleAccount": "persistence10ys69560pqr6zmqam80g8s0smtjw6p3ugzmy3u" + }, + "info": { + "url": "https://persistence.one/", + "documentation": "https://docs.persistence.one/" + } + }, + { + "id": "akash", + "name": "Akash", + "displayName": "Akash", + "coinId": 17000118, + "symbol": "AKT", + "decimals": 6, + "chainId": "akashnet-2", + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "akash", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/akash", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "C0083856344425908D5333D4325E3E0DE9D697BA568C6D99C34303819F615D25", + "sampleAccount": "akash1f4nskxfw8ufhwnajh7xwt0wmdtxm02vwta6krg" + }, + "info": { + "url": "https://akash.network/", + "documentation": "https://docs.akash.network/" + } + }, + { + "id": "zcash", + "name": "Zcash", + "coinId": 133, + "symbol": "ZEC", + "decimals": 8, + "blockchain": "Zcash", + "derivation": [ + { + "path": "m/44'/133'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 28, + "p2pkhPrefix": 184, + "p2shPrefix": 189, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockchair.com/zcash", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35", + "sampleAccount": "t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2" + }, + "info": { + "url": "https://z.cash", + "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", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/136'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 82, + "p2shPrefix": 7, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.firo.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111", + "sampleAccount": "a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM" + }, + "info": { + "url": "https://firo.org/", + "source": "https://github.com/firoorg/firo", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "komodo", + "name": "Komodo", + "coinId": 141, + "symbol": "KMD", + "decimals": 8, + "blockchain": "Komodo", + "derivation": [ + { + "path": "m/44'/141'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 60, + "p2shPrefix": 85, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://kmdexplorer.io/", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e", + "sampleAccount": "RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2" + }, + "info": { + "url": "https://komodoplatform.com", + "source": "https://github.com/KomodoPlatform/komodo", + "rpc": "", + "documentation": "https://developers.komodoplatform.com" + } + }, + { + "id": "ripple", + "name": "XRP", + "coinId": 144, + "symbol": "XRP", + "decimals": 6, + "blockchain": "Ripple", + "derivation": [ + { + "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": "BitcoinCash", + "derivation": [ + { + "path": "m/44'/145'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bitcoincash", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/bitcoin-cash/transaction/", + "accountPath": "/bitcoin-cash/address/" + }, + "info": { + "url": "https://bitcoincash.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "stellar", + "name": "Stellar", + "coinId": 148, + "symbol": "XLM", + "decimals": 7, + "blockchain": "Stellar", + "derivation": [ + { + "path": "m/44'/148'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://blockchair.com/stellar", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "8a7ff7261e8b3f31af7f6ed257c2e9fe7c47afcd9b1ce1be1bfc1bc5f6a3ad9e", + "sampleAccount": "GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52" + }, + "info": { + "url": "https://stellar.org", + "source": "https://github.com/stellar/go", + "rpc": "https://horizon.stellar.org", + "documentation": "https://www.stellar.org/developers/horizon/reference" + } + }, + { + "id": "bitcoingold", + "name": "Bitcoin Gold", + "coinId": 156, + "symbol": "BTG", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/84'/156'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 38, + "p2shPrefix": 23, + "hrp": "btg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.bitcoingold.org/insight", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23", + "sampleAccount": "GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U" + }, + "info": { + "url": "https://bitcoingold.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "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://nanexplorer.com/nano", + "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": "Ethereum", + "derivation": [ + { + "path": "m/44'/178'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "99", + "addressHasher": "keccak256", + "explorer": { + "url": "https://blockscout.com", + "txPath": "/poa/core/tx/", + "accountPath": "/poa/core/address/" + }, + "info": { + "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://developers.eos.io/eosio-nodeos/reference" + } + }, + { + "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://allo.info", + "txPath": "/tx/", + "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.callistodao.org", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "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-1", + "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://thornode.ninerealms.com", + "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'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "100", + "addressHasher": "keccak256", + "explorer": { + "url": "https://blockscout.com/xdai/mainnet", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1", + "sampleAccount": "0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8" + }, + "info": { + "url": "https://www.xdaichain.com", + "client": "https://github.com/openethereum/openethereum", + "clientPublic": "https://rpc.gnosischain.com", + "clientDocs": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "fantom", + "name": "Fantom", + "coinId": 10000250, + "symbol": "FTM", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "250", + "addressHasher": "keccak256", + "explorer": { + "url": "https://ftmscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202", + "sampleAccount": "0x9474feb9917b87da6f0d830ba66ee0035835c0d3" + }, + "info": { + "url": "https://fantom.foundation", + "client": "https://github.com/openethereum/openethereum", + "clientPublic": "https://rpc.ftm.tools", + "clientDocs": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "cryptoorg", + "name": "CryptoOrg", + "displayName": "Crypto.org", + "coinId": 394, + "symbol": "CRO", + "decimals": 8, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/394'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "cro", + "chainId": "crypto-org-chain-mainnet-1", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://crypto.org/explorer", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2", + "sampleAccount": "cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent" + }, + "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" + } + }, + { + "id": "celo", + "name": "Celo", + "coinId": 52752, + "symbol": "CELO", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "42220", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.celo.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693", + "sampleAccount": "0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD" + }, + "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" + } + }, + { + "id": "ronin", + "name": "Ronin", + "coinId": 10002020, + "slip44": 60, + "symbol": "RON", + "decimals": 18, + "blockchain": "Ronin", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "2020", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.roninchain.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab", + "sampleAccount": "0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb" + }, + "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" + } + }, + { + "id": "secret", + "name": "Secret", + "displayName": "Secret", + "coinId": 529, + "symbol": "SCRT", + "decimals": 6, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/529'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "secret", + "chainId": "secret-4", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://mintscan.io/secret", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "026B4886B1D9CE836A99755DDE99D4F8A7748D27B1CE9D298A763B1CFFF62C00", + "sampleAccount": "secret167m3s89ddurjpyr82vsluvvj0t8ylzn95trrqy" + }, + "info": { + "url": "https://scrt.network/", + "source": "https://github.com/scrtlabs/SecretNetwork", + "rpc": "https://scrt-rpc.blockpane.com/", + "documentation": "https://docs.scrt.network/" + } + }, + { + "id": "osmosis", + "name": "Osmosis", + "displayName": "Osmosis", + "coinId": 10000118, + "symbol": "OSMO", + "decimals": 6, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "osmo", + "chainId": "osmosis-1", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://mintscan.io/osmosis", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8", + "sampleAccount": "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5" + }, + "info": { + "url": "https://osmosis.zone/", + "client": "https://github.com/osmosis-labs/osmosis", + "clientPublic": "https://rpc-osmosis.keplr.app/", + "clientDocs": "" + } + }, + { + "id": "ecash", + "name": "eCash", + "coinId": 899, + "symbol": "XEC", + "decimals": 2, + "blockchain": "BitcoinCash", + "derivation": [ + { + "path": "m/44'/899'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "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": "6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b", + "sampleAccount": "ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j" + }, + "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" + } + }, + { + "id": "iost", + "name": "IOST", + "coinId": 291, + "symbol": "IOST", + "decimals": 2, + "blockchain": "IOST", + "derivation": [ + { + "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://explorer.iost.io", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx", + "sampleAccount": "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" + }, + "info": { + "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": "cronos", + "name": "Cronos Chain", + "coinId": 10000025, + "symbol": "CRO", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "25", + "addressHasher": "keccak256", + "explorer": { + "url": "https://cronoscan.com", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "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" + }, + "testFolderName": "Cronos" + }, + { + "id": "kavaevm", + "name": "KavaEvm", + "coinId": 10002222, + "symbol": "KAVA", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "2222", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.kava.io", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "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": "smartbch", + "name": "Smart Bitcoin Cash", + "coinId": 10000145, + "symbol": "BCH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "10000", + "addressHasher": "keccak256", + "explorer": { + "url": "https://www.smartscout.cash", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9", + "sampleAccount": "0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F" + }, + "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" + }, + "testFolderName": "Bitcoin" + }, + { + "id": "kcc", + "name": "KuCoin Community Chain", + "coinId": 10000321, + "symbol": "KCS", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "321", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.kcc.io/en", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d", + "sampleAccount": "0x4446fc4eb47f2f6586f9faab68b3498f86c07521" + }, + "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/" + }, + "testFolderName": "KuCoinCommunityChain" + }, + { + "id": "boba", + "name": "Boba", + "coinId": 10000288, + "symbol": "BOBAETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "288", + "addressHasher": "keccak256", + "explorer": { + "url": "https://eth.bobascan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103", + "sampleAccount": "0x4F96F50eDB37a19216d87693E5dB241e31bD3735" + }, + "info": { + "url": "https://boba.network/", + "source": "https://github.com/bobanetwork/boba", + "rpc": "https://mainnet.boba.network", + "documentation": "https://docs.boba.network/" + } + }, + { + "id": "metis", + "name": "Metis", + "coinId": 10001088, + "symbol": "METIS", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1088", + "addressHasher": "keccak256", + "explorer": { + "url": "https://andromeda-explorer.metis.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce", + "sampleAccount": "0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86" + }, + "info": { + "url": "https://www.metis.io/", + "source": "https://github.com/MetisProtocol/mvm", + "rpc": "https://andromeda.metis.io/?owner=1088", + "documentation": "https://docs.metis.io/" + } + }, + { + "id": "aurora", + "name": "Aurora", + "coinId": 1323161554, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1313161554", + "addressHasher": "keccak256", + "explorer": { + "url": "https://aurorascan.dev", + "txPath": "/tx/", + "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": "evmos", + "name": "Evmos", + "coinId": 10009001, + "symbol": "EVMOS", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "9001", + "addressHasher": "keccak256", + "explorer": { + "url": "https://evm.evmos.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422", + "sampleAccount": "0x30627903124Aa1e71384bc52e1cb96E4AB3252b6" + }, + "info": { + "url": "https://evmos.org/", + "source": "https://github.com/tharsis/evmos", + "rpc": "https://eth.bd.evmos.org:8545", + "documentation": "https://docs.evmos.org/" + } + }, + { + "id": "nativeevmos", + "name": "NativeEvmos", + "displayName": "Native Evmos", + "coinId": 20009001, + "symbol": "EVMOS", + "decimals": 18, + "blockchain": "NativeEvmos", + "chainId": "evmos_9001-2", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "evmos", + "addressHasher": "keccak256", + "explorer": { + "url": "https://mintscan.io/evmos", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811", + "sampleAccount": "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" + }, + "info": { + "url": "https://evmos.org/", + "client": "https://github.com/tharsis/evmos", + "clientPublic": "https://rest.bd.evmos.org:1317", + "clientDocs": "" + } + }, + { + "id": "moonriver", + "name": "Moonriver", + "coinId": 10001285, + "symbol": "MOVR", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1285", + "explorer": { + "url": "https://moonriver.moonscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032", + "sampleAccount": "0x899831D937937d011305E73EE782cce0455DF15a" + }, + "info": { + "url": "https://moonbeam.network/networks/moonriver", + "rpc": "https://moonriver.public.blastapi.io" + } + }, + { + "id": "moonbeam", + "name": "Moonbeam", + "coinId": 10001284, + "symbol": "GLMR", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1284", + "explorer": { + "url": "https://moonscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6", + "sampleAccount": "0x201bb4f276C765dF7225e5A4153E17edD23a67eC" + }, + "info": { + "url": "https://moonbeam.network", + "rpc": "https://rpc.api.moonbeam.network", + "documentation": "https://docs.moonbeam.network" + } + }, + { + "id": "kaia", + "name": "Kaia", + "coinId": 10008217, + "symbol": "KAIA", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "8217", + "explorer": { + "url": "https://kaiascan.io", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7", + "sampleAccount": "0x2ad9656bf5b82caf10847b431012e28e301e83ba" + }, + "info": { + "url": "https://kaia.io", + "rpc": "https://public-en.node.kaia.io", + "documentation": "https://docs.kaia.io" + } + }, + { + "id": "meter", + "name": "Meter", + "coinId": 18000, + "chainId": "82", + "symbol": "MTR", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://scan.meter.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144", + "sampleAccount": "0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd" + }, + "info": { + "url": "https://meter.io/", + "source": "https://github.com/meterio/meter-pov", + "rpc": "https://rpc.meter.io", + "documentation": "https://docs.meter.io/" + } + }, + { + "id": "okc", + "name": "OKX Chain", + "coinId": 996, + "chainId": "66", + "symbol": "OKT", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "addressHasher": "keccak256", + "explorer": { + "url": "https://www.oklink.com/oktc", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37", + "sampleAccount": "0x074faafd0b20fad2efa115b8ed7e75993e580b85" + }, + "info": { + "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": "cfxevm", + "name": "Conflux eSpace", + "coinId": 1030, + "chainId": "1030", + "symbol": "CFX", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "addressHasher": "keccak256", + "explorer": { + "url": "https://evm.confluxscan.net", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x920efefb3213b2d6a3b84149cb50b61a813d085443a20e1b0eab74120e41ff72", + "sampleAccount": "0x337a087daef75c72871de592fbbba570829a936a" + }, + "info": { + "url": "https://confluxnetwork.org", + "source": "https://github.com/conflux-chain", + "rpc": "https://evm.confluxrpc.com", + "documentation": "https://doc.confluxnetwork.org/docs/espace" + } + }, + { + "id": "greenfield", + "name": "Greenfield", + "displayName": "BNB Greenfield", + "coinId": 5600, + "symbol": "BNB", + "decimals": 18, + "chainId": "1017", + "blockchain": "Greenfield", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "addressHasher": "keccak256", + "explorer": { + "url": "https://greenfieldscan.com", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3", + "sampleAccount": "0xcf0f6b88ed72653b00fdebbffc90b98072cb3285" + }, + "info": { + "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": "opbnb", + "name": "OpBNB", + "coinId": 204, + "chainId": "204", + "symbol": "BNB", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "addressHasher": "keccak256", + "explorer": { + "url": "https://opbnbscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x788ea8fb4a82dae957f1d3b18af3cd0bbde55a276e66bd17af8c869f24c03a0f", + "sampleAccount": "0x4eaf936c172b5e5511959167e8ab4f7031113ca3" + }, + "info": { + "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": "stratis", + "name": "Stratis", + "coinId": 105105, + "symbol": "STRAX", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "name": "segwit", + "path": "m/44'/105105'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "strax", + "p2pkhPrefix": 75, + "p2shPrefix": 140, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.rutanio.com/strax/explorer", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb", + "sampleAccount": "XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN" + }, + "info": { + "url": "https://www.stratisplatform.com/", + "source": "https://github.com/stratisproject", + "rpc": "", + "documentation": "https://academy.stratisplatform.com/index.html" + } + }, + { + "id": "Nebl", + "name": "Nebl", + "coinId": 146, + "symbol": "NEBL", + "decimals": 8, + "blockchain": "Verge", + "derivation": [ + { + "path": "m/44'/146'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 53, + "p2shPrefix": 112, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.nebl.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "1e56c745ab87d702c98eddc6bc2475b2eb292ed4af92d170b2362c8a089272a4", + "sampleAccount": "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc" + }, + "info": { + "url": "https://nebl.io", + "source": "https://github.com/NeblioTeam", + "rpc": "", + "documentation": "https://github.com/NeblioTeam" + } + }, + { + "id": "hedera", + "name": "Hedera", + "coinId": 3030, + "symbol": "HBAR", + "decimals": 8, + "blockchain": "Hedera", + "derivation": [ + { + "path": "m/44'/3030'/0'/0'/0" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://hashscan.io/mainnet", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "0.0.19790-1666769504-858851231", + "sampleAccount": "0.0.19790" + }, + "info": { + "url": "https://hedera.com/", + "source": "https://github.com/hashgraph", + "documentation": "https://docs.hedera.com" + } + }, + { + "id": "agoric", + "name": "Agoric", + "coinId": 564, + "symbol": "BLD", + "decimals": 6, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/564'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "agoric", + "chainId": "agoric-3", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://atomscan.com/agoric", + "txPath": "/transactions/", + "accountPath": "/accounts/", + "sampleTx": "576224B1A0F3D56BA23C5350C2A0E3CEA86F40005B828793E5ACBC2F4813152E", + "sampleAccount": "agoric1cqvwa8jr6pmt45jndanc8lqmdsxjkhw0yertc0" + }, + "info": { + "url": "https://agoric.com", + "source": "https://github.com/Agoric/agoric-sdk", + "rpc": "https://agoric-rpc.polkachu.com", + "documentation": "https://docs.agoric.com" + } + }, + { + "id": "dydx", + "name": "Dydx", + "displayName": "dYdX", + "coinId": 22000118, + "symbol": "DYDX", + "decimals": 18, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "dydx", + "chainId": "dydx-mainnet-1", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/dydx", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "F236222E4F7C92FA84711FD6451ED22DD56CBDFA319BFDAFB99A21E4E9B9EC2F", + "sampleAccount": "dydx1adl7usw7z2dnysyn7wvrghu0u0q6gr7jqs4gtt" + }, + "info": { + "url": "https://dydx.exchange", + "source": "https://github.com/dydxprotocol", + "rpc": "https://dydx-dao-api.polkachu.com", + "documentation": "https://docs.dydx.exchange" + } + }, + { + "id": "nativeinjective", + "name": "NativeInjective", + "displayName": "Native Injective", + "coinId": 10000060, + "symbol": "INJ", + "decimals": 18, + "blockchain": "NativeInjective", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "inj", + "addressHasher": "keccak256", + "chainId": "injective-1", + "explorer": { + "url": "https://www.mintscan.io/injective", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "C5F6A4FF9DF1AE9FF543D2CEBD8E3E9B04290B2445F9D91D7707EDBF4B7EE16B", + "sampleAccount": "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd" + }, + "info": { + "url": "https://injective.com", + "documentation": "https://docs.injective.network" + } + }, + { + "id": "nativecanto", + "name": "NativeCanto", + "displayName": "NativeCanto", + "coinId": 10007700, + "symbol": "CANTO", + "decimals": 18, + "blockchain": "Cosmos", + "chainId": "canto_7700-1", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "canto", + "addressHasher": "keccak256", + "explorer": { + "url": "https://mintscan.io/canto", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "7A7830270097AA9AC8B819EFBB8E0B56579F20ECB7615ECD37E19ABEEFB8DB83", + "sampleAccount": "canto17xpfvakm2amg962yls6f84z3kell8c5lz0zsl4" + }, + "info": { + "url": "https://canto.io/", + "documentation": "https://docs.canto.io/" + } + }, + { + "id": "zetachain", + "name": "NativeZetaChain", + "displayName": "NativeZetaChain", + "coinId": 10007000, + "symbol": "ZETA", + "decimals": 18, + "blockchain": "Cosmos", + "chainId": "zetachain_7000-1", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "zeta", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.zetachain.com", + "txPath": "/cosmos/tx/", + "accountPath": "/address/", + "sampleTx": "2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE", + "sampleAccount": "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne" + }, + "info": { + "url": "https://www.zetachain.com/", + "documentation": "https://www.zetachain.com/docs/" + } + }, + { + "id": "zetaevm", + "name": "Zeta EVM", + "coinId": 20007000, + "symbol": "ZETA", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "7000", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.zetachain.com", + "txPath": "/evm/tx/", + "accountPath": "/address/", + "sampleTx": "0x04cb1201857de29af97b755e51c888454fb96c1f3bb3c1329bb94d5353d5c19e", + "sampleAccount": "0x85539A58F9c88DdDccBaBBfc660968323Fd1e167" + }, + "info": { + "url": "https://www.zetachain.com/", + "documentation": "https://www.zetachain.com/docs/" + } + }, + { + "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": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "245022934", + "addressHasher": "keccak256", + "explorer": { + "url": "https://neonscan.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x77d86af2c6f02f14ef13ca52bf54864d92fcc4b32d8e884e225061c006738ed6", + "sampleAccount": "0xfa4a8650e7bebb918859c280a86f9661bed29877" + }, + "info": { + "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": "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", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "169", + "addressHasher": "keccak256", + "explorer": { + "url": "https://pacific-explorer.manta.network", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x2bbd5d85b0ed05d1416e30ce1197a6f0c27d10ce02593a2719e2baf486d2e8c2", + "sampleAccount": "0xF122a1aC569a36a5Cf6d0F828A22254c8A9afF84" + }, + "info": { + "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": "merlin", + "name": "Merlin", + "coinId": 4200, + "symbol": "BTC", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "4200", + "addressHasher": "keccak256", + "explorer": { + "url": "https://scan.merlinchain.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xca6f2891959b669237738dd0bc2c1051d966778c9de90b94165032ce58364212", + "sampleAccount": "0xf7e017b3f61bD3410A3b03D7DAD7699FD6780584" + }, + "info": { + "url": "https://merlinchain.io", + "source": "https://merlinchain.io", + "rpc": "https://rpc.merlinchain.io", + "documentation": "https://docs.merlinchain.io/merlin-docs" + } + }, + { + "id": "lightlink", + "name": "Lightlink", + "displayName": "Lightlink Phoenix", + "coinId": 1890, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1890", + "addressHasher": "keccak256", + "explorer": { + "url": "https://phoenix.lightlink.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xc65f82445aefc883fd4ffe09149c8ce48f61b670c0734528a49d4a8bd8309bb0", + "sampleAccount": "0x4566ED6c7a7fFc90E2C7cfF7eB9156262afD2fDe" + }, + "info": { + "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": "blast", + "name": "Blast", + "coinId": 81457, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "81457", + "addressHasher": "keccak256", + "explorer": { + "url": "https://blastscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x511fc00e8329343b9e953bf1f75e9b0a7b3cc2eb3a8f049d5be41adf4fbd6cac", + "sampleAccount": "0x0d11f2f0ff55c4fcfc3ff86bdc8e78ffa7df99fd" + }, + "info": { + "url": "https://blast.io", + "source": "https://github.com/blast-io", + "rpc": "https://rpc.blast.io", + "documentation": "https://docs.blast.io" + } + }, + { + "id": "bouncebit", + "name": "BounceBit", + "coinId": 6001, + "symbol": "BB", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "6001", + "addressHasher": "keccak256", + "explorer": { + "url": "https://bbscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x52558f4143d058d942e3b73414090266ae3ffce1fe8c25fe86896e2c8e5ef932", + "sampleAccount": "0xf4aa7349a9ccca4609943955b5ddc7bd9278c223" + }, + "info": { + "url": "https://bouncebit.io", + "source": "https://github.com/BounceBit-Labs", + "rpc": "https://fullnode-mainnet.bouncebitapi.com", + "documentation": "https://docs.bouncebit.io" + } + }, + { + "id": "zklinknova", + "name": "ZkLinkNova", + "displayName": "zkLink Nova Mainnet", + "coinId": 810180, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "810180", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.zklink.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xeb5eb8710369c89115a83f3e744c15c9d388030cfce2fd3a653dbd18f2947400", + "sampleAccount": "0xF95115BaD9a4585B3C5e2bfB50579f17163A45aA" + }, + "info": { + "url": "https://zklink.io", + "source": "https://github.com/zkLinkProtocol", + "rpc": "https://rpc.zklink.io", + "documentation": "https://docs.zklink.io" + } + }, + { + "id": "sonic", + "name": "Sonic", + "coinId": 10000146, + "symbol": "S", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "146", + "addressHasher": "keccak256", + "explorer": { + "url": "https://sonicscan.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x886c34de9e054c741b2bcb15c3a3e0382e3ed7a48f2c6f2a489f6d82abdd4a7c", + "sampleAccount": "0x9c174f0e2d11559447e5fe2815d930475be19016" + }, + "info": { + "url": "https://www.soniclabs.com", + "source": "https://github.com/Fantom-foundation/Sonic", + "rpc": "https://rpc.soniclabs.com", + "documentation": "https://docs.soniclabs.com" + } + }, + { + "id": "pactus", + "name": "Pactus", + "coinId": 21888, + "symbol": "PAC", + "decimals": 9, + "blockchain": "Pactus", + "derivation": [ + { + "name": "mainnet", + "path": "m/44'/21888'/3'/0'" + }, + { + "name": "testnet", + "path": "m/44'/21777'/3'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "hrp": "pc", + "explorer": { + "url": "https://pacviewer.com", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://pactus.org", + "source": "https://github.com/pactus-project/pactus", + "rpc": "https://docs.pactus.org/api/http", + "documentation": "https://docs.pactus.org" + } + }, + { + "id": "polymesh", + "name": "Polymesh", + "coinId": 595, + "symbol": "POLYX", + "decimals": 6, + "blockchain": "Polymesh", + "derivation": [ + { + "path": "m/44'/595'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "addressHasher": "keccak256", + "ss58Prefix": 12, + "explorer": { + "url": "https://polymesh.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04", + "sampleAccount": "2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv" + }, + "info": { + "url": "https://polymesh.network", + "source": "https://github.com/PolymeshAssociation/Polymesh", + "rpc": "wss://rpc.polymesh.network/", + "documentation": "https://developers.polymesh.network/" + } + } +] diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 00000000000..d963b2c89d3 --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Generated by tw_macros +bindings/ diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 00000000000..d1e1d9c7423 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,2679 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +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", + "serde", +] + +[[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", + "serde", +] + +[[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.96", + "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.96", +] + +[[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-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[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.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +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.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "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.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +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.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +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", + "serde", +] + +[[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.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +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.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[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.96", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[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.96", +] + +[[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.96", +] + +[[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.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +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.96", +] + +[[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_macros", + "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", + "itertools", + "lazy_static", + "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_bitcoincash" +version = "0.1.0" +dependencies = [ + "tw_bitcoin", + "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", + "strum_macros", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "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_bitcoincash", + "tw_coin_entry", + "tw_cosmos", + "tw_decred", + "tw_ethereum", + "tw_evm", + "tw_greenfield", + "tw_groestlcoin", + "tw_hash", + "tw_internet_computer", + "tw_keypair", + "tw_komodo", + "tw_memory", + "tw_misc", + "tw_native_evmos", + "tw_native_injective", + "tw_pactus", + "tw_polkadot", + "tw_polymesh", + "tw_ripple", + "tw_ronin", + "tw_solana", + "tw_substrate", + "tw_sui", + "tw_thorchain", + "tw_ton", + "tw_utxo", + "tw_zcash", +] + +[[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_decred" +version = "0.1.0" +dependencies = [ + "tw_base58_address", + "tw_bitcoin", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", + "tw_utxo", +] + +[[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_macros", + "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_groestlcoin" +version = "0.1.0" +dependencies = [ + "tw_base58_address", + "tw_bitcoin", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", + "tw_utxo", +] + +[[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_macros", + "tw_memory", + "tw_misc", + "zeroize", +] + +[[package]] +name = "tw_komodo" +version = "0.1.0" +dependencies = [ + "tw_bitcoin", + "tw_coin_entry", + "tw_keypair", + "tw_proto", + "tw_utxo", + "tw_zcash", +] + +[[package]] +name = "tw_macros" +version = "0.1.0" +dependencies = [ + "derive-syn-parse", + "proc-macro2", + "quote", + "serde", + "serde_yaml", + "syn 2.0.96", +] + +[[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_pactus" +version = "0.1.0" +dependencies = [ + "bech32", + "byteorder", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_polkadot" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", + "tw_scale", + "tw_ss58_address", + "tw_substrate", +] + +[[package]] +name = "tw_polymesh" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", + "tw_scale", + "tw_ss58_address", + "tw_substrate", +] + +[[package]] +name = "tw_proto" +version = "0.1.0" +dependencies = [ + "arbitrary 1.3.0", + "pb-rs", + "quick-protobuf", +] + +[[package]] +name = "tw_ripple" +version = "0.1.0" +dependencies = [ + "bigdecimal", + "byteorder", + "lazy_static", + "serde", + "serde_json", + "strum_macros", + "tw_base58_address", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", +] + +[[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_scale" +version = "0.1.0" +dependencies = [ + "tw_hash", + "tw_number", +] + +[[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_misc", + "tw_proto", +] + +[[package]] +name = "tw_ss58_address" +version = "0.1.0" +dependencies = [ + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_scale", +] + +[[package]] +name = "tw_substrate" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", + "tw_scale", + "tw_ss58_address", +] + +[[package]] +name = "tw_sui" +version = "0.1.0" +dependencies = [ + "indexmap", + "move-core-types", + "serde", + "serde_json", + "serde_repr", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "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 = "tw_zcash" +version = "0.1.0" +dependencies = [ + "tw_base58_address", + "tw_bitcoin", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", + "tw_utxo", +] + +[[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 = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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_evm", + "tw_hash", + "tw_keypair", + "tw_macros", + "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.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[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.96", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 00000000000..09ab546a720 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,66 @@ +[workspace] +resolver = "2" +members = [ + "chains/tw_aptos", + "chains/tw_binance", + "chains/tw_bitcoin", + "chains/tw_bitcoincash", + "chains/tw_cosmos", + "chains/tw_decred", + "chains/tw_ethereum", + "chains/tw_greenfield", + "chains/tw_groestlcoin", + "chains/tw_internet_computer", + "chains/tw_komodo", + "chains/tw_native_evmos", + "chains/tw_native_injective", + "chains/tw_pactus", + "chains/tw_polkadot", + "chains/tw_polymesh", + "chains/tw_ripple", + "chains/tw_ronin", + "chains/tw_solana", + "chains/tw_sui", + "chains/tw_thorchain", + "chains/tw_ton", + "chains/tw_zcash", + "frameworks/tw_substrate", + "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_macros", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", + "tw_scale", + "tw_ss58_address", + "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..913b3393b9c --- /dev/null +++ b/rust/chains/tw_aptos/src/aptos_move_packages.rs @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; + +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 fungible_asset_transfer( + metadata_address: AccountAddress, + 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!("primary_fungible_store").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![TypeTag::from_str("0x1::fungible_asset::Metadata") + .tw_err(SigningErrorType::Error_internal)?], + vec![ + bcs::encode(&metadata_address)?, + bcs::encode(&to)?, + bcs::encode(&amount)?, + ], + json!([ + metadata_address.to_hex_literal(), + to.to_hex_literal(), + amount.to_string() + ]), + ))) +} diff --git a/rust/chains/tw_aptos/src/aptos_move_types.rs b/rust/chains/tw_aptos/src/aptos_move_types.rs new file mode 100644 index 00000000000..f364b7f6e42 --- /dev/null +++ b/rust/chains/tw_aptos/src/aptos_move_types.rs @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use move_core_types::{ + account_address::AccountAddress, + identifier::Identifier, + language_storage::{StructTag, TypeTag}, + parser::parse_type_tag, +}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use tw_encoding::EncodingError; + +/// The address of an account +/// +/// This is represented in a string as a 64 character hex string, sometimes +/// shortened by stripping leading 0s, and adding a 0x. +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Address(AccountAddress); + +impl From for Address { + fn from(address: AccountAddress) -> Self { + Self(address) + } +} + +impl From
for AccountAddress { + fn from(address: Address) -> Self { + address.0 + } +} + +impl From<&Address> for AccountAddress { + fn from(address: &Address) -> Self { + address.0 + } +} + +/// A wrapper of a Move identifier +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct IdentifierWrapper(pub Identifier); + +impl From for Identifier { + fn from(value: IdentifierWrapper) -> Identifier { + value.0 + } +} + +impl From for IdentifierWrapper { + fn from(value: Identifier) -> IdentifierWrapper { + Self(value) + } +} + +impl From<&Identifier> for IdentifierWrapper { + fn from(value: &Identifier) -> IdentifierWrapper { + Self(value.clone()) + } +} + +/// A Move struct tag for referencing an onchain struct type +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MoveStructTag { + pub address: Address, + pub module: IdentifierWrapper, + pub name: IdentifierWrapper, + /// Generic type parameters associated with the struct + pub generic_type_params: Vec, +} + +impl From for MoveStructTag { + fn from(tag: StructTag) -> Self { + Self { + address: tag.address.into(), + module: tag.module.into(), + name: tag.name.into(), + generic_type_params: tag.type_params.into_iter().map(MoveType::from).collect(), + } + } +} + +impl From<&StructTag> for MoveStructTag { + fn from(tag: &StructTag) -> Self { + Self { + address: tag.address.into(), + module: IdentifierWrapper::from(&tag.module), + name: IdentifierWrapper::from(&tag.name), + generic_type_params: tag.type_params.iter().map(MoveType::from).collect(), + } + } +} + +impl TryFrom for StructTag { + type Error = EncodingError; + + fn try_from(tag: MoveStructTag) -> Result { + Ok(Self { + address: tag.address.into(), + module: tag.module.into(), + name: tag.name.into(), + type_params: tag + .generic_type_params + .into_iter() + .map(|p| p.try_into()) + .collect::, Self::Error>>()?, + }) + } +} + +/// An enum of Move's possible types on-chain +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MoveType { + /// A bool type + Bool, + /// An 8-bit unsigned int + U8, + /// A 16-bit unsigned int + U16, + /// A 32-bit unsigned int + U32, + /// A 64-bit unsigned int + U64, + /// A 128-bit unsigned int + U128, + /// A 256-bit unsigned int + U256, + /// A 32-byte account address + Address, + /// An account signer + Signer, + /// A Vector of [`MoveType`] + Vector { items: Box }, + /// A struct of [`MoveStructTag`] + Struct(MoveStructTag), + /// A generic type param with index + GenericTypeParam { index: u16 }, + /// A reference + Reference { mutable: bool, to: Box }, + /// A move type that couldn't be parsed + /// + /// This prevents the parser from just throwing an error because one field + /// was unparsable, and gives the value in it. + Unparsable(String), +} + +impl FromStr for MoveType { + type Err = EncodingError; + + // Taken from: https://github.com/aptos-labs/aptos-core/blob/aaa3514c8ee4e5d38b89d916eadff7286a42e040/api/types/src/move_types.rs#L612-L639 + fn from_str(mut s: &str) -> Result { + let mut is_ref = false; + let mut is_mut = false; + if s.starts_with('&') { + s = &s[1..]; + is_ref = true; + } + if is_ref && s.starts_with("mut ") { + s = &s[4..]; + is_mut = true; + } + // Previously this would just crap out, but this meant the API could + // return a serialized version of an object and not be able to + // deserialize it using that same object. + let inner = match parse_type_tag(s) { + Ok(inner) => inner.into(), + Err(_e) => MoveType::Unparsable(s.to_string()), + }; + if is_ref { + Ok(MoveType::Reference { + mutable: is_mut, + to: Box::new(inner), + }) + } else { + Ok(inner) + } + } +} + +impl From for MoveType { + fn from(tag: TypeTag) -> Self { + match tag { + TypeTag::Bool => MoveType::Bool, + TypeTag::U8 => MoveType::U8, + TypeTag::U16 => MoveType::U16, + TypeTag::U32 => MoveType::U32, + TypeTag::U64 => MoveType::U64, + TypeTag::U256 => MoveType::U256, + TypeTag::U128 => MoveType::U128, + TypeTag::Address => MoveType::Address, + TypeTag::Signer => MoveType::Signer, + TypeTag::Vector(v) => MoveType::Vector { + items: Box::new(MoveType::from(*v)), + }, + TypeTag::Struct(v) => MoveType::Struct((*v).into()), + } + } +} + +impl From<&TypeTag> for MoveType { + fn from(tag: &TypeTag) -> Self { + match tag { + TypeTag::Bool => MoveType::Bool, + TypeTag::U8 => MoveType::U8, + TypeTag::U16 => MoveType::U16, + TypeTag::U32 => MoveType::U32, + TypeTag::U64 => MoveType::U64, + TypeTag::U128 => MoveType::U128, + TypeTag::U256 => MoveType::U256, + TypeTag::Address => MoveType::Address, + TypeTag::Signer => MoveType::Signer, + TypeTag::Vector(v) => MoveType::Vector { + items: Box::new(MoveType::from(v.as_ref())), + }, + TypeTag::Struct(v) => MoveType::Struct((&**v).into()), + } + } +} + +impl TryFrom for TypeTag { + type Error = EncodingError; + + fn try_from(tag: MoveType) -> Result { + let ret = match tag { + MoveType::Bool => TypeTag::Bool, + MoveType::U8 => TypeTag::U8, + MoveType::U16 => TypeTag::U16, + MoveType::U32 => TypeTag::U32, + MoveType::U64 => TypeTag::U64, + MoveType::U128 => TypeTag::U128, + MoveType::U256 => TypeTag::U256, + MoveType::Address => TypeTag::Address, + MoveType::Signer => TypeTag::Signer, + MoveType::Vector { items } => TypeTag::Vector(Box::new((*items).try_into()?)), + MoveType::Struct(v) => TypeTag::Struct(Box::new(v.try_into()?)), + MoveType::GenericTypeParam { index: _ } => TypeTag::Address, // Dummy type, allows for Object + _ => { + return Err(EncodingError::InvalidInput); + }, + }; + Ok(ret) + } +} 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..2f2996ccceb --- /dev/null +++ b/rust/chains/tw_aptos/src/constants.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use move_core_types::{ident_str, identifier::IdentStr}; + +pub const GAS_UNIT_PRICE: u64 = 100; +pub const MAX_GAS_AMOUNT: u64 = 100_000_000; +pub const APTOS_SALT: &[u8] = b"APTOS::RawTransaction"; + +pub const OBJECT_MODULE: &IdentStr = ident_str!("object"); +pub const OBJECT_STRUCT: &IdentStr = ident_str!("Object"); diff --git a/rust/chains/tw_aptos/src/entry.rs b/rust/chains/tw_aptos/src/entry.rs new file mode 100644 index 00000000000..e2ef01a6e2f --- /dev/null +++ b/rust/chains/tw_aptos/src/entry.rs @@ -0,0 +1,108 @@ +// 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, 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..1407bdd73c0 --- /dev/null +++ b/rust/chains/tw_aptos/src/lib.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod aptos_move_packages; +pub mod aptos_move_types; +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..7fc0fb96eb5 --- /dev/null +++ b/rust/chains/tw_aptos/src/transaction_builder.rs @@ -0,0 +1,290 @@ +// 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, fungible_asset_transfer, 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::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::fungible_asset_transfer(fungible_asset_transfer) => factory + .fungible_asset_transfer( + AccountAddress::from_str(&fungible_asset_transfer.metadata_address) + .map_err(from_account_error) + .into_tw() + .context("Invalid metadata address")?, + AccountAddress::from_str(&fungible_asset_transfer.to) + .map_err(from_account_error) + .into_tw() + .context("Invalid destination address")?, + fungible_asset_transfer.amount, + ), + 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")?; + let abi = + serde_json::from_str::(&input.abi).unwrap_or(serde_json::json!([])); + if is_blind_sign { + let entry_function = EntryFunction::parse_with_abi(v, abi)?; + 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 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 fungible_asset_transfer( + &self, + metadata_address: AccountAddress, + to: AccountAddress, + amount: u64, + ) -> SigningResult { + Ok(self.payload(fungible_asset_transfer(metadata_address, 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..2352b465cd4 --- /dev/null +++ b/rust/chains/tw_aptos/src/transaction_payload.rs @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::aptos_move_types::MoveType; +use crate::constants::{OBJECT_MODULE, OBJECT_STRUCT}; +use crate::serde_helper::vec_bytes; +use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::{ModuleId, StructTag, TypeTag, CORE_CODE_ADDRESS}; +use move_core_types::parser::parse_transaction_argument; +use move_core_types::transaction_argument::TransactionArgument; +use move_core_types::u256; +use move_core_types::value::{MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue}; +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::hex::DecodeHex; +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 { + Self::parse_with_abi(value, json!([])) + } +} + +impl EntryFunction { + pub fn parse_with_abi(value: Value, abi: 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 abi = abi + .as_array() + .ok_or(EntryFunctionError::MissingTypeArguments)?; + let get_abi_str = + |index: usize| -> Option { abi.get(index)?.as_str().map(|s| s.to_string()) }; + + let args = value["arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingArguments)? + .iter() + .enumerate() + .map(|(index, element)| { + let arg_str = element.to_string(); + let arg_str = arg_str.trim_start_matches('"').trim_end_matches('"'); + + if let Some(abi_str) = get_abi_str(index) { + let arg = convert_to_move_value(&abi_str, element.clone())?; + bcs::encode(&arg).map_err(EntryFunctionError::from) + } else { + let arg = parse_transaction_argument(arg_str) + .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 convert_to_move_value(abi_str: &str, element: Value) -> EntryFunctionResult { + let move_type: MoveType = abi_str + .parse() + .map_err(|_| EntryFunctionError::InvalidTypeArguments)?; + let type_tag: TypeTag = move_type + .try_into() + .map_err(|_| EntryFunctionError::InvalidTypeArguments)?; + // Taken from: https://github.com/aptos-labs/aptos-core/blob/aaa3514c8ee4e5d38b89d916eadff7286a42e040/api/types/src/convert.rs#L845-L872 + let layout = match type_tag { + TypeTag::Struct(ref boxed_struct) => { + // The current framework can't handle generics, so we handle this here + if boxed_struct.address == AccountAddress::ONE + && boxed_struct.module.as_ident_str() == OBJECT_MODULE + && boxed_struct.name.as_ident_str() == OBJECT_STRUCT + { + // Objects are just laid out as an address + MoveTypeLayout::Address + } else { + // For all other structs, use their set layout + build_type_layout(&type_tag)? + } + }, + _ => build_type_layout(&type_tag)?, + }; + parse_argument(&layout, element).map_err(|_| EntryFunctionError::InvalidArguments) +} + +fn build_type_layout(t: &TypeTag) -> EncodingResult { + use TypeTag::*; + Ok(match t { + Bool => MoveTypeLayout::Bool, + U8 => MoveTypeLayout::U8, + U64 => MoveTypeLayout::U64, + U128 => MoveTypeLayout::U128, + Address => MoveTypeLayout::Address, + Vector(elem_t) => MoveTypeLayout::Vector(Box::new(build_type_layout(elem_t)?)), + Struct(s) => MoveTypeLayout::Struct(build_struct_layout(s)?), + U16 => MoveTypeLayout::U16, + U32 => MoveTypeLayout::U32, + U256 => MoveTypeLayout::U256, + Signer => Err(EncodingError::InvalidInput)?, + }) +} + +fn build_struct_layout(s: &StructTag) -> EncodingResult { + let type_arguments = s + .type_params + .iter() + .map(build_type_layout) + .collect::>>()?; + if type_arguments.is_empty() { + Ok(MoveStructLayout::WithTypes { + type_: s.clone(), + fields: vec![], + }) + } else { + Ok(MoveStructLayout::Runtime(type_arguments)) + } +} + +fn parse_argument(layout: &MoveTypeLayout, val: Value) -> EncodingResult { + let val_str = val + .to_string() + .trim_start_matches('"') + .trim_end_matches('"') + .to_string(); + Ok(match layout { + MoveTypeLayout::Bool => MoveValue::Bool( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U8 => MoveValue::U8( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U16 => MoveValue::U16( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U32 => MoveValue::U32( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U64 => MoveValue::U64( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U128 => MoveValue::U128( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U256 => MoveValue::U256( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::Address => MoveValue::Address( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::Vector(item_layout) => parse_vector_argument(item_layout.as_ref(), val)?, + MoveTypeLayout::Struct(struct_layout) => parse_struct_argument(struct_layout, val)?, + // Some values, e.g., signer or ones with custom serialization + // (native), are not stored to storage and so we do not expect + // to see them here. + MoveTypeLayout::Signer => { + return Err(EncodingError::InvalidInput); + }, + }) +} + +fn parse_vector_argument(layout: &MoveTypeLayout, val: Value) -> EncodingResult { + if matches!(layout, MoveTypeLayout::U8) { + Ok(MoveValue::Vector( + val.as_str() + .ok_or(EncodingError::InvalidInput)? + .decode_hex() + .map_err(|_| EncodingError::InvalidInput)? + .into_iter() + .map(MoveValue::U8) + .collect::>(), + )) + } else { + let val = trim_if_needed(val)?; + if let Value::Array(list) = val { + let vals = list + .into_iter() + .map(|v| parse_argument(layout, v).map_err(|_| EncodingError::InvalidInput)) + .collect::>()?; + Ok(MoveValue::Vector(vals)) + } else { + Err(EncodingError::InvalidInput) + } + } +} + +// Inspired from: https://github.com/aptos-labs/aptos-core/blob/aaa3514c8ee4e5d38b89d916eadff7286a42e040/api/types/src/convert.rs#L924 +// However, we expect struct with strings and unnamed fields while the original code expects struct with named fields. +// This is because the original code uses a module resolver internally to obtain the struct types and we don't have that here. +// In order to be able to accept that as an API, we need to change the code to accept struct with unnamed fields. +fn parse_struct_argument(layout: &MoveStructLayout, val: Value) -> EncodingResult { + let field_layouts = match layout { + MoveStructLayout::Runtime(fields) => fields, + MoveStructLayout::WithTypes { type_, .. } => { + if is_utf8_string(type_) { + let string = val.as_str().ok_or(EncodingError::InvalidInput)?; + return Ok(new_vm_utf8_string(string)); + } else { + return Err(EncodingError::InvalidInput); + } + }, + _ => return Err(EncodingError::InvalidInput), + }; + let val = trim_if_needed(val)?; + let field_values = if let Value::Array(fields) = val { + fields + } else { + return Err(EncodingError::InvalidInput); + }; + let fields = field_layouts + .iter() + .zip(field_values.into_iter()) + .map(|(field_layout, value)| { + let move_value = parse_argument(field_layout, value)?; + Ok(move_value) + }) + .collect::>()?; + + Ok(MoveValue::Struct(MoveStruct::Runtime(fields))) +} + +fn trim_if_needed(val: Value) -> EncodingResult { + if val.is_string() { + let val_str = val.as_str().ok_or(EncodingError::InvalidInput)?; + let val_str = val_str + .trim_start_matches('"') + .trim_end_matches('"') + .to_string(); + let val: Value = serde_json::from_str(&val_str).map_err(|_| EncodingError::InvalidInput)?; + Ok(val) + } else { + Ok(val) + } +} + +fn is_utf8_string(st: &StructTag) -> bool { + st.address == CORE_CODE_ADDRESS + && st.name.to_string() == "String" + && st.module.to_string() == "string" +} + +fn new_vm_utf8_string(string: &str) -> MoveValue { + let byte_vector = MoveValue::Vector( + string + .as_bytes() + .iter() + .map(|byte| MoveValue::U8(*byte)) + .collect(), + ); + MoveValue::Struct(MoveStruct::Runtime(vec![byte_vector])) +} + +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) => bcs::encode(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()); + + let tp = TransactionPayload::EntryFunction(v); + let serialized = bcs::encode(&tp).unwrap(); + assert_eq!(hex::encode(serialized, false), "02c23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d500349464f0772656c65617365030748e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced9051705636f696e730442555344000748e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced9051705636f696e730344414900079936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a0575696e7473025531000120c95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6"); + } + + #[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(); + let expected_serialized = "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"; + assert_eq!(hex::encode(serialized, false), expected_serialized); + 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); + + // Rebuild a new EntryFunction object from the JSON above + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + let tp = TransactionPayload::EntryFunction(v); + // Serialize the new EntryFunction object and compare with the expected serialized value + let serialized = bcs::encode(&tp).unwrap(); + assert_eq!(hex::encode(serialized, false), expected_serialized); + } + + #[test] + fn test_payload_with_vector_of_u8() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "0x010302" + ] + }); + let abi = r#"[ + "vector" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::>(&v.args[0]).unwrap(); + assert_eq!(v, vec![1u8, 3u8, 2u8]); + } + + #[test] + fn test_payload_with_vector_of_u64() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "[\"1\", \"2\", \"3\"]" + ] + }); + let abi = r#"[ + "vector" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::>(&v.args[0]).unwrap(); + assert_eq!(v, vec![1u64, 2u64, 3u64]); + } + + #[test] + fn test_payload_with_vector_of_vector() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "[\"0x4d61696e204163636f756e74\",\"0x6112\"]" + ] + }); + let abi = r#"[ + "vector>" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::>>(&v.args[0]).unwrap(); + assert_eq!( + hex::encode(v[0].clone(), true), + "0x4d61696e204163636f756e74" + ); + assert_eq!(hex::encode(v[1].clone(), true), "0x6112"); + } + + #[test] + fn test_payload_with_struct_string() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "123" + ] + }); + let abi = r#"[ + "0x1::string::String" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::(&v.args[0]).unwrap(); + assert_eq!(v, "123"); + } + + #[test] + fn test_payload_with_struct() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "[10]" + ] + }); + let abi = r#"[ + "0x1::coin::Coin" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::(&v.args[0]).unwrap(); + assert_eq!(v, 10u64); + } + + fn assert_value_conversion(abi_str: &str, v: V, expected: MoveValue) { + let vm_value = convert_to_move_value(abi_str, json!(v)).unwrap(); + assert_eq!(vm_value, expected); + } + + #[test] + fn test_value_conversion() { + assert_value_conversion("u8", 1i32, MoveValue::U8(1)); + assert_value_conversion("u64", "1", MoveValue::U64(1)); + assert_value_conversion("u128", "1", MoveValue::U128(1)); + assert_value_conversion("bool", true, MoveValue::Bool(true)); + + let address = AccountAddress::from_hex_literal("0x1").unwrap(); + assert_value_conversion("address", "0x1", MoveValue::Address(address)); + + assert_value_conversion("0x1::string::String", "hello", new_vm_utf8_string("hello")); + + assert_value_conversion( + "vector", + "0x0102", + MoveValue::Vector(vec![MoveValue::U8(1), MoveValue::U8(2)]), + ); + assert_value_conversion( + "vector", + ["1", "2"], + MoveValue::Vector(vec![MoveValue::U64(1), MoveValue::U64(2)]), + ); + + assert_value_conversion( + "0x1::guid::ID", // As we do not have access to the module resolver, the types of the struct should be provided as params + ["1", "0x1"], + MoveValue::Struct(MoveStruct::Runtime(vec![ + MoveValue::U64(1), + MoveValue::Address(address), + ])), + ); + } +} diff --git a/rust/chains/tw_aptos/tests/signer.rs b/rust/chains/tw_aptos/tests/signer.rs new file mode 100644 index 00000000000..cdbf9b59160 --- /dev/null +++ b/rust/chains/tw_aptos/tests/signer.rs @@ -0,0 +1,965 @@ +// 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 FungibleAssetTransfer { + metadata_address: String, + to: String, + amount: u64, +} + +pub struct RegisterToken { + coin_type: TypeTag, +} + +pub enum OpsDetails { + RegisterToken(RegisterToken), + LiquidStakingOps(LiquidStakingOperation), + AccountCreation(AccountCreation), + Transfer(Transfer), + TokenTransfer(TokenTransfer), + ImplicitTokenTransfer(TokenTransfer), + NftOps(NftOperation), + FungibleAssetTransfer(FungibleAssetTransfer), +} + +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, + abi: &'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") + } + }, + "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") + } + }, + "fungible_asset_transfer" => { + if let OpsDetails::FungibleAssetTransfer(fungible_asset_transfer) = ops_details.unwrap() + { + Proto::mod_SigningInput::OneOftransaction_payload::fungible_asset_transfer( + Proto::FungibleAssetTransferMessage { + to: fungible_asset_transfer.to.into(), + amount: fungible_asset_transfer.amount, + metadata_address: fungible_asset_transfer.metadata_address.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, + abi: abi.into(), + }; + + 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/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet +#[test] +fn test_aptos_sign_fungible_asset_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "fungible_asset_transfer", + 74, // Sequence number + 1, + 20, + 1736060099, + 100, + "", + "", + Some(OpsDetails::FungibleAssetTransfer(FungibleAssetTransfer { + metadata_address: "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12" + .to_string(), + to: "0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52".to_string(), + amount: 100000000, + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001", // Expected raw transaction bytes + "2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "1736060099", + "gas_unit_price": "100", + "max_gas_amount": "20", + "payload": { + "arguments": ["0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12","0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52", "100000000"], + "function": "0x1::primary_fungible_store::transfer", + "type": "entry_function_payload", + "type_arguments": ["0x1::fungible_asset::Metadata"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "74", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", + "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/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/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet +#[test] +fn test_aptos_blind_sign_with_abi() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 69, // Sequence number + 1, + 50000, + 1735902711, + 100, + r#"{ + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + }"#, + r#"[ + "vector", + "u64", + "bool" + ]"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001", // Expected raw transaction bytes + "13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "1735902711", + "gas_unit_price": "100", + "max_gas_amount": "50000", + "payload": { + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "69", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304", + "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..36e66459790 --- /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..6552c0e6bd0 --- /dev/null +++ b/rust/chains/tw_binance/src/entry.rs @@ -0,0 +1,96 @@ +// 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, 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..7173bbca9c8 --- /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..861615d0a16 --- /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..a668e24ca09 --- /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..3cce93cf298 --- /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..4917a852ca1 --- /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 transfer_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 typed 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(transfer_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) => { + transfer_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/transfer_out_order.rs b/rust/chains/tw_binance/src/transaction/message/transfer_out_order.rs new file mode 100644 index 00000000000..de44ede9de9 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/transfer_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..ce4101b5407 --- /dev/null +++ b/rust/chains/tw_bitcoin/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_bitcoin" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitcoin = { version = "0.30.0", features = ["rand-std", "serde"] } +itertools = "0.10.5" +lazy_static = "1.4.0" +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/babylon/conditions.rs b/rust/chains/tw_bitcoin/src/babylon/conditions.rs new file mode 100644 index 00000000000..e31b97ddb92 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/conditions.rs @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use tw_hash::H32; +use tw_keypair::schnorr; +use tw_utxo::script::standard_script::opcodes::*; +use tw_utxo::script::Script; + +const VERIFY: bool = true; +const NO_VERIFY: bool = false; + +/// https://github.com/babylonchain/babylon/blob/dev/docs/transaction-impl-spec.md#op_return-output-description +/// ```txt +/// OP_RETURN OP_DATA_71 +/// ``` +pub fn new_op_return_script( + tag: &H32, + version: u8, + staker_key: &schnorr::XOnlyPublicKey, + finality_provider_key: &schnorr::XOnlyPublicKey, + locktime: u16, +) -> Script { + let mut buf = Vec::with_capacity(71); + buf.extend_from_slice(tag.as_slice()); + buf.push(version); + buf.extend_from_slice(staker_key.bytes().as_slice()); + buf.extend_from_slice(finality_provider_key.bytes().as_slice()); + buf.extend_from_slice(&locktime.to_be_bytes()); + + let mut s = Script::new(); + s.push(OP_RETURN); + s.push_slice(&buf); + s +} + +/// The timelock path locks the staker's Bitcoin for a pre-determined number of Bitcoin blocks. +/// https://github.com/babylonchain/babylon/blob/dev/docs/staking-script.md#1-timelock-path +/// +/// ```txt +/// OP_CHECKSIGVERIFY OP_CHECKSEQUENCEVERIFY +/// ``` +pub fn new_timelock_script(staker_key: &schnorr::XOnlyPublicKey, locktime: u16) -> Script { + let mut s = Script::with_capacity(64); + append_single_sig(&mut s, staker_key, VERIFY); + s.push_int(locktime as i64); + s.push(OP_CHECKSEQUENCEVERIFY); + s +} + +/// The unbonding path allows the staker to on-demand unlock their locked Bitcoin before the timelock expires. +/// https://github.com/babylonchain/babylon/blob/dev/docs/staking-script.md#2-unbonding-path +/// +/// ```txt +/// OP_CHECKSIGVERIFY +/// OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD +/// OP_NUMEQUAL +/// ``` +pub fn new_unbonding_script( + staker_key: &schnorr::XOnlyPublicKey, + covenants: &MultiSigOrderedKeys, +) -> Script { + let mut s = Script::with_capacity(64); + append_single_sig(&mut s, staker_key, VERIFY); + // Covenant multisig is always last in script so we do not run verify and leave + // last value on the stack. If we do not leave at least one element on the stack + // script will always error. + append_multi_sig( + &mut s, + covenants.public_keys_ordered(), + covenants.quorum(), + NO_VERIFY, + ); + s +} + +/// The slashing path is utilized for punishing finality providers and their delegators in the case of double signing. +/// https://github.com/babylonchain/babylon/blob/dev/docs/staking-script.md#3-slashing-path +/// +/// ```txt +/// OP_CHECKSIGVERIFY +/// OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD +/// 1 OP_NUMEQUAL +/// OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD +/// OP_NUMEQUAL +/// ``` +pub fn new_slashing_script( + staker_key: &schnorr::XOnlyPublicKey, + finality_providers_keys: &MultiSigOrderedKeys, + covenants: &MultiSigOrderedKeys, +) -> Script { + let mut s = Script::with_capacity(64); + append_single_sig(&mut s, staker_key, VERIFY); + // We need to run verify to clear the stack, as finality provider multisig is in the middle of the script. + append_multi_sig( + &mut s, + finality_providers_keys.public_keys_ordered(), + finality_providers_keys.quorum(), + VERIFY, + ); + // Covenant multisig is always last in script so we do not run verify and leave + // last value on the stack. If we do not leave at least one element on the stack + // script will always error. + append_multi_sig( + &mut s, + covenants.public_keys_ordered(), + covenants.quorum(), + NO_VERIFY, + ); + s +} + +fn append_single_sig(dst: &mut Script, key: &schnorr::XOnlyPublicKey, verify: bool) { + dst.push_slice(key.bytes().as_slice()); + if verify { + dst.push(OP_CHECKSIGVERIFY); + } else { + dst.push(OP_CHECKSIG); + } +} + +/// Creates a multisig script with given keys and signer threshold to +/// successfully execute script. +/// It validates whether threshold is not greater than number of keys. +/// If there is only one key provided it will return single key sig script. +/// Note: It is up to the caller to ensure that the keys are unique and sorted. +fn append_multi_sig( + dst: &mut Script, + pubkeys: &[schnorr::XOnlyPublicKey], + quorum: u32, + verify: bool, +) { + if pubkeys.len() == 1 { + return append_single_sig(dst, &pubkeys[0], verify); + } + + for (i, pk_xonly) in pubkeys.iter().enumerate() { + dst.push_slice(pk_xonly.bytes().as_slice()); + if i == 0 { + dst.push(OP_CHECKSIG); + } else { + dst.push(OP_CHECKSIGADD); + } + } + + dst.push_int(quorum as i64); + if verify { + dst.push(OP_NUMEQUALVERIFY); + } else { + dst.push(OP_NUMEQUAL); + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/mod.rs b/rust/chains/tw_bitcoin/src/babylon/mod.rs new file mode 100644 index 00000000000..c59063ae77d --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/mod.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod conditions; +pub mod multi_sig_ordered; +pub mod proto_builder; +pub mod spending_data; +pub mod spending_info; +pub mod tx_builder; diff --git a/rust/chains/tw_bitcoin/src/babylon/multi_sig_ordered.rs b/rust/chains/tw_bitcoin/src/babylon/multi_sig_ordered.rs new file mode 100644 index 00000000000..ef7424bdbf2 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/multi_sig_ordered.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use itertools::Itertools; +use std::collections::BTreeMap; +use tw_coin_entry::error::prelude::*; +use tw_keypair::schnorr; +use tw_utxo::signature::BitcoinSchnorrSignature; + +type OptionalSignature = Option; +type PubkeySigMap = BTreeMap; + +pub struct MultiSigOrderedKeys { + pks: Vec, + quorum: u32, +} + +impl MultiSigOrderedKeys { + /// Sort the keys in lexicographical order. + pub fn new(mut pks: Vec, quorum: u32) -> SigningResult { + if pks.is_empty() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("No public keys provided"); + } + + if pks.len() < quorum as usize { + return SigningError::err(SigningErrorType::Error_invalid_params).context( + "Required number of valid signers is greater than number of provided keys", + ); + } + + // TODO it's not optimal to use a `HashSet` because the keys are sorted already. + if !pks.iter().all_unique() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Public keys must be unique"); + } + + pks.sort(); + Ok(MultiSigOrderedKeys { pks, quorum }) + } + + pub fn public_keys_ordered(&self) -> &[schnorr::XOnlyPublicKey] { + &self.pks + } + + pub fn quorum(&self) -> u32 { + self.quorum + } + + pub fn with_partial_signatures<'a, I>(self, sigs: I) -> SigningResult + where + I: IntoIterator, + { + let mut pk_sig_map = MultiSigOrdered::checked(self.pks, self.quorum); + pk_sig_map.set_partial_signatures(sigs)?; + pk_sig_map.check_quorum()?; + Ok(pk_sig_map) + } +} + +#[derive(Clone, Debug)] +pub struct MultiSigOrdered { + pk_sig_map: PubkeySigMap, + quorum: u32, +} + +impl MultiSigOrdered { + /// `pk_sig_map` and `quorum` must be checked at [`MultiSigOrderedKeys::new`] already. + fn checked(pks: Vec, quorum: u32) -> Self { + let mut pk_sig_map = PubkeySigMap::new(); + + // Initialize the map with public keys and null signatures first. + for pk in pks { + pk_sig_map.insert(pk, None); + } + + MultiSigOrdered { pk_sig_map, quorum } + } + + fn set_partial_signatures<'a, I>(&mut self, sigs: I) -> SigningResult<()> + where + I: IntoIterator, + { + // Set the signatures for the specific public keys. + // There can be less signatures than public keys, but not less than `quorum`. + for (pk, sig) in sigs { + // Find the signature of the corresponding public key. + let pk_sig = self + .pk_sig_map + .get_mut(pk) + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("Signature provided for an unknown public key")?; + + // Only one signature per public key allowed. + if pk_sig.is_some() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Duplicate public key"); + } + *pk_sig = Some(sig.clone()); + } + Ok(()) + } + + fn check_quorum(&self) -> SigningResult<()> { + let sig_num = self.pk_sig_map.values().filter(|sig| sig.is_some()).count(); + if sig_num < self.quorum as usize { + return SigningError::err(SigningErrorType::Error_invalid_params).context(format!( + "Number of signatures '{sig_num}' is less than quorum '{}'", + self.quorum + )); + } + Ok(()) + } + + /// Get signatures sorted by corresponding public keys in reverse lexicographical order + /// because the script interpreter will pop the left-most byte-array as the first stack element, + /// the second-left-most byte array as the second stack element, and so on. + /// + /// In other words, + /// `[SigN] [SigN-1] ... [Sig0]` + /// where the list `Sig0 ... SigN` are the Schnorr signatures corresponding to the public keys `Pk0 ... PkN`. + /// + /// https://gnusha.org/pi/bitcoindev/20220820134850.ofvz7225zwcyffit@artanis (=== Spending K-of-N Multisig outputs ===) + pub fn get_signatures_reverse_order(&self) -> Vec { + self.pk_sig_map + .iter() + .rev() + .map(|(_pk, sig)| sig.clone()) + .collect() + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/proto_builder/mod.rs b/rust/chains/tw_bitcoin/src/babylon/proto_builder/mod.rs new file mode 100644 index 00000000000..205f969f0eb --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/mod.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use crate::babylon::tx_builder::BabylonStakingParams; +use std::borrow::Cow; +use tw_coin_entry::error::prelude::*; +use tw_keypair::schnorr; +use tw_proto::BabylonStaking::Proto; +use tw_utxo::sighash::SighashType; +use tw_utxo::signature::BitcoinSchnorrSignature; + +pub mod output_protobuf; +pub mod utxo_protobuf; + +/// We always require only one finality provider to sign, +/// even if there are multiple finality providers in the script. +const FINALITY_PROVIDERS_QUORUM: u32 = 1; + +pub fn staking_params_from_proto( + params: &Option, +) -> SigningResult { + let params = params + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("No 'StakingInfo' params provided")?; + + let staker = schnorr::PublicKey::try_from(params.staker_public_key.as_ref()) + .into_tw() + .context("Invalid stakerPublicKey")?; + let staking_locktime: u16 = params + .staking_time + .try_into() + .tw_err(SigningErrorType::Error_invalid_params) + .context("stakingTime cannot be greater than 65535")?; + + let covenants_pks = parse_schnorr_pks(¶ms.covenant_committee_public_keys) + .context("Invalid covenantCommitteePublicKeys")?; + let covenants = MultiSigOrderedKeys::new(covenants_pks, params.covenant_quorum) + .context("Invalid covenantCommitteePublicKeys")?; + + let finality_provider = parse_schnorr_pk(¶ms.finality_provider_public_key) + .context("Invalid finalityProviderPublicKey")?; + let finality_providers = + MultiSigOrderedKeys::new(vec![finality_provider], FINALITY_PROVIDERS_QUORUM) + .context("Invalid finalityProviderPublicKey")?; + + Ok(BabylonStakingParams { + staker, + staking_locktime, + finality_providers, + covenants, + }) +} + +pub fn parse_schnorr_pk(bytes: T) -> SigningResult +where + T: AsRef<[u8]>, +{ + schnorr::XOnlyPublicKey::try_from(bytes.as_ref()).into_tw() +} + +pub fn parse_schnorr_pks(pks: &[Cow<[u8]>]) -> SigningResult> { + pks.iter().map(parse_schnorr_pk).collect() +} + +pub fn parse_schnorr_pubkey_sig( + pubkey_sig: &Proto::PublicKeySignature, + sighash_ty: SighashType, +) -> SigningResult<(schnorr::XOnlyPublicKey, BitcoinSchnorrSignature)> { + let pk = parse_schnorr_pk(pubkey_sig.public_key.as_ref())?; + let sig = schnorr::Signature::try_from(pubkey_sig.signature.as_ref()) + .tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid signature")?; + let btc_sign = BitcoinSchnorrSignature::new(sig, sighash_ty)?; + Ok((pk, btc_sign)) +} diff --git a/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs b/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs new file mode 100644 index 00000000000..313bd63f3fd --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::proto_builder::{parse_schnorr_pk, staking_params_from_proto}; +use crate::babylon::tx_builder::output::BabylonOutputBuilder; +use crate::modules::tx_builder::output_protobuf::OutputProtobuf; +use tw_coin_entry::error::prelude::*; +use tw_hash::H32; +use tw_proto::BabylonStaking::Proto; +use tw_utxo::context::UtxoContext; +use tw_utxo::transaction::standard_transaction::TransactionOutput; + +pub trait BabylonOutputProtobuf { + fn babylon_staking( + &self, + timelock: &Proto::mod_OutputBuilder::StakingOutput, + ) -> SigningResult; + + fn babylon_staking_op_return( + &self, + timelock: &Proto::mod_OutputBuilder::OpReturn, + ) -> SigningResult; + + fn babylon_unbonding( + &self, + unbonding: &Proto::mod_OutputBuilder::UnbondingOutput, + ) -> SigningResult; +} + +impl BabylonOutputProtobuf for OutputProtobuf<'_, Context> { + fn babylon_staking( + &self, + staking: &Proto::mod_OutputBuilder::StakingOutput, + ) -> SigningResult { + let params = staking_params_from_proto(&staking.params)?; + self.prepare_builder()?.babylon_staking(params) + } + + fn babylon_staking_op_return( + &self, + op_return: &Proto::mod_OutputBuilder::OpReturn, + ) -> SigningResult { + let tag = H32::try_from(op_return.tag.as_ref()) + .tw_err(SigningErrorType::Error_invalid_params) + .context("Expected exactly 4 bytes tag")?; + let staker = + parse_schnorr_pk(&op_return.staker_public_key).context("Invalid stakerPublicKey")?; + let staking_locktime: u16 = op_return + .staking_time + .try_into() + .tw_err(SigningErrorType::Error_invalid_params) + .context("stakingTime cannot be greater than 65535")?; + let finality_provider = &parse_schnorr_pk(&op_return.finality_provider_public_key) + .context("Invalid finalityProviderPublicKeys")?; + + Ok(self.prepare_builder()?.babylon_staking_op_return( + &tag, + &staker, + finality_provider, + staking_locktime, + )) + } + + fn babylon_unbonding( + &self, + unbonding: &Proto::mod_OutputBuilder::UnbondingOutput, + ) -> SigningResult { + let params = staking_params_from_proto(&unbonding.params)?; + self.prepare_builder()?.babylon_unbonding(params) + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs b/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs new file mode 100644 index 00000000000..fc9d88e15ef --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::proto_builder::{parse_schnorr_pubkey_sig, staking_params_from_proto}; +use crate::babylon::tx_builder::utxo::BabylonUtxoBuilder; +use crate::modules::tx_builder::utxo_protobuf::UtxoProtobuf; +use tw_coin_entry::error::prelude::*; +use tw_proto::BabylonStaking::Proto; +use tw_utxo::context::UtxoContext; +use tw_utxo::transaction::standard_transaction::TransactionInput; +use tw_utxo::transaction::UtxoToSign; + +pub trait BabylonUtxoProtobuf { + fn babylon_staking_timelock( + &self, + timelock: &Proto::mod_InputBuilder::StakingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_staking_unbonding( + &self, + unbonding: &Proto::mod_InputBuilder::StakingUnbondingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_staking_slashing( + &self, + slashing: &Proto::mod_InputBuilder::StakingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_unbonding_timelock( + &self, + timelock: &Proto::mod_InputBuilder::UnbondingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_unbonding_slashing( + &self, + slashing: &Proto::mod_InputBuilder::UnbondingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; +} + +impl BabylonUtxoProtobuf for UtxoProtobuf<'_, Context> { + fn babylon_staking_timelock( + &self, + timelock: &Proto::mod_InputBuilder::StakingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let params = staking_params_from_proto(&timelock.params)?; + self.prepare_builder()? + .babylon_staking_timelock_path(params) + } + + fn babylon_staking_unbonding( + &self, + unbonding: &Proto::mod_InputBuilder::StakingUnbondingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let params = staking_params_from_proto(&unbonding.params)?; + let sighash_ty = self.sighash_ty()?; + + let covenant_signatures = unbonding + .covenant_committee_signatures + .iter() + .map(|pk_sig| parse_schnorr_pubkey_sig(pk_sig, sighash_ty)) + .collect::>>()?; + self.prepare_builder()? + .babylon_staking_unbonding_path(params, &covenant_signatures) + } + + fn babylon_staking_slashing( + &self, + _slashing: &Proto::mod_InputBuilder::StakingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + SigningError::err(SigningErrorType::Error_not_supported) + .context("'babylonStakingSlashing' is not supported at the moment") + } + + fn babylon_unbonding_timelock( + &self, + timelock: &Proto::mod_InputBuilder::UnbondingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let params = staking_params_from_proto(&timelock.params)?; + self.prepare_builder()? + .babylon_unbonding_timelock_path(params) + } + + fn babylon_unbonding_slashing( + &self, + _slashing: &Proto::mod_InputBuilder::UnbondingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + SigningError::err(SigningErrorType::Error_not_supported) + .context("'babylonUnbondingSlashing' is not supported at the moment") + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/spending_data.rs b/rust/chains/tw_bitcoin/src/babylon/spending_data.rs new file mode 100644 index 00000000000..5b50ad66e0b --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/spending_data.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrdered; +use tw_memory::Data; +use tw_utxo::script::standard_script::claims; +use tw_utxo::script::Script; +use tw_utxo::signature::BitcoinSchnorrSignature; +use tw_utxo::spending_data::{SchnorrSpendingDataConstructor, SpendingData}; + +#[derive(Clone, Debug)] +pub struct BabylonUnbondingPath { + unbonding_script: Script, + control_block: Data, + /// Signatures signed by covenant committees. + /// Sorted by covenant committees public keys in reverse order. + covenant_committee_signatures: MultiSigOrdered, +} + +impl BabylonUnbondingPath { + pub fn new( + unbonding_script: Script, + control_block: Data, + covenant_committee_signatures: MultiSigOrdered, + ) -> Self { + BabylonUnbondingPath { + unbonding_script, + control_block, + covenant_committee_signatures, + } + } +} + +impl SchnorrSpendingDataConstructor for BabylonUnbondingPath { + fn get_spending_data(&self, sig: &BitcoinSchnorrSignature) -> SpendingData { + // Covenant committee signatures are sorted by corresponding public keys in reverse lexicographical order. + // That's because the script interpreter will pop the left-most byte-array as the first stack element, + // the second-left-most byte array as the second stack element, and so on. + let mut unbonding_sigs = self + .covenant_committee_signatures + .get_signatures_reverse_order(); + // User's signature is always last. + unbonding_sigs.push(Some(sig.clone())); + + SpendingData { + script_sig: Script::default(), + witness: claims::new_p2tr_script_path( + &unbonding_sigs, + self.unbonding_script.clone(), + self.control_block.clone(), + ), + } + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/spending_info.rs b/rust/chains/tw_bitcoin/src/babylon/spending_info.rs new file mode 100644 index 00000000000..9257803e34d --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/spending_info.rs @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::conditions; +use crate::babylon::tx_builder::BabylonStakingParams; +use bitcoin::hashes::Hash; +use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::*; +use tw_hash::{H256, H264}; +use tw_keypair::schnorr; +use tw_utxo::script::Script; + +lazy_static! { + /// Point with unknown discrete logarithm defined in: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + /// using it as internal public key effectively disables taproot key spends. + pub static ref UNSPENDABLE_KEY_PATH_BYTES: H264 = + H264::from("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"); + pub static ref UNSPENDABLE_KEY_PATH: schnorr::PublicKey = + schnorr::PublicKey::try_from(UNSPENDABLE_KEY_PATH_BYTES.as_slice()) + .expect("Expected a valid unspendable key path"); + pub static ref UNSPENDABLE_KEY_PATH_XONLY: bitcoin::key::UntweakedPublicKey = + bitcoin::key::UntweakedPublicKey::from_slice(UNSPENDABLE_KEY_PATH.x_only().bytes().as_slice()) + .expect("Expected a valid unspendable key path"); +} + +pub struct StakingSpendInfo { + timelock_script: Script, + unbonding_script: Script, + slashing_script: Script, + spend_info: bitcoin::taproot::TaprootSpendInfo, +} + +impl StakingSpendInfo { + pub fn new(params: &BabylonStakingParams) -> SigningResult { + let staker_xonly = params.staker.x_only(); + + let timelock_script = + conditions::new_timelock_script(&staker_xonly, params.staking_locktime); + let unbonding_script = conditions::new_unbonding_script(&staker_xonly, ¶ms.covenants); + let slashing_script = conditions::new_slashing_script( + &staker_xonly, + ¶ms.finality_providers, + ¶ms.covenants, + ); + + // IMPORTANT - order and leaf depths are important! + let spend_info = bitcoin::taproot::TaprootBuilder::new() + .add_leaf(2, timelock_script.clone().into()) + .expect("Leaf added at a valid depth") + .add_leaf(2, unbonding_script.clone().into()) + .expect("Leaf added at a valid depth") + .add_leaf(1, slashing_script.clone().into()) + .expect("Leaf added at a valid depth") + .finalize(secp256k1::SECP256K1, *UNSPENDABLE_KEY_PATH_XONLY) + .expect("Expected a valid Taproot tree"); + + Ok(StakingSpendInfo { + timelock_script, + unbonding_script, + slashing_script, + spend_info, + }) + } + + pub fn merkle_root(&self) -> SigningResult { + merkle_root(&self.spend_info) + } + + pub fn timelock_script(&self) -> &Script { + &self.timelock_script + } + + pub fn unbonding_script(&self) -> &Script { + &self.unbonding_script + } + + pub fn slashing_script(&self) -> &Script { + &self.slashing_script + } + + pub fn timelock_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.timelock_script) + } + + pub fn unbonding_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.unbonding_script) + } + + pub fn slashing_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.slashing_script) + } +} + +pub struct UnbondingSpendInfo { + timelock_script: Script, + slashing_script: Script, + spend_info: bitcoin::taproot::TaprootSpendInfo, +} + +impl UnbondingSpendInfo { + pub fn new(params: &BabylonStakingParams) -> SigningResult { + let staker_xonly = params.staker.x_only(); + + let timelock_script = + conditions::new_timelock_script(&staker_xonly, params.staking_locktime); + let slashing_script = conditions::new_slashing_script( + &staker_xonly, + ¶ms.finality_providers, + ¶ms.covenants, + ); + + // IMPORTANT - order and leaf depths are important! + let spend_info = bitcoin::taproot::TaprootBuilder::new() + .add_leaf(1, slashing_script.clone().into()) + .expect("Leaf added at a valid depth") + .add_leaf(1, timelock_script.clone().into()) + .expect("Leaf added at a valid depth") + .finalize(secp256k1::SECP256K1, *UNSPENDABLE_KEY_PATH_XONLY) + .expect("Expected a valid Taproot tree"); + + Ok(UnbondingSpendInfo { + timelock_script, + slashing_script, + spend_info, + }) + } + + pub fn merkle_root(&self) -> SigningResult { + merkle_root(&self.spend_info) + } + + pub fn timelock_script(&self) -> &Script { + &self.timelock_script + } + + pub fn slashing_script(&self) -> &Script { + &self.slashing_script + } + + pub fn timelock_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.timelock_script) + } + + pub fn slashing_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.slashing_script) + } +} + +fn control_block( + spend_info: &bitcoin::taproot::TaprootSpendInfo, + script: &Script, +) -> SigningResult { + let script = bitcoin::script::ScriptBuf::from_bytes(script.to_vec()); + spend_info + .control_block(&(script, bitcoin::taproot::LeafVersion::TapScript)) + .or_tw_err(SigningErrorType::Error_internal) + .context("'TaprootSpendInfo::control_block' is None") +} + +fn merkle_root(spend_info: &bitcoin::taproot::TaprootSpendInfo) -> SigningResult { + spend_info + .merkle_root() + .map(|root| H256::from(root.to_byte_array())) + .or_tw_err(SigningErrorType::Error_internal) + .context("No merkle root of the Babylon Staking transaction spend info") +} diff --git a/rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs b/rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs new file mode 100644 index 00000000000..f2ad565b831 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use tw_keypair::schnorr; + +pub mod output; +pub mod utxo; + +/// Unbonding parameters are the same as Staking except `staking_locktime` means an unbonding timelock. +pub type BabylonUnbondingParams = BabylonStakingParams; + +pub struct BabylonStakingParams { + pub staker: schnorr::PublicKey, + pub staking_locktime: u16, + pub finality_providers: MultiSigOrderedKeys, + pub covenants: MultiSigOrderedKeys, +} diff --git a/rust/chains/tw_bitcoin/src/babylon/tx_builder/output.rs b/rust/chains/tw_bitcoin/src/babylon/tx_builder/output.rs new file mode 100644 index 00000000000..e8b9d61ba1f --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/tx_builder/output.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon; +use crate::babylon::tx_builder::{BabylonStakingParams, BabylonUnbondingParams}; +use tw_coin_entry::error::prelude::*; +use tw_hash::H32; +use tw_keypair::schnorr; +use tw_utxo::script::standard_script::conditions; +use tw_utxo::transaction::standard_transaction::builder::OutputBuilder; +use tw_utxo::transaction::standard_transaction::TransactionOutput; + +pub const VERSION: u8 = 0; + +/// An extension of the [`OutputBuilder`] with Babylon BTC Staking outputs. +pub trait BabylonOutputBuilder: Sized { + /// Create a Staking Output. + fn babylon_staking(self, params: BabylonStakingParams) -> SigningResult; + + /// Creates an OP_RETURN output used to identify the staking transaction among other transactions in the Bitcoin ledger. + fn babylon_staking_op_return( + self, + tag: &H32, + staker_key: &schnorr::XOnlyPublicKey, + finality_provider_key: &schnorr::XOnlyPublicKey, + staking_locktime: u16, + ) -> TransactionOutput; + + fn babylon_unbonding(self, params: BabylonUnbondingParams) -> SigningResult; +} + +impl BabylonOutputBuilder for OutputBuilder { + fn babylon_staking(self, params: BabylonStakingParams) -> SigningResult { + let spend_info = babylon::spending_info::StakingSpendInfo::new(¶ms)?; + let merkle_root = spend_info.merkle_root()?; + + Ok(TransactionOutput { + value: self.get_amount(), + script_pubkey: conditions::new_p2tr_script_path( + // Using an unspendable key as a P2TR internal public key effectively disables taproot key spends. + &babylon::spending_info::UNSPENDABLE_KEY_PATH.compressed(), + &merkle_root, + ), + }) + } + + fn babylon_staking_op_return( + self, + tag: &H32, + staker_key: &schnorr::XOnlyPublicKey, + finality_provider_key: &schnorr::XOnlyPublicKey, + staking_locktime: u16, + ) -> TransactionOutput { + let op_return = babylon::conditions::new_op_return_script( + tag, + VERSION, + staker_key, + finality_provider_key, + staking_locktime, + ); + TransactionOutput { + value: self.get_amount(), + script_pubkey: op_return, + } + } + + fn babylon_unbonding(self, params: BabylonUnbondingParams) -> SigningResult { + let spend_info = babylon::spending_info::UnbondingSpendInfo::new(¶ms)?; + let merkle_root = spend_info.merkle_root()?; + + Ok(TransactionOutput { + value: self.get_amount(), + script_pubkey: conditions::new_p2tr_script_path( + // Using an unspendable key as a P2TR internal public key effectively disables taproot key spends. + &babylon::spending_info::UNSPENDABLE_KEY_PATH.compressed(), + &merkle_root, + ), + }) + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs b/rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs new file mode 100644 index 00000000000..293dc160241 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon; +use crate::babylon::spending_data; +use crate::babylon::spending_info::UNSPENDABLE_KEY_PATH; +use crate::babylon::tx_builder::{BabylonStakingParams, BabylonUnbondingParams}; +use tw_coin_entry::error::prelude::*; +use tw_keypair::schnorr; +use tw_utxo::signature::BitcoinSchnorrSignature; +use tw_utxo::spending_data::SpendingDataConstructor; +use tw_utxo::transaction::standard_transaction::builder::UtxoBuilder; +use tw_utxo::transaction::standard_transaction::TransactionInput; +use tw_utxo::transaction::UtxoToSign; + +/// An extension of the [`UtxoBuilder`] with Babylon BTC Staking outputs. +pub trait BabylonUtxoBuilder: Sized { + /// Spend a Staking Output via timelock path (staking time expired). + /// In other words, create a Withdraw transaction. + fn babylon_staking_timelock_path( + self, + params: BabylonStakingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + /// Spend a Staking Output via unbonding path. + /// In other words, create an Unbonding transaction. + fn babylon_staking_unbonding_path( + self, + params: BabylonStakingParams, + covenant_committee_signatures: &[(schnorr::XOnlyPublicKey, BitcoinSchnorrSignature)], + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_unbonding_timelock_path( + self, + params: BabylonUnbondingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; +} + +impl BabylonUtxoBuilder for UtxoBuilder { + fn babylon_staking_timelock_path( + self, + params: BabylonStakingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let spend_info = babylon::spending_info::StakingSpendInfo::new(¶ms)?; + + let control_block = spend_info.timelock_control_block()?.serialize(); + let merkle_root = spend_info.merkle_root()?; + let timelock_script = spend_info.timelock_script().clone(); + + self.p2tr_script_path() + .reveal_script_pubkey(timelock_script) + // Staker is responsible to sign the UTXO. + .spender_public_key(¶ms.staker) + // Babylon Staking or Unbonding output was created using an unspendable internal public key, + // that means taproot key spends is disabled. + .restore_prevout_script_pubkey(&UNSPENDABLE_KEY_PATH, &merkle_root) + .control_block(control_block) + .build() + } + + fn babylon_staking_unbonding_path( + self, + params: BabylonStakingParams, + covenant_committee_signatures: &[(schnorr::XOnlyPublicKey, BitcoinSchnorrSignature)], + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let spend_info = babylon::spending_info::StakingSpendInfo::new(¶ms)?; + let signatures = params + .covenants + .with_partial_signatures(covenant_committee_signatures)?; + let unbonding_script = spend_info.unbonding_script(); + + let unbonding_control_block = spend_info.unbonding_control_block()?.serialize(); + let spending_data_ctor = + SpendingDataConstructor::schnorr(spending_data::BabylonUnbondingPath::new( + unbonding_script.clone(), + unbonding_control_block.clone(), + signatures, + )); + + let merkle_root = spend_info.merkle_root()?; + + self.p2tr_script_path() + .reveal_script_pubkey(unbonding_script.clone()) + // Staker is responsible to sign the UTXO. + .spender_public_key(¶ms.staker) + // Babylon Staking or Unbonding output was created using an unspendable internal public key, + // that means taproot key spends is disabled. + .restore_prevout_script_pubkey(&UNSPENDABLE_KEY_PATH, &merkle_root) + .control_block(unbonding_control_block) + // For Babylon Unbonding path we use a custom spending data constructor. + .custom_spending_data_ctor(spending_data_ctor) + .build() + } + + fn babylon_unbonding_timelock_path( + self, + params: BabylonUnbondingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let spend_info = babylon::spending_info::UnbondingSpendInfo::new(¶ms)?; + + let control_block = spend_info.timelock_control_block()?.serialize(); + let merkle_root = spend_info.merkle_root()?; + let timelock_script = spend_info.timelock_script().clone(); + + self.p2tr_script_path() + .reveal_script_pubkey(timelock_script.clone()) + // Staker is responsible to sign the UTXO. + .spender_public_key(¶ms.staker) + // Babylon Staking or Unbonding output was created using an unspendable internal public key, + // that means taproot key spends is disabled. + .restore_prevout_script_pubkey(&UNSPENDABLE_KEY_PATH, &merkle_root) + .control_block(control_block) + .build() + } +} diff --git a/rust/chains/tw_bitcoin/src/context.rs b/rust/chains/tw_bitcoin/src/context.rs new file mode 100644 index 00000000000..5b65c0d0281 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/context.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::protobuf_builder::standard_protobuf_builder::StandardProtobufBuilder; +use crate::modules::protobuf_builder::ProtobufBuilder; +use crate::modules::psbt_request::standard_psbt_request_builder::StandardPsbtRequestBuilder; +use crate::modules::psbt_request::PsbtRequestBuilder; +use crate::modules::signing_request::standard_signing_request::StandardSigningRequestBuilder; +use crate::modules::signing_request::SigningRequestBuilder; +use tw_coin_entry::error::prelude::SigningResult; +use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress; +use tw_utxo::context::{AddressPrefixes, UtxoContext}; +use tw_utxo::fee::fee_estimator::StandardFeeEstimator; +use tw_utxo::script::Script; +use tw_utxo::transaction::standard_transaction::Transaction; + +/// A set of associated modules different from a chain to chain. +/// The modules are mainly used to generate a signing request from a [`BitcoinV2::Proto::SigningInput`], +/// and generate a [`Utxo::Proto::Transaction`] output when the transaction is signed. +pub trait BitcoinSigningContext: UtxoContext + Sized { + type SigningRequestBuilder: SigningRequestBuilder; + type ProtobufBuilder: ProtobufBuilder; + type PsbtRequestBuilder: PsbtRequestBuilder; +} + +#[derive(Default)] +pub struct StandardBitcoinContext; + +impl UtxoContext for StandardBitcoinContext { + type Address = StandardBitcoinAddress; + type Transaction = Transaction; + type FeeEstimator = StandardFeeEstimator; + + fn addr_to_script_pubkey( + addr: &Self::Address, + prefixes: AddressPrefixes, + ) -> SigningResult + + + + + + 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 3418220ea7d..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeAeternity}; } - 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..47b6ff36aea 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,10 +11,11 @@ #include using namespace TW; -using namespace TW::Aeternity; -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); +namespace TW::Aeternity { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); std::string sender_id = input.from_address(); std::string recipient_id = input.to_address(); std::string payload = input.payload(); @@ -29,15 +27,15 @@ 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 auto msg = buildMessageToSign(txRlp); /// sign ed25519 - auto sigRaw = privateKey.sign(msg, TWCurveED25519); - auto signature = Identifiers::prefixSignature + Base58::bitcoin.encodeCheck(sigRaw); + auto sigRaw = privateKey.sign(msg); + 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 416531544b5..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,15 +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}); - } else { - return Ethereum::RLP::encode(value); - } -} \ No newline at end of file +} // 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 c30ee168d7a..9c979ddc9e0 100644 --- a/src/Aion/Entry.cpp +++ b/src/Aion/Entry.cpp @@ -1,27 +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(); } -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) { + 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 a369e5b39ea..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 const std::vector coinTypes() const { return {TWCoinTypeAion}; } - 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::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..b79afa26bed 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 { - 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())); +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); + auto transaction = Signer::buildTransaction(input); Signer::sign(key, transaction); auto output = Proto::SigningOutput(); @@ -37,7 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { auto encoded = transaction.encode(); auto hashData = Hash::blake2b(encoded, 32); - auto hashSignature = privateKey.sign(hashData, TWCurveED25519); + auto hashSignature = privateKey.sign(hashData); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; // Aion signature = pubKeyBytes + signatureBytes @@ -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 new file mode 100644 index 00000000000..f8644988cbc --- /dev/null +++ b/src/Algorand/AssetTransfer.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AssetTransfer.h" +#include "BinaryCoding.h" + +namespace TW::Algorand { + +Data AssetTransfer::serialize() const { + Data data; + + // encode map length + uint8_t size = 10; + if (!note.empty()) { + // note is optional + size += 1; + } + data.push_back(0x80 + size); + + // encode fields one by one (sorted by name) + encodeString("aamt", data); + encodeNumber(amount, data); + + encodeString("arcv", data); + encodeBytes(Data(to.bytes.begin(), to.bytes.end()), data); + + encodeString("fee", data); + encodeNumber(fee, data); + + encodeString("fv", data); + encodeNumber(firstRound, data); + + encodeString("gen", data); + encodeString(genesisId, data); + + encodeString("gh", data); + encodeBytes(genesisHash, data); + + encodeString("lv", data); + encodeNumber(lastRound, data); + + if (!note.empty()) { + encodeString("note", data); + encodeBytes(note, data); + } + + encodeString("snd", data); + encodeBytes(Data(from.bytes.begin(), from.bytes.end()), data); + + encodeString("type", data); + encodeString(type, data); + + encodeString("xaid", data); + encodeNumber(assetId, data); + + return data; +} + +} // namespace TW::Algorand diff --git a/src/Algorand/AssetTransfer.h b/src/Algorand/AssetTransfer.h new file mode 100644 index 00000000000..3de0f22bb82 --- /dev/null +++ b/src/Algorand/AssetTransfer.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "BaseTransaction.h" +#include "Data.h" +#include "../proto/Algorand.pb.h" + +namespace TW::Algorand { + +class AssetTransfer: public BaseTransaction { + public: + Address from; + Address to; + uint64_t fee; + uint64_t amount; + uint64_t assetId; + uint64_t firstRound; + uint64_t lastRound; + Data note; + std::string type; + + std::string genesisId; + Data genesisHash; + + AssetTransfer(Address &from, Address &to, uint64_t fee, uint64_t amount, uint64_t assetId, uint64_t firstRound, + uint64_t lastRound, Data& note, std::string type, std::string& genesisId, Data& genesisHash) + : from(from), to(to) + , fee(fee), amount(amount) + , assetId(assetId), firstRound(firstRound) + , lastRound(lastRound), note(note) + , type(std::move(type)), genesisId(genesisId) + , genesisHash(genesisHash) {} + + public: + Data serialize() const override; +}; + +} // namespace TW::Algorand diff --git a/src/Algorand/BaseTransaction.h b/src/Algorand/BaseTransaction.h new file mode 100644 index 00000000000..4938e43bb2a --- /dev/null +++ b/src/Algorand/BaseTransaction.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "BinaryCoding.h" +#include "Data.h" + +namespace TW::Algorand { + +class BaseTransaction { +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: + { + "sig": + "txn": , + } + */ + Data data; + // encode map length + data.push_back(0x80 + 2); + // signature + encodeString("sig", data); + encodeBytes(signature, data); + + // transaction + encodeString("txn", data); + append(data, serialize()); + return data; + } +}; + +} // namespace TW::Algorand diff --git a/src/Algorand/BinaryCoding.h b/src/Algorand/BinaryCoding.h index 8a3bce78823..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 @@ -12,6 +10,7 @@ namespace TW::Algorand { #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#pragma GCC diagnostic ignored "-Wunused-function" static inline void encodeString(std::string string, Data& data) { // encode string header diff --git a/src/Algorand/Entry.cpp b/src/Algorand/Entry.cpp index e7648f4c0fa..14a1320fede 100644 --- a/src/Algorand/Entry.cpp +++ b/src/Algorand/Entry.cpp @@ -1,27 +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); } + +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 579189e5534..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeAlgorand}; } - 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; + 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 new file mode 100644 index 00000000000..7246c4e8c54 --- /dev/null +++ b/src/Algorand/OptInAssetTransaction.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "OptInAssetTransaction.h" +#include "BinaryCoding.h" + +namespace TW::Algorand { + +Data OptInAssetTransaction::serialize() const { + Data data; + + // encode map length + uint8_t size = 9; + if (!note.empty()) { + // note is optional + size += 1; + } + data.push_back(0x80 + size); + + // encode fields one by one (sorted by name) + encodeString("arcv", data); + encodeBytes(Data(address.bytes.begin(), address.bytes.end()), data); + + encodeString("fee", data); + encodeNumber(fee, data); + + encodeString("fv", data); + encodeNumber(firstRound, data); + + encodeString("gen", data); + encodeString(genesisId, data); + + encodeString("gh", data); + encodeBytes(genesisHash, data); + + encodeString("lv", data); + encodeNumber(lastRound, data); + + if (!note.empty()) { + encodeString("note", data); + encodeBytes(note, data); + } + + encodeString("snd", data); + encodeBytes(Data(address.bytes.begin(), address.bytes.end()), data); + + encodeString("type", data); + encodeString(type, data); + + encodeString("xaid", data); + encodeNumber(assetId, data); + + return data; +} + +} // namespace TW::Algorand diff --git a/src/Algorand/OptInAssetTransaction.h b/src/Algorand/OptInAssetTransaction.h new file mode 100644 index 00000000000..ca98873fa71 --- /dev/null +++ b/src/Algorand/OptInAssetTransaction.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include "Address.h" +#include "BaseTransaction.h" +#include "Data.h" +#include "../proto/Algorand.pb.h" + +namespace TW::Algorand { + +class OptInAssetTransaction: public BaseTransaction { + public: + Address address; + uint64_t fee; + uint64_t assetId; + uint64_t firstRound; + uint64_t lastRound; + Data note; + std::string type; + + std::string genesisId; + Data genesisHash; + + OptInAssetTransaction(Address &address, uint64_t fee, uint64_t assetId, uint64_t firstRound, + uint64_t lastRound, Data& note, std::string type, std::string& genesisId, Data& genesisHash) + : address(address), fee(fee) + , assetId(assetId), firstRound(firstRound) + , lastRound(lastRound), note(note) + , type(std::move(type)), genesisId(genesisId) + , genesisHash(genesisHash) {} + + public: + Data serialize() const override; +}; + +} // namespace TW::Algorand diff --git a/src/Algorand/Signer.cpp b/src/Algorand/Signer.cpp index 057a621bbb8..02a7bf0b3e7 100644 --- a/src/Algorand/Signer.cpp +++ b/src/Algorand/Signer.cpp @@ -1,46 +1,143 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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" +#include "BaseTransaction.h" +#include "Base64.h" +#include "../HexCoding.h" -using namespace TW; -using namespace TW::Algorand; +#include + +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 key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); - auto from = Address(pubkey); + + auto preImageData = Signer::preImage(pubkey, input); + auto signature = key.sign(preImageData); + 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); + 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()); - if (input.has_transaction_pay()) { - auto message = input.transaction_pay(); + + TW::Data transactionData; + if (input.has_transfer()) { + auto message = input.transfer(); auto to = Address(message.to_address()); - auto transaction = Transaction(from, to, message.fee(), message.amount(), message.first_round(), - message.last_round(), note, TRANSACTION_PAY, genesisId, genesisHash); - auto signature = sign(key, transaction); - auto serialized = transaction.serialize(signature); - protoOutput.set_encoded(serialized.data(), serialized.size()); + 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{}}; } - - return protoOutput; -} -Data Signer::sign(const PrivateKey &privateKey, Transaction &transaction) noexcept { Data data; append(data, TRANSACTION_TAG); - append(data, transaction.serialize()); - auto signature = privateKey.sign(data, TWCurveED25519); - return Data(signature.begin(), signature.end()); + 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(); + + 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()); + 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); + + 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 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 serialized = transaction.BaseTransaction::serialize(signature); + protoOutput.set_encoded(serialized.data(), serialized.size()); + protoOutput.set_signature(Base64::encode(signature)); + } + return protoOutput; +} + +} // namespace TW::Algorand diff --git a/src/Algorand/Signer.h b/src/Algorand/Signer.h index b6c195062d6..2c0514328c7 100644 --- a/src/Algorand/Signer.h +++ b/src/Algorand/Signer.h @@ -1,14 +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 "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 { @@ -21,8 +22,19 @@ class Signer { /// 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); + /// Signs the given transaction. - static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; + 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/Transaction.cpp b/src/Algorand/Transaction.cpp deleted file mode 100644 index ac608af5b3e..00000000000 --- a/src/Algorand/Transaction.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 "Transaction.h" -#include "BinaryCoding.h" -#include "../HexCoding.h" - -using namespace TW; -using namespace TW::Algorand; - -Data Transaction::serialize() const { - /* Algorand transaction is encoded with msgpack - { - amt: 847, - fee: 488931, - fv: 51, - gen: 'mainnet-v1.0', - gh: - lv: 61, - note: - rcv: - snd: - type: 'pay', - } - */ - Data data; - - // encode map length - uint8_t size = 9; - if (!note.empty()) { - // note is optional - size += 1; - } - data.push_back(0x80 + size); - - // encode fields one by one (sorted by name) - encodeString("amt", data); - encodeNumber(amount, data); - - encodeString("fee", data); - encodeNumber(fee, data); - - encodeString("fv", data); - encodeNumber(firstRound, data); - - encodeString("gen", data); - encodeString(genesisId, data); - - encodeString("gh", data); - encodeBytes(genesisHash, data); - - encodeString("lv", data); - encodeNumber(lastRound, data); - - if (!note.empty()) { - encodeString("note", data); - encodeBytes(note, data); - } - - encodeString("rcv", data); - encodeBytes(Data(to.bytes.begin(), to.bytes.end()), data); - - encodeString("snd", data); - encodeBytes(Data(from.bytes.begin(), from.bytes.end()), data); - - encodeString("type", data); - encodeString(type, data); - return data; -} - -Data Transaction::serialize(Data& signature) const { - /* Algorand transaction and signature are encoded with msgpack: - { - "sig": - "txn": , - } - */ - Data data; - // encode map length - data.push_back(0x80 + 2); - // signature - encodeString("sig", data); - encodeBytes(signature, data); - - // transaction - encodeString("txn", data); - append(data, serialize()); - return data; -} diff --git a/src/Algorand/Transaction.h b/src/Algorand/Transaction.h deleted file mode 100644 index f7b9c3dd694..00000000000 --- a/src/Algorand/Transaction.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "../Data.h" -#include "../proto/Algorand.pb.h" - -namespace TW::Algorand { - -class Transaction { - public: - Address from; - Address to; - uint64_t fee; - uint64_t amount; - uint64_t firstRound; - uint64_t lastRound; - Data note; - std::string type; - - std::string genesisId; - Data genesisHash; - - Transaction(Address &from, Address &to, uint64_t fee, uint64_t amount, uint64_t firstRound, - uint64_t lastRound, Data& note, std::string type, std::string& genesisIdg, Data& genesisHash) - : from(from) , to(to) - , fee(fee), amount(amount) - , firstRound(firstRound), lastRound(lastRound) - , note(note), type(type) - , genesisId(genesisIdg), genesisHash(genesisHash) {} - - public: - Data serialize() const; - Data serialize(Data& signature) const; -}; - -} // namespace TW::Algorand diff --git a/src/Algorand/Transfer.cpp b/src/Algorand/Transfer.cpp new file mode 100644 index 00000000000..645463679c0 --- /dev/null +++ b/src/Algorand/Transfer.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Transfer.h" +#include "BinaryCoding.h" + +namespace TW::Algorand { + +Data Transfer::serialize() const { + /* Algorand transaction is encoded with msgpack + { + amt: 847, + fee: 488931, + fv: 51, + gen: 'mainnet-v1.0', + gh: + lv: 61, + note: + rcv: + snd: + type: 'pay', + } + */ + Data data; + + // encode map length + uint8_t size = 9; + if (!note.empty()) { + // note is optional + size += 1; + } + // don't encode 0 amount + if (amount == 0) { + size -= 1; + } + data.push_back(0x80 + size); + + // encode fields one by one (sorted by name) + if (amount > 0) { + encodeString("amt", data); + encodeNumber(amount, data); + } + + encodeString("fee", data); + encodeNumber(fee, data); + + encodeString("fv", data); + encodeNumber(firstRound, data); + + encodeString("gen", data); + encodeString(genesisId, data); + + encodeString("gh", data); + encodeBytes(genesisHash, data); + + encodeString("lv", data); + encodeNumber(lastRound, data); + + if (!note.empty()) { + encodeString("note", data); + encodeBytes(note, data); + } + + encodeString("rcv", data); + encodeBytes(Data(to.bytes.begin(), to.bytes.end()), data); + + encodeString("snd", data); + encodeBytes(Data(from.bytes.begin(), from.bytes.end()), data); + + encodeString("type", data); + encodeString(type, data); + return data; +} + +} // namespace TW::Algorand diff --git a/src/Algorand/Transfer.h b/src/Algorand/Transfer.h new file mode 100644 index 00000000000..8ca7bbcad59 --- /dev/null +++ b/src/Algorand/Transfer.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include "Address.h" +#include "BaseTransaction.h" +#include "Data.h" +#include "../proto/Algorand.pb.h" + +namespace TW::Algorand { + +class Transfer : public BaseTransaction { + public: + Address from; + Address to; + uint64_t fee; + uint64_t amount; + uint64_t firstRound; + uint64_t lastRound; + Data note; + std::string type; + + std::string genesisId; + Data genesisHash; + + Transfer(Address &from, Address &to, uint64_t fee, uint64_t amount, uint64_t firstRound, + uint64_t lastRound, Data& note, std::string type, std::string& genesisIdg, Data& genesisHash) + : from(from) , to(to) + , fee(fee), amount(amount) + , firstRound(firstRound), lastRound(lastRound) + , note(note), type(std::move(type)) + , genesisId(genesisIdg), genesisHash(genesisHash) {} + + public: + Data serialize() const override; +}; + +} // namespace TW::Algorand 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 c0e430f50a0..00000000000 --- a/src/Base58.cpp +++ /dev/null @@ -1,195 +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 - -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 = 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 { - auto it = begin; - - // Skip leading spaces. - it = std::find_if_not(it, end, std::isspace); - - // 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, std::isspace); - 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 = 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 9bcc2df7aa9..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::sha256d) 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::sha256d) 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::sha256d) 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::sha256d) 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 c294f364161..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"); } @@ -80,13 +78,13 @@ class Base58Address { if (publicKey.type != TWPublicKeyTypeSECP256k1) { throw std::invalid_argument("Bitcoin::Address needs a compressed SECP256k1 public key."); } - const auto data = publicKey.hash(prefix, Hash::sha256ripemd); + const auto data = publicKey.hash(prefix, Hash::HasherSha256ripemd); std::copy(data.begin(), data.end(), bytes.begin()); } /// 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 f88a1b7e267..dfcfa1353aa 100644 --- a/src/Base64.cpp +++ b/src/Base64.cpp @@ -1,80 +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 6946132b9ec..273c1f3f16c 100644 --- a/src/Bech32.cpp +++ b/src/Bech32.cpp @@ -1,17 +1,22 @@ // 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 "Bech32.h" #include "Data.h" #include +// Bech32 address encoding +// Bech32M variant also supported (BIP350) +// Max length of 90 constraint is extended here to 120 for other usages + + using namespace TW; +namespace TW::Bech32 { + namespace { /** The Bech32 character set for encoding. */ @@ -21,16 +26,13 @@ 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}; - -/** Concatenate two byte arrays. */ -Data cat(Data x, const Data& y) { - x.insert(x.end(), y.begin(), y.end()); - return x; -} + -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) { @@ -62,16 +64,35 @@ Data expand_hrp(const std::string& hrp) { return ret; } +inline uint32_t xorConstant(ChecksumVariant variant) { + if (variant == ChecksumVariant::Bech32) { + return BECH32_XOR_CONST; + } + // Bech32M + return BECH32M_XOR_CONST; +} + /** Verify a checksum. */ -bool verify_checksum(const std::string& hrp, const Data& values) { - return polymod(cat(expand_hrp(hrp), values)) == 1; +ChecksumVariant verify_checksum(const std::string& hrp, const Data& values) { + Data enc = expand_hrp(hrp); + append(enc, values); + auto poly = polymod(enc); + if (poly == BECH32_XOR_CONST) { + return ChecksumVariant::Bech32; + } + if (poly == BECH32M_XOR_CONST) { + return ChecksumVariant::Bech32M; + } + return ChecksumVariant::None; } /** Create a checksum. */ -Data create_checksum(const std::string& hrp, const Data& values) { - Data enc = cat(expand_hrp(hrp), values); +Data create_checksum(const std::string& hrp, const Data& values, ChecksumVariant variant) { + Data enc = expand_hrp(hrp); + append(enc, values); enc.resize(enc.size() + 6); - uint32_t mod = polymod(enc) ^ 1; + auto xorConst = xorConstant(variant); + uint32_t mod = polymod(enc) ^ xorConst; Data ret; ret.resize(6); for (size_t i = 0; i < 6; ++i) { @@ -82,10 +103,11 @@ Data create_checksum(const std::string& hrp, const Data& values) { } // namespace -/** Encode a Bech32 string. */ -std::string Bech32::encode(const std::string& hrp, const Data& values) { - Data checksum = create_checksum(hrp, values); - Data combined = cat(values, checksum); +/** Encode a Bech32 string. Note that the values must each encode 5 bits, normally get from convertBits<8, 5, true> */ +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); std::string ret = hrp + '1'; ret.reserve(ret.size() + combined.size()); for (const auto& value : combined) { @@ -94,8 +116,12 @@ std::string Bech32::encode(const std::string& hrp, const Data& values) { return ret; } -/** Decode a Bech32 string. */ -std::pair Bech32::decode(const std::string& str) { +/** Decode a Bech32 string. Note that the returned values are 5 bits each, you may want to use convertBits<5, 8, false> */ +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); + } bool lower = false, upper = false; bool ok = true; for (size_t i = 0; ok && i < str.size(); ++i) { @@ -114,7 +140,7 @@ std::pair Bech32::decode(const std::string& str) { ok = false; } size_t pos = str.rfind('1'); - if (ok && str.size() <= 120 && 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) { @@ -129,10 +155,13 @@ std::pair Bech32::decode(const std::string& str) { for (size_t i = 0; i < pos; ++i) { hrp += lc(str[i]); } - if (verify_checksum(hrp, values)) { - return std::make_pair(hrp, Data(values.begin(), values.end() - 6)); + auto variant = verify_checksum(hrp, values); + if (variant != None) { + return std::make_tuple(hrp, Data(values.begin(), values.end() - 6), variant); } } } - return std::make_pair(std::string(), Data()); + return std::make_tuple(std::string(), Data(), None); } + +} // namespace TW::Bech32 diff --git a/src/Bech32.h b/src/Bech32.h index 38840fdde94..cb0a4bc7b9d 100644 --- a/src/Bech32.h +++ b/src/Bech32.h @@ -1,30 +1,42 @@ // 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. + +#pragma once + +#include "Data.h" #include #include #include +#include namespace TW::Bech32 { +enum ChecksumVariant { + None = 0, + Bech32, + Bech32M, +}; + /// Encodes a Bech32 string. /// /// \returns the encoded string, or an empty string in case of failure. -std::string encode(const std::string& hrp, const std::vector& values); +std::string encode(const std::string& hrp, const Data& values, ChecksumVariant variant); /// Decodes a Bech32 string. /// -/// \returns a pair with the human-readable part and the data, or a pair or -/// empty collections on failure. -std::pair> decode(const std::string& str); +/// \returns a tuple with +/// - the human-readable part +/// - data, or a pair or +/// - ChecksumVariant used +/// or empty values on failure. +std::tuple decode(const std::string& str); /// Converts from one power-of-2 number base to another. template -inline bool convertBits(std::vector& out, const std::vector& in) { +inline bool convertBits(Data& out, const Data& in) { int acc = 0; int bits = 0; const int maxv = (1 << tobits) - 1; diff --git a/src/Bech32Address.cpp b/src/Bech32Address.cpp index 3df8b491ef7..8394773b635 100644 --- a/src/Bech32Address.cpp +++ b/src/Bech32Address.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 "Bech32.h" @@ -18,15 +16,15 @@ bool Bech32Address::isValid(const std::string& addr) { bool Bech32Address::isValid(const std::string& addr, const std::string& hrp) { auto dec = Bech32::decode(addr); // check hrp prefix (if given) - if (hrp.length() > 0 && dec.first.compare(0, hrp.length(), hrp) != 0) { + if (hrp.length() > 0 && std::get<0>(dec).compare(0, hrp.length(), hrp) != 0) { return false; } - if (dec.second.empty()) { + if (std::get<1>(dec).empty()) { return false; } Data conv; - auto success = Bech32::convertBits<5, 8, false>(conv, dec.second); + auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(dec)); if (!success || conv.size() < 2 || conv.size() > 40) { return false; } @@ -37,56 +35,34 @@ bool Bech32Address::isValid(const std::string& addr, const std::string& hrp) { bool Bech32Address::decode(const std::string& addr, Bech32Address& obj_out, const std::string& hrp) { auto dec = Bech32::decode(addr); // check hrp prefix (if given) - if (hrp.length() > 0 && dec.first.compare(0, hrp.length(), hrp) != 0) { + if (hrp.length() > 0 && std::get<0>(dec).compare(0, hrp.length(), hrp) != 0) { return false; } - if (dec.second.empty()) { + if (std::get<1>(dec).empty()) { return false; } Data conv; - auto success = Bech32::convertBits<5, 8, false>(conv, dec.second); + auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(dec)); if (!success || conv.size() < 2 || conv.size() > 40) { return false; } - obj_out.setHrp(dec.first); + obj_out.setHrp(std::get<0>(dec)); obj_out.setKey(conv); return true; } -Bech32Address::Bech32Address(const std::string& hrp, HasherType hasher, const PublicKey& publicKey) +Bech32Address::Bech32Address(const std::string& hrp, Hash::Hasher hasher, const PublicKey& publicKey) : hrp(hrp) { - switch (hasher) { - case HASHER_SHA2_RIPEMD: - { - auto key = Data(20); - ecdsa_get_pubkeyhash(publicKey.compressed().bytes.data(), HASHER_SHA2_RIPEMD, key.data()); - setKey(key); - } - break; - - case HASHER_SHA2: - { - const auto hash = Hash::sha256(publicKey.bytes); - auto key = Data(20); - std::copy(hash.end() - 20, hash.end(), key.begin()); - setKey(key); - } - break; - - case HASHER_SHA3K: - { - const auto hash = publicKey.hash({}, static_cast(Hash::keccak256), true); - auto key = Data(20); - std::copy(hash.end() - 20, hash.end(), key.begin()); - setKey(key); - } - break; - - default: - throw std::invalid_argument("Invalid HasherType in Bech32Address"); + bool skipTypeByte = false; + // Extended-key / keccak-hash skips first byte (Evmos) + if (publicKey.type == TWPublicKeyTypeSECP256k1Extended || hasher == Hash::HasherKeccak256) { + skipTypeByte = true; } + const auto hash = publicKey.hash({}, hasher, skipTypeByte); + auto key = subData(hash, hash.size() - 20, 20); + setKey(key); } std::string Bech32Address::string() const { @@ -94,7 +70,7 @@ std::string Bech32Address::string() const { if (!Bech32::convertBits<8, 5, true>(enc, keyHash)) { return ""; } - std::string result = Bech32::encode(hrp, enc); + std::string result = Bech32::encode(hrp, enc, Bech32::ChecksumVariant::Bech32); // check back Bech32Address obj; if (!decode(result, obj, hrp)) { diff --git a/src/Bech32Address.h b/src/Bech32Address.h index 23b4c06a640..b7a1707b34d 100644 --- a/src/Bech32Address.h +++ b/src/Bech32Address.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 "PublicKey.h" -#include +#include "Hash.h" #include #include @@ -45,7 +43,7 @@ class Bech32Address { Bech32Address(const std::string& hrp, const Data& keyHash) : hrp(std::move(hrp)), keyHash(std::move(keyHash)) {} /// Initialization from public key --> chain specific hash methods - Bech32Address(const std::string& hrp, HasherType hasher, const PublicKey& publicKey); + Bech32Address(const std::string& hrp, Hash::Hasher hasher, const PublicKey& publicKey); void setHrp(const std::string& hrp_in) { hrp = std::move(hrp_in); } void setKey(const Data& keyHash_in) { keyHash = std::move(keyHash_in); } diff --git a/src/Binance/Address.cpp b/src/Binance/Address.cpp index 481f2eda9ca..0ab1d1a77c0 100644 --- a/src/Binance/Address.cpp +++ b/src/Binance/Address.cpp @@ -1,28 +1,49 @@ // 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"; -bool Address::isValid(const std::string& addr) { - std::vector hrps = {hrp, hrpValidator, "bnbp", "bvap", "bca", "bcap"}; - bool result = false; - for (auto& hrp : hrps) { - result = Bech32Address::isValid(addr, hrp); - if (result) { - break; +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; } } - return result; + return false; } + +} // namespace TW::Binance diff --git a/src/Binance/Address.h b/src/Binance/Address.h index 2fb7c1fab0d..227f3088727 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -1,36 +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 "../Bech32Address.h" +#include #include namespace TW::Binance { -/// Binance address is a Bech32Address, with "bnb" prefix and HASHER_SHA2_RIPEMD hash +/// 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, HASHER_SHA2_RIPEMD, publicKey) {} + /// Initializes an address with a key hash and hrp. + explicit Address(const Data& keyHash, const std::string& chainHrp) : Bech32Address(chainHrp, keyHash) {} - static bool decode(const std::string& addr, Address& obj_out) { - return Bech32Address::decode(addr, obj_out, hrp); - } + /// 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); }; } // namespace TW::Binance diff --git a/src/Binance/Entry.cpp b/src/Binance/Entry.cpp index 3688f01ec81..c1915455dec 100644 --- a/src/Binance/Entry.cpp +++ b/src/Binance/Entry.cpp @@ -1,29 +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 "Address.h" -#include "Signer.h" +#include "HexCoding.h" +#include "proto/Binance.pb.h" -using namespace TW::Binance; -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(); -} - -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); -} +} // namespace TW::Binance diff --git a/src/Binance/Entry.h b/src/Binance/Entry.h index d9bf47e8bde..dab9768dff7 100644 --- a/src/Binance/Entry.h +++ b/src/Binance/Entry.h @@ -1,25 +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 const std::vector coinTypes() const { return {TWCoinTypeBinance}; } - 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 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 03736f2dc07..00000000000 --- a/src/Binance/Signer.cpp +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 key = PrivateKey(input.private_key()); - auto hash = Hash::sha256(signaturePreimage()); - auto signature = key.sign(hash, TWCurveSECP256k1); - return Data(signature.begin(), signature.end() - 1); -} - -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); - - 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 1539c48d7b7..00000000000 --- a/src/Binance/Signer.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 "Data.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; - - private: - std::string signaturePreimage() const; - TW::Data encodeTransaction(const TW::Data& signature) const; - TW::Data encodeOrder() const; - TW::Data encodeSignature(const TW::Data& signature) 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 551c042c760..210101accbe 100644 --- a/src/BinaryCoding.cpp +++ b/src/BinaryCoding.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 "BinaryCoding.h" #include +#include namespace TW { diff --git a/src/BinaryCoding.h b/src/BinaryCoding.h index f5c6ff18067..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 @@ -10,8 +8,9 @@ #include #include -#include #include +#include +#include namespace TW { @@ -61,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. @@ -108,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 8a43592e933..50aab3e10af 100644 --- a/src/Bitcoin/Address.h +++ b/src/Bitcoin/Address.h @@ -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. #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include namespace TW::Bitcoin { +/// Class for non-segwit P2PKH and P2SH addresses class Address : public TW::Base58Address<21> { public: - /// Initializes a address with a string representation. + /// Initializes an address with a string representation. explicit Address(const std::string& string) : TW::Base58Address<21>(string) {} - /// Initializes a address with a collection of bytes. + /// Initializes an address with a collection of bytes. explicit Address(const Data& data) : TW::Base58Address<21>(data) {} - /// Initializes a address with a public key and a prefix. + /// Initializes an address with a public key and a prefix. Applicable for P2PKH addresses (but not P2SH). Address(const PublicKey& publicKey, byte prefix) : TW::Base58Address<21>(publicKey, {prefix}) {} }; diff --git a/src/Bitcoin/Amount.h b/src/Bitcoin/Amount.h index 873b9ba871d..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,15 +13,4 @@ namespace TW::Bitcoin { /// Amount in satoshis (can be negative) using Amount = int64_t; -/// One bitcoin in satoshis -static const Amount coin = 100000000; - -/// Maxximum valid amount in satoshis. -static const Amount maxAmount = 21000000 * coin; - -/// Detemines if the provided value is a valid amount. -inline bool isValidAmount(const Amount& amount) { - return (amount >= 0 && amount <= maxAmount); -} - } // namespace TW::Bitcoin diff --git a/src/Bitcoin/CashAddress.cpp b/src/Bitcoin/CashAddress.cpp index 43eaa408b6c..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" @@ -12,67 +10,78 @@ #include #include -#include +#include -using namespace TW::Bitcoin; - -/// Cash address human-readable part -static const std::string cashHRP = "bitcoincash"; +namespace TW::Bitcoin { /// From https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md +namespace { -static const uint8_t p2khVersion = 0x00; -static const uint8_t p2shVersion = 0x08; +enum class Version : uint8_t { + p2kh = 0x00, + p2sh = 0x08 +}; +constexpr size_t maxHRPSize{20}; +constexpr size_t maxDataSize{104}; -static constexpr size_t maxHRPSize = 20; -static constexpr size_t maxDataSize = 104; +} // namespace -bool CashAddress::isValid(const std::string& string) { - auto withPrefix = string; - if (!std::equal(cashHRP.begin(), cashHRP.end(), string.begin())) { - withPrefix = cashHRP + ":" + string; - } +namespace details { - std::array hrp = {0}; - std::array data; - size_t dataLen; - if (cash_decode(hrp.data(), data.data(), &dataLen, withPrefix.c_str()) == 0 || dataLen != CashAddress::size) { - return false; +inline std::string buildPrefix(const std::string& hrp, const std::string& string) noexcept { + if (string.size() < hrp.size() || !std::equal(hrp.begin(), hrp.end(), string.begin())) { + return hrp + ":" + string; } - if (std::strncmp(hrp.data(), cashHRP.c_str(), std::min(cashHRP.size(), maxHRPSize)) != 0) { - return false; - } - return true; + return string; } -CashAddress::CashAddress(const std::string& string) { - auto withPrefix = string; - if (!std::equal(cashHRP.begin(), cashHRP.end(), string.begin())) { - withPrefix = cashHRP + ":" + string; +inline void determinePrefix(TW::Data& data) noexcept { + auto& prefix = data.front(); + switch (static_cast(prefix)) { + case Version::p2kh: + prefix = TW::p2pkhPrefix(TWCoinTypeBitcoinCash); + break; + case Version::p2sh: + prefix = TW::p2shPrefix(TWCoinTypeBitcoinCash); + break; } +} - std::array hrp; - std::array data; +} // namespace details + +bool CashAddress::isValid(const std::string& hrp, const std::string& string) noexcept { + const auto withPrefix = details::buildPrefix(hrp, string); + std::array decodedHRP{0}; + std::array data{}; size_t dataLen; - auto success = cash_decode(hrp.data(), data.data(), &dataLen, withPrefix.c_str()) != 0; - if (!success || std::strncmp(hrp.data(), cashHRP.c_str(), std::min(cashHRP.size(), maxHRPSize)) != 0 || dataLen != CashAddress::size) { - throw std::invalid_argument("Invalid address string"); - } - std::copy(data.begin(), data.begin() + dataLen, bytes.begin()); + const bool decodeValid = + cash_decode(decodedHRP.data(), data.data(), &dataLen, withPrefix.c_str()) != 0 && + dataLen == CashAddress::size; + return decodeValid && + std::string(decodedHRP.data()).compare(0, std::min(hrp.size(), maxHRPSize), hrp) == 0; } -CashAddress::CashAddress(const std::vector& data) { - if (!isValid(data)) { - throw std::invalid_argument("Invalid address key data"); +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{}; + size_t dataLen; + bool success = cash_decode(decodedHRP.data(), data.data(), &dataLen, withPrefix.c_str()) != 0; + if (!success || + std::string(decodedHRP.data()).compare(0, std::min(hrp.size(), maxHRPSize), hrp) != 0 || + dataLen != CashAddress::size) { + throw std::invalid_argument("Invalid address string"); } - std::copy(data.begin(), data.end(), bytes.begin()); + std::copy(data.begin(), data.begin() + dataLen, bytes.begin()); } -CashAddress::CashAddress(const PublicKey& publicKey) { +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."); } - std::array payload; + std::array payload{}; payload[0] = 0; ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, payload.data() + 1); @@ -83,21 +92,26 @@ CashAddress::CashAddress(const PublicKey& publicKey) { } } -std::string CashAddress::string() const { - std::array result; - cash_encode(result.data(), cashHRP.c_str(), bytes.data(), CashAddress::size); +std::string CashAddress::string() const noexcept { + std::array result{}; + cash_encode(result.data(), hrp.c_str(), bytes.data(), CashAddress::size); return result.data(); } -Address CashAddress::legacyAddress() const { - std::vector result(Address::size); +Address CashAddress::legacyAddress() const noexcept { + Data result(Address::size); size_t outlen = 0; cash_data_to_addr(result.data(), &outlen, bytes.data(), CashAddress::size); assert(outlen == 21 && "Invalid length"); - if (result[0] == p2khVersion) { - result[0] = TW::p2pkhPrefix(TWCoinTypeBitcoinCash); - } else if (result[0] == p2shVersion) { - result[0] = TW::p2shPrefix(TWCoinTypeBitcoinCash); - } + details::determinePrefix(result); return Address(result); } + +Data CashAddress::getData() const { + Data data(Address::size); + size_t outlen = 0; + 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 f9613645676..58b1a22d5b6 100644 --- a/src/Bitcoin/CashAddress.h +++ b/src/Bitcoin/CashAddress.h @@ -1,27 +1,34 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, 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 "../PublicKey.h" +#include + #include #include namespace TW::Bitcoin { +inline const std::string gBitcoinCashHrp{HRP_BITCOINCASH}; +inline const std::string gECashHrp{HRP_ECASH}; + class CashAddress { - public: +public: /// Number of bytes in an address. - static const size_t size = 34; + static constexpr size_t size{34}; /// Address data consisting of a prefix byte followed by the public key /// hash. - std::array bytes; + std::array bytes{}; + + /// Cash address human-readable part + const std::string hrp; /// Determines whether a collection of bytes makes a valid address. template @@ -30,26 +37,47 @@ class CashAddress { } /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); + static bool isValid(const std::string& hrp, const std::string& string) noexcept; /// Initializes a address with a string representation. - explicit CashAddress(const std::string& string); - - /// Initializes a address with a collection of bytes. - explicit CashAddress(const std::vector& data); + explicit CashAddress(const std::string& hrp, const std::string& string); /// Initializes a address with a public key. - explicit CashAddress(const PublicKey& publicKey); + explicit CashAddress(std::string hrp, const PublicKey& publicKey); /// Returns a string representation of the address. - std::string string() const; + [[nodiscard]] std::string string() const noexcept; /// Returns the legacy address representation. - Address legacyAddress() const; + [[nodiscard]] Address legacyAddress() const noexcept; + + Data getData() const; }; -inline bool operator==(const CashAddress& lhs, const CashAddress& rhs) { - return lhs.bytes == rhs.bytes; -} +class BitcoinCashAddress : public CashAddress { +public: + /// Initializes an address with a string representation. + explicit BitcoinCashAddress(const std::string& string) : CashAddress(gBitcoinCashHrp, string) {} + + /// Initializes an address with a public key. + explicit BitcoinCashAddress(const PublicKey& publicKey) : CashAddress(gBitcoinCashHrp, publicKey) {} + + static bool isValid(const std::string& string) noexcept { + return CashAddress::isValid(gBitcoinCashHrp, string); + } +}; + +class ECashAddress : public CashAddress { +public: + /// Initializes an address with a string representation. + explicit ECashAddress(const std::string& string) : CashAddress(gECashHrp, string) {} + + /// Initializes an address with a public key. + explicit ECashAddress(const PublicKey& publicKey) : CashAddress(gECashHrp, publicKey) {} + + static bool isValid(const std::string& string) noexcept { + return CashAddress::isValid(gECashHrp, string); + } +}; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/DustCalculator.cpp b/src/Bitcoin/DustCalculator.cpp new file mode 100644 index 00000000000..01254c12639 --- /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)) { +} + +Amount LegacyDustCalculator::dustAmount(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 42c39c99cd1..ff5b6936977 100644 --- a/src/Bitcoin/Entry.cpp +++ b/src/Bitcoin/Entry.cpp @@ -1,87 +1,185 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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" -using namespace TW::Bitcoin; -using namespace std; +namespace TW::Bitcoin { + +const EntryV2 bitcoinV2Dispatcher; + +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, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { switch (coin) { - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeLitecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeViacoin: - case TWCoinTypeBitcoinGold: - return SegwitAddress::isValid(address, hrp) - || Address::isValid(address, {{p2pkh}, {p2sh}}); - - case TWCoinTypeBitcoinCash: - return CashAddress::isValid(address) - || Address::isValid(address, {{p2pkh}, {p2sh}}); - - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeRavencoin: - case TWCoinTypeZcoin: - default: - return Address::isValid(address, {{p2pkh}, {p2sh}}); + case TWCoinTypeBitcoin: + case TWCoinTypeDigiByte: + case TWCoinTypeLitecoin: + case TWCoinTypeMonacoin: + case TWCoinTypeQtum: + case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: + return isValidBase58 || isValidHrp; + case TWCoinTypeBitcoinCash: + return base58Prefix ? isValidBase58 : BitcoinCashAddress::isValid(address); + case TWCoinTypeECash: + return base58Prefix ? isValidBase58 : ECashAddress::isValid(address); + case TWCoinTypeFiro: + return isValidBase58 || ExchangeAddress::isValid(address); + case TWCoinTypeDash: + case TWCoinTypeDogecoin: + case TWCoinTypePivx: + case TWCoinTypeRavencoin: + default: + 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 - if (CashAddress::isValid(address)) { - return CashAddress(address).string(); - } else { - return std::string(address); - } + case TWCoinTypeBitcoinCash: + // normalized with bitcoincash: prefix + if (BitcoinCashAddress::isValid(address)) { + return BitcoinCashAddress(address).string(); + } + return address; - default: - // no change - return address; + case TWCoinTypeECash: + // normalized with ecash: prefix + if (ECashAddress::isValid(address)) { + return ECashAddress(address).string(); + } + return address; + + default: + // no change + return 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, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + switch (coin) { - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeLitecoin: - case TWCoinTypeViacoin: - case TWCoinTypeBitcoinGold: - return SegwitAddress(publicKey, 0, hrp).string(); - - case TWCoinTypeBitcoinCash: - return CashAddress(publicKey).string(); - - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeRavencoin: - case TWCoinTypeZcoin: - default: + 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 TWDerivationBitcoinTaproot: + return bitcoinV2Dispatcher.deriveAddress(coin, publicKey, derivation, addressPrefix); + + case TWDerivationBitcoinSegwit: + case TWDerivationDefault: + default: + 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: + default: + return Address(publicKey, p2pkh).string(); } } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +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 TWCoinTypeBitcoinCash: + return cashAddressToData(BitcoinCashAddress(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 {}; + } + + 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()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; + } + } +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, 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 Data& dataIn, Data& dataOut) const { planTemplate(dataIn, dataOut); } + +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([[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 { + 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 ccdd4f4932e..3e793f8715c 100644 --- a/src/Bitcoin/Entry.h +++ b/src/Bitcoin/Entry.h @@ -1,40 +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 "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Bitcoin { +/// A helper Bitcoin Entry migrated to Rust partly used in some cases, +/// for example, in Taproot address generation. +/// TODO remove when `Bitcoin` is migrated to Rust completely. +class EntryV2: public Rust::RustCoinEntry { +}; + /// 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 { +/// 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 const std::vector coinTypes() const { - return { - TWCoinTypeBitcoin, - TWCoinTypeBitcoinCash, - TWCoinTypeBitcoinGold, - TWCoinTypeDash, - TWCoinTypeDigiByte, - TWCoinTypeDogecoin, - TWCoinTypeLitecoin, - TWCoinTypeMonacoin, - TWCoinTypeQtum, - TWCoinTypeRavencoin, - TWCoinTypeViacoin, - TWCoinTypeZcoin, - }; - } - 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 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 final; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const final; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const final; + Data addressToData(TWCoinType coin, const std::string& address) const final; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const final; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const final; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const final; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const final; + // Note: buildTransactionInput is not implemented for Binance chain with UTXOs }; } // namespace TW::Bitcoin 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 d67c28a91bd..1b913ec8032 100644 --- a/src/Bitcoin/FeeCalculator.cpp +++ b/src/Bitcoin/FeeCalculator.cpp @@ -1,54 +1,63 @@ -// 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" +#include #include using namespace TW; namespace TW::Bitcoin { -int64_t DefaultFeeCalculator::calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const { - const auto txsize = ((148 * inputs) + (34 * outputs) + 10); - return int64_t(txsize) * byteFee; -} - -int64_t DefaultFeeCalculator::calculateSingleInput(int64_t byteFee) const { - return int64_t(148) * byteFee; -} +constexpr double gDecredBytesPerInput{166}; +constexpr double gDecredBytesPerOutput{38}; +constexpr double gDecredBytesBase{12}; -int64_t SegwitFeeCalculator::calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const { - const auto txsize = int64_t(std::ceil(101.25 * inputs + 31.0 * outputs + 10.0)); +int64_t LinearFeeCalculator::calculate(int64_t inputs, int64_t outputs, + int64_t byteFee) const noexcept { + const auto txsize = + static_cast(std::ceil(bytesPerInput * static_cast(inputs) + + bytesPerOutput * static_cast(outputs) + bytesBase)); return txsize * byteFee; } -int64_t SegwitFeeCalculator::calculateSingleInput(int64_t byteFee) const { - return int64_t(102) * byteFee; // std::ceil(101.25) = 102 +int64_t LinearFeeCalculator::calculateSingleInput(int64_t byteFee) const noexcept { + return static_cast(std::ceil(bytesPerInput)) * byteFee; // std::ceil(101.25) = 102 } -class DecredFeeCalculator : public FeeCalculator { +class DecredFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override { - const auto txsize = ((166 * inputs) + (38 * outputs) + 12); - return int64_t(txsize) * byteFee; - } + constexpr DecredFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gDecredBytesPerInput, gDecredBytesPerOutput, gDecredBytesBase) + , disableDustFilter(disableFilter) {} - int64_t calculateSingleInput(int64_t byteFee) const override { - return int64_t(166) * byteFee; + int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); } }; -DefaultFeeCalculator defaultFeeCalculator; -DecredFeeCalculator decredFeeCalculator; -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); +static constexpr Zip0317FeeCalculator zip0317FeeCalculator{}; -FeeCalculator& getFeeCalculator(TWCoinType coinType) { +const FeeCalculator& getFeeCalculator(TWCoinType coinType, bool disableFilter, bool zip0317) noexcept { switch (coinType) { case TWCoinTypeDecred: + if (disableFilter) { + return decredFeeCalculatorNoDustFilter; + } return decredFeeCalculator; case TWCoinTypeBitcoin: @@ -57,11 +66,34 @@ FeeCalculator& getFeeCalculator(TWCoinType coinType) { case TWCoinTypeLitecoin: case TWCoinTypeViacoin: case TWCoinTypeGroestlcoin: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: + if (disableFilter) { + return segwitFeeCalculatorNoDustFilter; + } return segwitFeeCalculator; + case TWCoinTypeZcash: + case TWCoinTypeKomodo: + case TWCoinTypeZelcash: + if (zip0317) { + return zip0317FeeCalculator; + } + return defaultFeeCalculator; + default: + if (disableFilter) { + return defaultFeeCalculatorNoDustFilter; + } return defaultFeeCalculator; } } +// https://github.com/Zondax/ledger-zcash-tools/blob/5ecf1c04c69d2454b73aa7acea4eadda563dfeff/ledger-zcash-app-builder/src/txbuilder.rs#L342-L363 +int64_t Zip0317FeeCalculator::calculate(int64_t inputs, int64_t outputs, [[maybe_unused]] int64_t byteFee) const noexcept { + const auto logicalActions = std::max(inputs, outputs); + const auto actions = std::max(gGraceActions, logicalActions); + return gMarginalFee * actions; +} + } // namespace TW::Bitcoin diff --git a/src/Bitcoin/FeeCalculator.h b/src/Bitcoin/FeeCalculator.h index 14c1fa9a459..10c8b9ec691 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 @@ -10,28 +8,100 @@ namespace TW::Bitcoin { +inline constexpr double gDefaultBytesPerInput{148}; +inline constexpr double gDefaultBytesPerOutput{34}; +inline constexpr double gDefaultBytesBase{10}; +inline constexpr double gSegwitBytesPerInput{101.25}; +inline constexpr double gSegwitBytesPerOutput{31}; +inline constexpr double gSegwitBytesBase{gDefaultBytesBase}; + /// Interface for transaction fee calculator. class FeeCalculator { public: - virtual int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const = 0; - virtual int64_t calculateSingleInput(int64_t byteFee) const = 0; + 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; +}; + +/// Generic fee calculator with linear input and output size, and a fix size +class LinearFeeCalculator : public FeeCalculator { +public: + const double bytesPerInput; + const double bytesPerOutput; + const double bytesBase; + explicit constexpr LinearFeeCalculator(double bytesPerInput, double bytesPerOutput, + double bytesBase) noexcept + : bytesPerInput(bytesPerInput), bytesPerOutput(bytesPerOutput), bytesBase(bytesBase) {} + + [[nodiscard]] int64_t calculate(int64_t inputs, int64_t outputs, + int64_t byteFee) const noexcept override; + [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept override; +}; + +/// Constant fee calculator +class ConstantFeeCalculator : public FeeCalculator { +public: + const int64_t fee; + explicit constexpr ConstantFeeCalculator(int64_t fee) noexcept : fee(fee) {} + + [[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([[maybe_unused]] int64_t byteFee) const noexcept final { return 0; } }; /// Default Bitcoin transaction fee calculator, non-segwit. -class DefaultFeeCalculator : public FeeCalculator { +class DefaultFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override; - int64_t calculateSingleInput(int64_t byteFee) const override; + 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 FeeCalculator { +class SegwitFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + +public: + 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); + } +}; + +class Zip0317FeeCalculator: public FeeCalculator { public: - int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override; - int64_t calculateSingleInput(int64_t byteFee) const override; + static constexpr int64_t gMarginalFee = 5000ul; + static constexpr int64_t gGraceActions = 2ul; + + Zip0317FeeCalculator() noexcept = default; + + [[nodiscard]] int64_t calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const noexcept final; + [[nodiscard]] int64_t calculateSingleInput([[maybe_unused]] int64_t byteFee) const noexcept final { + return gMarginalFee; + } }; /// Return the fee calculator for the given coin. -FeeCalculator& getFeeCalculator(TWCoinType coinType); +const FeeCalculator& getFeeCalculator(TWCoinType coinType, bool disableFilter = false, bool zip0317 = false) noexcept; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/InputSelector.cpp b/src/Bitcoin/InputSelector.cpp new file mode 100644 index 00000000000..98a5f4e3358 --- /dev/null +++ b/src/Bitcoin/InputSelector.cpp @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "InputSelector.h" + +#include "UTXO.h" + +#include +#include +#include + +namespace TW::Bitcoin { + +template +uint64_t InputSelector::sum(const std::vector& amounts) noexcept { + uint64_t sum = 0; + for (auto& i : amounts) { + sum += i.amount; + } + return sum; +} + +template +std::vector +InputSelector::filterOutDust(const std::vector& inputs, + int64_t byteFee) noexcept { + auto dustThreshold = static_cast(dustCalculator->dustAmount(byteFee)); + return filterThreshold(inputs, dustThreshold); +} + +// Filters utxos that are dust +template +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 +// [0,1,2,3,4,5,6,7,8,9].eachSlices(3) +// > +// [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], +// [7, 8, 9]] +template +static inline std::vector> +slice(const std::vector& inputs, size_t sliceSize) { + std::vector> slices; + 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; +} + +template +std::vector +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 (sorted.empty() || sum(sorted) < targetValue) { + return {}; + } + assert(sorted.size() >= 1); + + // 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(); + 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); + } + + // difference from 2x targetValue + auto distFrom2x = [doubleTargetValue](uint64_t val) -> uint64_t { + if (val > doubleTargetValue) { + return val - doubleTargetValue; + } + return doubleTargetValue - val; + }; + + 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 (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 (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(); + } + } + + // 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 +std::vector InputSelector::selectSimple(int64_t targetValue, + int64_t byteFee, + int64_t numOutputs) { + // if target value is zero, no UTXOs are needed + if (targetValue == 0) { + return {}; + } + if (_inputs.empty()) { + return {}; + } + assert(_inputs.size() >= 1); + + // 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); + + 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) { + if (input.amount <= dustThreshold) { + continue; // skip dust + } + selected.push_back(input); + sum += input.amount; + if (sum >= increasedTargetValue) { + // we have enough + return selected; + } + } + + // 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); +} + +// Explicitly instantiate +template class Bitcoin::InputSelector; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/InputSelector.h b/src/Bitcoin/InputSelector.h new file mode 100644 index 00000000000..848dae390d4 --- /dev/null +++ b/src/Bitcoin/InputSelector.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "FeeCalculator.h" +#include "DustCalculator.h" +#include + +#include +#include + +namespace TW::Bitcoin { + +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. 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. 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); + + /// Selects UTXOs for max amount; select all except those which would reduce output (dust). + /// Return indices. One output and no change is assumed. + std::vector selectMaxAmount(int64_t byteFee) noexcept; + + /// Construct, using provided feeCalculator (see getFeeCalculator()) and dustCalculator (see getDustCalculator()). + explicit InputSelector(const std::vector& inputs, + const FeeCalculator& feeCalculator, + DustCalculatorShared dustCalculator) noexcept + : _inputs(inputs), + feeCalculator(feeCalculator), + dustCalculator(std::move(dustCalculator)) { + } + + explicit InputSelector(const std::vector& inputs) noexcept + : _inputs(inputs), + feeCalculator(getFeeCalculator(TWCoinTypeBitcoin)), + dustCalculator(std::make_shared(TWCoinTypeBitcoin)) { + } + + /// Sum of input amounts + static uint64_t sum(const std::vector& amounts) noexcept; + /// Filters out utxos that are dust + inline std::vector filterOutDust(const std::vector& inputsIn, + int64_t byteFee) noexcept; + /// Filters out inputsIn below (or equal) a certain threshold limit + inline std::vector filterThreshold(const std::vector& inputsIn, + uint64_t minimumAmount) noexcept; + +private: + 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 84e18fd4601..fd65e3a6a51 100644 --- a/src/Bitcoin/OutPoint.cpp +++ b/src/Bitcoin/OutPoint.cpp @@ -1,16 +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(std::vector& 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 24367be4d9c..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,42 +15,44 @@ 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; - /// Initializes an out-point reference with a hash and an index. + /// Sequence number, matches sequence from Proto::OutPoint (not always used, see also + /// TransactionInput.sequence) + uint32_t sequence; + + /// 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) { - std::copy(std::begin(h), std::end(h), hash.begin()); - this->index = index; - } + 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(); } /// Encodes the out-point into the provided buffer. - void encode(std::vector& 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); + 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; } - - friend bool operator!=(const OutPoint& a, const OutPoint& b) { return !(a == b); } }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 08a9570e99f..1d2112769d4 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -1,34 +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. - -#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)); @@ -40,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; @@ -88,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; @@ -97,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; @@ -147,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()) { @@ -205,46 +237,156 @@ bool Script::getScriptOp(size_t& index, uint8_t& opcode, Data& operand) const { return true; } +Script Script::buildPayToPublicKey(const Data& publicKey) { + assert(publicKey.size() == PublicKey::secp256k1Size || publicKey.size() == PublicKey::secp256k1ExtendedSize); + Script script; + script.bytes.push_back(static_cast(publicKey.size())); + append(script.bytes, publicKey); + script.bytes.push_back(OP_CHECKSIG); + return script; +} + Script Script::buildPayToPublicKeyHash(const Data& hash) { assert(hash.size() == 20); Script script; script.bytes.push_back(OP_DUP); script.bytes.push_back(OP_HASH160); script.bytes.push_back(20); - script.bytes.insert(script.bytes.end(), hash.begin(), hash.end()); + append(script.bytes, hash); script.bytes.push_back(OP_EQUALVERIFY); script.bytes.push_back(OP_CHECKSIG); 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; script.bytes.push_back(OP_HASH160); script.bytes.push_back(20); - script.bytes.insert(script.bytes.end(), scriptHash.begin(), scriptHash.end()); + append(script.bytes, scriptHash); script.bytes.push_back(OP_EQUAL); return script; } -Script Script::buildPayToWitnessProgram(const Data& program) { +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())); - script.bytes.insert(script.bytes.end(), program.begin(), program.end()); + pushDataLength(script.bytes, static_cast(program.size())); + append(script.bytes, program); assert(script.bytes.size() == 22 || script.bytes.size() == 34); return script; } Script Script::buildPayToWitnessPublicKeyHash(const Data& hash) { assert(hash.size() == 20); - return Script::buildPayToWitnessProgram(hash); + return Script::buildPayToV0WitnessProgram(hash); } Script Script::buildPayToWitnessScriptHash(const Data& scriptHash) { assert(scriptHash.size() == 32); - return Script::buildPayToWitnessProgram(scriptHash); + return Script::buildPayToV0WitnessProgram(scriptHash); +} + +Script Script::buildPayToV1WitnessProgram(const Data& publicKey) { + assert(publicKey.size() == 32); + Script script; + script.bytes.push_back(OP_1); + pushDataLength(script.bytes, static_cast(publicKey.size())); + append(script.bytes, publicKey); + assert(script.bytes.size() == 34); + return script; +} + +Script Script::buildOpReturnScript(const Data& data) { + 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); + 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; } void Script::encode(Data& data) const { @@ -252,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); @@ -263,51 +444,122 @@ 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)) { - auto result = SegwitAddress::decode(string); - // address starts with bc/ltc - auto program = std::get<0>(result).witnessProgram; - return buildPayToWitnessProgram(program); - } else if (CashAddress::isValid(string)) { - auto address = CashAddress(string); - auto bitcoinAddress = address.legacyAddress(); - return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); - } else if (Decred::Address::isValid(string)) { - auto bytes = Base58::bitcoin.decodeCheck(string, Hash::blake256d); - 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 (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); + 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); } - if (address.bytes[0] == TW::p2shPrefix(TWCoinTypeGroestlcoin)) { - return buildPayToScriptHash(data); + if (address.witnessVersion == 1 && address.witnessProgram.size() == 32) { + return buildPayToV1WitnessProgram(address.witnessProgram); } - } 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 06db939ccce..dc48452604f 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -1,17 +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 "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; @@ -68,14 +88,26 @@ class Script { /// Matches the script to a multisig 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& 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-witness-program script, P2WSH or P2WPKH. - static Script buildPayToWitnessProgram(const Data& program); + /// 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. @@ -84,10 +116,23 @@ class Script { /// Builds a pay-to-witness-script-hash (P2WSH) script from a script hash. static Script buildPayToWitnessScriptHash(const Data& scriptHash); + /// Builds a V0 pay-to-witness-program script, P2WSH or P2WPKH. + static Script buildPayToV0WitnessProgram(const Data& program); + + /// 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. 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; @@ -100,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 a8b7f6baa42..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)); @@ -24,15 +21,15 @@ bool SegwitAddress::isValid(const std::string& string, const std::string& hrp) { } // extra step to check hrp auto dec = Bech32::decode(string); - if (dec.first != hrp) { + if (std::get<0>(dec) != hrp) { return false; } return true; } -SegwitAddress::SegwitAddress(const PublicKey& publicKey, int witver, std::string hrp) - : hrp(std::move(hrp)), witnessVersion(witver), witnessProgram() { +SegwitAddress::SegwitAddress(const PublicKey& publicKey, std::string hrp) + : hrp(std::move(hrp)), witnessVersion(0), witnessProgram() { if (publicKey.type != TWPublicKeyTypeSECP256k1) { throw std::invalid_argument("SegwitAddress needs a compressed SECP256k1 public key."); } @@ -44,35 +41,51 @@ SegwitAddress::SegwitAddress(const PublicKey& publicKey, int witver, std::string std::tuple SegwitAddress::decode(const std::string& addr) { auto resp = std::make_tuple(SegwitAddress(), "", false); auto dec = Bech32::decode(addr); - if (dec.second.empty()) { + auto& hrp = std::get<0>(dec); + auto& data = std::get<1>(dec); + auto& variant = std::get<2>(dec); + if (data.empty()) { // bech32 decode fails, or decoded data is empty return resp; } - assert(dec.second.size() >= 1); + assert(data.size() >= 1); // First byte is Segwit version - // Only version 0 is currently supported; BIP173 BIP350 - if (dec.second[0] != 0) { - return resp; + auto segwitVersion = data[0]; + if (segwitVersion == 0) { + // v0 uses Bech32 (not M) + if (variant != Bech32::ChecksumVariant::Bech32) { + return resp; + } + } else { // segwitVersion >= 1 + // v1 uses Bech32M, BIP350 + if (variant != Bech32::ChecksumVariant::Bech32M) { + return resp; + } } - auto raw = fromRaw(dec.first, dec.second); - return std::make_tuple(raw.first, dec.first, raw.second); + auto raw = fromRaw(hrp, data); + return std::make_tuple(raw.first, hrp, raw.second); } std::string SegwitAddress::string() const { Data enc; - enc.push_back(static_cast(witnessVersion)); + enc.push_back(witnessVersion); Bech32::convertBits<8, 5, true>(enc, witnessProgram); - std::string result = Bech32::encode(hrp, enc); + Bech32::ChecksumVariant variant = Bech32::ChecksumVariant::Bech32; + if (witnessVersion == 0) { + variant = Bech32::ChecksumVariant::Bech32; + } else if (witnessVersion >= 1) { + variant = Bech32::ChecksumVariant::Bech32M; + } + std::string result = Bech32::encode(hrp, enc, variant); if (!std::get<2>(decode(result))) { return {}; } return result; } -std::pair SegwitAddress::fromRaw(const std::string& hrp, - const std::vector& data) { +std::pair SegwitAddress::fromRaw(const std::string& hrp, const Data& data) { auto resp = std::make_pair(SegwitAddress(), false); if (data.size() == 0) { return resp; @@ -87,3 +100,5 @@ std::pair SegwitAddress::fromRaw(const std::string& hrp, 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 3317d075615..8d65f7d83fc 100644 --- a/src/Bitcoin/SegwitAddress.h +++ b/src/Bitcoin/SegwitAddress.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 "../PublicKey.h" +#include "Data.h" #include #include @@ -25,10 +24,13 @@ class SegwitAddress { std::string hrp; /// Witness program version. - int witnessVersion; + byte witnessVersion; /// Witness program. - std::vector witnessProgram; + 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); @@ -39,11 +41,15 @@ class SegwitAddress { /// Initializes a Bech32 address with a human-readable part, a witness /// version, and a witness program. - SegwitAddress(std::string hrp, int witver, std::vector witprog) + SegwitAddress(std::string hrp, byte witver, Data witprog) : hrp(std::move(hrp)), witnessVersion(witver), witnessProgram(std::move(witprog)) {} - /// Initializes a Bech32 address with a public key and a HRP prefix. - SegwitAddress(const PublicKey& publicKey, int witver, std::string hrp); + /// Initializes a segwit-version-0 Bech32 address with a public key and a HRP prefix. + /// 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. /// @@ -56,8 +62,7 @@ class SegwitAddress { std::string string() const; /// Initializes a Bech32 address with raw data. - static std::pair fromRaw(const std::string& hrp, - const std::vector& data); + static std::pair fromRaw(const std::string& hrp, const Data& data); bool operator==(const SegwitAddress& rhs) const { return hrp == rhs.hrp && witnessVersion == rhs.witnessVersion && diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h index b41c1bf88dd..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 @@ -13,23 +11,24 @@ namespace TW::Bitcoin { // Defines the number of bits of the hash type which is used to identify which // outputs are signed. -static const uint8_t SigHashMask = 0x1f; +static const uint32_t SigHashMask = 0x1f; // Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. -inline uint32_t hashTypeForCoin(enum TWCoinType coinType) { - // set fork hash type for BCH +inline enum TWBitcoinSigHashType hashTypeForCoin(enum TWCoinType coinType) { + // set fork hash type for BCH and XEC switch (coinType) { case TWCoinTypeBitcoinCash: - return (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork; + case TWCoinTypeECash: + return (TWBitcoinSigHashType)((uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); case TWCoinTypeBitcoinGold: - return (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG; + return (TWBitcoinSigHashType)((uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG); default: return TWBitcoinSigHashTypeAll; } } -inline bool hashTypeIsSingle(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeSingle; } +inline bool hashTypeIsSingle(enum TWBitcoinSigHashType type) { return ((uint32_t)type & SigHashMask) == (uint32_t)TWBitcoinSigHashTypeSingle; } -inline bool hashTypeIsNone(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeNone; } +inline bool hashTypeIsNone(enum TWBitcoinSigHashType type) { return ((uint32_t)type & SigHashMask) == (uint32_t)TWBitcoinSigHashTypeNone; } } // namespace TW::Bitcoin diff --git a/src/Bitcoin/SignatureBuilder.cpp b/src/Bitcoin/SignatureBuilder.cpp new file mode 100644 index 00000000000..0e1f621a8d0 --- /dev/null +++ b/src/Bitcoin/SignatureBuilder.cpp @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "SignatureBuilder.h" +#include "SigHashType.h" +#include "TransactionInput.h" +#include "TransactionOutput.h" + +#include "../BinaryCoding.h" +#include "../HexCoding.h" + +#include "../BitcoinDiamond/Transaction.h" +#include "../Groestlcoin/Transaction.h" +#include "../Verge/Transaction.h" +#include "../Zcash/Transaction.h" +#include "../Zcash/TransactionBuilder.h" + +namespace TW::Bitcoin { + +template +Result SignatureBuilder::sign() { + if (plan.error != Common::Proto::OK) { + // plan with error, fail + return Result::failure(plan.error); + } + if (_transaction.inputs.size() == 0 || plan.utxos.size() == 0) { + return Result::failure(Common::Proto::Error_missing_input_utxos); + } + + transactionToSign = _transaction; + transactionToSign.inputs.clear(); + std::copy(std::begin(_transaction.inputs), std::end(_transaction.inputs), + std::back_inserter(transactionToSign.inputs)); + + const auto hashSingle = hashTypeIsSingle(input.hashType); + for (auto i = 0ul; i < plan.utxos.size(); i++) { + // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output + if (hashSingle && i >= _transaction.outputs.size()) { + continue; + } + auto& utxo = plan.utxos[i]; + if (i < _transaction.inputs.size()) { + auto result = sign(utxo.script, i, utxo); + if (!result) { + return Result::failure(result.error()); + } + } + } + + // save estimated size + if ((input.byteFee > 0) && (plan.fee > 0)) { + transactionToSign.previousEstimatedVirtualSize = static_cast(plan.fee / input.byteFee); + } + + return Result::success(std::move(transactionToSign)); +} + +template +Result SignatureBuilder::sign(Script script, size_t index, + const UTXO& utxo) { + assert(index < _transaction.inputs.size()); + + Script redeemScript; + std::vector results; + + uint32_t signatureVersion = [this]() { + if ((input.hashType & TWBitcoinSigHashTypeFork) != 0) { + return WITNESS_V0; + } + return BASE; + }(); + auto result = signStep(script, index, utxo, signatureVersion); + if (!result) { + return Result::failure(result.error()); + } + results = result.payload(); + assert(results.size() >= 1); + auto txin = _transaction.inputs[index]; + + if (script.isPayToScriptHash()) { + script = Script(results[0]); + auto signStepResult = signStep(script, index, utxo, signatureVersion); + if (!signStepResult) { + return Result::failure(signStepResult.error()); + } + results = signStepResult.payload(); + results.push_back(script.bytes); + redeemScript = script; + } + + std::vector witnessStack; + 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()); + } + 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()); + } + witnessStack = _result.payload(); + witnessStack.push_back(std::move(witnessScript.bytes)); + results.clear(); + } else if (script.isWitnessProgram()) { + // Error: Unrecognized witness program. + return Result::failure(Common::Proto::Error_script_witness_program); + } + + if (!redeemScript.bytes.empty()) { + results.push_back(redeemScript.bytes); + } + + auto transactionInput = TransactionInput(txin.previousOutput, Script(pushAll(results)), txin.sequence); + transactionInput.scriptWitness = witnessStack; + transactionToSign.inputs[index] = transactionInput; + return Result::success(); +} + +template +Result, Common::Proto::SigningError> SignatureBuilder::signStep( + Script script, size_t index, const UTXO& utxo, uint32_t version) { + + Data data; + std::vector keys; + int required; + + if (script.matchPayToScriptHash(data) || script.matchPayToScriptHashReplay(data)) { + auto redeemScript = scriptForScriptHash(data); + if (redeemScript.empty()) { + // Error: Missing redeem script + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_redeem); + } + return Result, Common::Proto::SigningError>::success({redeemScript}); + } + if (script.matchPayToWitnessScriptHash(data)) { + auto scripthash = Hash::ripemd(data); + auto redeemScript = scriptForScriptHash(scripthash); + if (redeemScript.empty()) { + // Error: Missing redeem script + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_redeem); + } + return Result, Common::Proto::SigningError>::success({redeemScript}); + } + if (script.matchPayToWitnessPublicKeyHash(data)) { + return Result, Common::Proto::SigningError>::success({data}); + } + if (script.isWitnessProgram()) { + // 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 + 1ul) { + break; + } + auto keyHash = Hash::ripemd(Hash::sha256(pubKey)); + auto pair = keyPairForPubKeyHash(keyHash); + if (!pair.has_value() && signingMode == SigningMode_Normal) { + // Error: missing key + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + } + auto signature = createSignature(transactionToSign, script, keyHash, pair, index, utxo.amount, version); + if (signature.empty()) { + // Error: Failed to sign + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); + } + results.push_back(signature); + } + results.resize(required + 1); + return Result, Common::Proto::SigningError>::success(std::move(results)); + } + if (script.matchPayToPublicKey(data)) { + auto keyHash = Hash::ripemd(Hash::sha256(data)); + auto pair = keyPairForPubKeyHash(keyHash); + if (!pair.has_value() && signingMode == SigningMode_Normal) { + // Error: Missing key + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + } + auto signature = createSignature(transactionToSign, script, keyHash, pair, index, utxo.amount, version); + if (signature.empty()) { + // Error: Failed to sign + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); + } + return Result, Common::Proto::SigningError>::success({signature}); + } + if (script.matchPayToPublicKeyHash(data) + || script.matchPayToPublicKeyHashReplay(data) + || script.matchPayToExchangePublicKeyHash(data)) { + // obtain public key + auto pair = keyPairForPubKeyHash(data); + Data pubkey; + if (!pair.has_value()) { + if (signingMode == SigningMode_SizeEstimationOnly || signingMode == SigningMode_HashOnly) { + // estimation mode, key is missing: use placeholder for public key + pubkey = Data(PublicKey::secp256k1Size); + } else if (signingMode == SigningMode_External) { + size_t _index = hashesForSigning.size(); + if (!externalSignatures.has_value() || externalSignatures.value().size() <= _index) { + // Error: no or not enough signatures provided + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); + } + pubkey = std::get<1>(externalSignatures.value()[_index]); + } else { + // Error: Missing keys + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + } + } else { + pubkey = std::get<1>(pair.value()).bytes; + } + assert(!pubkey.empty()); + + auto signature = createSignature(transactionToSign, script, data, pair, index, utxo.amount, version); + 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}); + } + // Error: Invalid output script + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_output); +} + +template +Data SignatureBuilder::createSignature( + const Transaction& transaction, + const Script& script, + const Data& publicKeyHash, + const std::optional& pair, + size_t index, + Amount amount, + 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)); + + 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 _index = hashesForSigning.size(); + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + + if (!externalSignatures.has_value() || externalSignatures.value().size() <= _index) { + // Error: no or not enough signatures provided + return {}; + } + + 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 {}; + } + const auto publicKeyObj = PublicKey(publicKey, TWPublicKeyTypeSECP256k1); + if (!publicKeyObj.verifyAsDER(externalSignature, sighash)) { + // Error: Signature does not match publickey+hash + return {}; + } + externalSignature.push_back(static_cast(input.hashType)); + + return externalSignature; + } + + const auto key = std::get<0>(pair.value()); + const auto pk = PrivateKey(key); + + auto sig = pk.signAsDER(sighash); + if (!sig.empty()) { + sig.push_back(static_cast(input.hashType)); + } + return sig; +} + +template +Data SignatureBuilder::pushAll(const std::vector& results) { + Data data; + for (auto& result : results) { + if (result.empty()) { + data.push_back(OP_0); + } else if (result.size() == 1 && result[0] >= 1 && result[0] <= 16) { + data.push_back(Script::encodeNumber(result[0])); + } else if (result.size() < OP_PUSHDATA1) { + data.push_back(static_cast(result.size())); + } else if (result.size() <= 0xff) { + data.push_back(OP_PUSHDATA1); + data.push_back(static_cast(result.size())); + } else if (result.size() <= 0xffff) { + data.push_back(OP_PUSHDATA2); + encode16LE(static_cast(result.size()), data); + } else { + data.push_back(OP_PUSHDATA4); + encode32LE(static_cast(result.size()), data); + } + std::copy(begin(result), end(result), back_inserter(data)); + } + return data; +} + +template +std::optional SignatureBuilder::keyPairForPubKeyHash(const Data& hash) const { + for (auto& key : input.privateKeys) { + auto pubKeyExtended = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + auto pubKey = pubKeyExtended.compressed(); + if (Hash::sha256ripemd(pubKey.bytes.data(), pubKey.bytes.size()) == hash) { + return std::make_tuple(key, pubKey); + } + if (Hash::sha256ripemd(pubKeyExtended.bytes.data(), pubKeyExtended.bytes.size()) == hash) { + return std::make_tuple(key, pubKeyExtended); + } + } + return {}; +} + +template +Data SignatureBuilder::scriptForScriptHash(const Data& hash) const { + auto hashString = hex(hash); + auto it = input.scripts.find(hashString); + if (it == input.scripts.end()) { + // Error: Missing redeem script + return {}; + } + return it->second.bytes; +} + +// Explicitly instantiate a Signers for compatible transactions. +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 new file mode 100644 index 00000000000..4f6af0b2124 --- /dev/null +++ b/src/Bitcoin/SignatureBuilder.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Script.h" +#include "SigningInput.h" +#include "Transaction.h" +#include "TransactionInput.h" +#include "Signer.h" +#include "../proto/Bitcoin.pb.h" +#include "../KeyPair.h" +#include "../Result.h" +#include "../PublicKey.h" +#include "../CoinEntry.h" + +#include +#include +#include +#include + +namespace TW::Bitcoin { + +/// Normal and special signature modes +enum SigningMode { + SigningMode_Normal = 0, // normal signing + SigningMode_SizeEstimationOnly, // no signing, only estimate size of the signature + SigningMode_HashOnly, // no signing, only collect hash to be signed + SigningMode_External, // no signing, signatures are provided +}; + +/// Class that performs Bitcoin transaction signing. +template +class SignatureBuilder { +private: + /// Private key and redeem script provider for signing. + SigningInput input; + + /// Transaction plan. + TransactionPlan plan; + + /// Transaction being signed. + Transaction _transaction; + + /// Transaction being signed, with list of signed inputs + Transaction transactionToSign; + + 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: + /// 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( + SigningInput input, + TransactionPlan plan, + Transaction& transaction, + SigningMode signingMode = SigningMode_Normal, + std::optional externalSignatures = {} + ) + : input(std::move(input)), plan(std::move(plan)), _transaction(transaction), signingMode(signingMode), externalSignatures(std::move(externalSignatures)) {} + + /// Signs the transaction. + /// + /// \returns the signed transaction or an error. + Result sign(); + +public: + // internal, public for testability and Decred + static Data pushAll(const std::vector& results); + + HashPubkeyList getHashesForSigning() const { return hashesForSigning; } + +private: + Result sign(Script script, size_t index, const UTXO& utxo); + Result, Common::Proto::SigningError> signStep(Script script, size_t index, + const UTXO& utxo, uint32_t version); + + Data createSignature(const Transaction& transaction, const Script& script, + const Data& publicKeyHash, const std::optional& key, + size_t index, Amount amount, uint32_t version); + + /// Returns the private key for the given public key hash. + std::optional keyPairForPubKeyHash(const Data& hash) const; + + /// Returns the redeem script for the given script hash. + Data scriptForScriptHash(const Data& hash) const; +}; + +} // namespace TW::Bitcoin 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 c9ac3119030..fd542c0eb0b 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,29 +8,36 @@ #include "Transaction.h" #include "TransactionBuilder.h" #include "TransactionSigner.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" -using namespace TW; -using namespace TW::Bitcoin; +#include "proto/Common.pb.h" + +namespace TW::Bitcoin { Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) noexcept { - auto signer = TransactionSigner(std::move(input)); - return signer.plan.proto(); + if (input.has_signing_v2()) { + return planAsV2(input); + } + auto plan = TransactionSigner::plan(input); + return plan.proto(); } -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, std::optional optionalExternalSigs) noexcept { + if (input.has_signing_v2()) { + return signAsV2(input); + } + Proto::SigningOutput output; - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); + 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(); Data encoded; - signer.encodeTx(tx, encoded); + tx.encode(encoded); output.set_encoded(encoded.data(), encoded.size()); Data txHashData = encoded; @@ -45,3 +50,133 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { output.set_transaction_id(hex(txHash)); return output; } + +Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) noexcept { + if (input.has_signing_v2()) { + return preImageHashesAsV2(input); + } + + Proto::PreSigningOutput output; + auto result = 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; +} + +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, + const std::vector& signatures, + const std::vector& publicKeys) noexcept { + if (input.has_signing_v2()) { + return compileAsV2(input, signatures, publicKeys); + } + + Proto::SigningOutput 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; +} + +Proto::TransactionPlan Signer::planAsV2(const Proto::SigningInput& input) noexcept { + 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; +} + +/// Signs a Proto::SigningInput transaction via BitcoinV2 protocol. +Proto::SigningOutput Signer::signAsV2(const Proto::SigningInput& input) noexcept { + 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. + Proto::SigningOutput output; + *output.mutable_signing_result_v2() = signingOutputV2; + return output; +} + +/// Collects pre-image hashes to be signed via BitcoinV2 protocol. +Proto::PreSigningOutput Signer::preImageHashesAsV2(const Proto::SigningInput& input) noexcept { + 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. + Proto::PreSigningOutput output; + *output.mutable_pre_signing_result_v2() = preSigningOutputV2; + return output; +} + +/// Compiles a transaction with the given signatures and public keys via BitcoinV2 protocol. +Proto::SigningOutput Signer::compileAsV2(const Proto::SigningInput& input, + const std::vector& signatures, + const std::vector& publicKeys) noexcept { + 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. + Proto::SigningOutput output; + *output.mutable_signing_result_v2() = outputV2; + return output; +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Signer.h b/src/Bitcoin/Signer.h index 81509b49a03..afc71935155 100644 --- a/src/Bitcoin/Signer.h +++ b/src/Bitcoin/Signer.h @@ -1,11 +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/Bitcoin.pb.h" +#include "Data.h" +#include "CoinEntry.h" + +#include +#include +#include namespace TW::Bitcoin { @@ -17,7 +21,29 @@ class Signer { static Proto::TransactionPlan plan(const Proto::SigningInput& input) noexcept; /// 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; + + /// Collects pre-image hashes to be signed + static Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input) noexcept; + + /// Compiles 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; + + /// Plans a transaction via BitcoinV2 protocol (utxo selection, fee estimation). + static Proto::TransactionPlan planAsV2(const Proto::SigningInput& input) noexcept; + + /// Signs a Proto::SigningInput transaction via BitcoinV2 protocol. + static Proto::SigningOutput signAsV2(const Proto::SigningInput& input) noexcept; + + /// Collects pre-image hashes to be signed via BitcoinV2 protocol. + static Proto::PreSigningOutput preImageHashesAsV2(const Proto::SigningInput& input) noexcept; + + /// Compiles a transaction with the given signatures and public keys via BitcoinV2 protocol. + static Proto::SigningOutput compileAsV2(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 new file mode 100644 index 00000000000..8ef598e9201 --- /dev/null +++ b/src/Bitcoin/SigningInput.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "SigningInput.h" + +namespace TW::Bitcoin { + +SigningInput::SigningInput() + : dustCalculator(std::make_shared(TWCoinTypeBitcoin)) { +} + +SigningInput::SigningInput(const Proto::SigningInput& input) { + hashType = static_cast(input.hash_type()); + amount = input.amount(); + byteFee = input.byte_fee(); + toAddress = input.to_address(); + changeAddress = input.change_address(); + for (auto&& key : input.private_key()) { + privateKeys.emplace_back(key, TWCurveSECP256k1); + } + for (auto&& script : input.scripts()) { + scripts[script.first] = Script(script.second.begin(), script.second.end()); + } + 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(); + zip0317 = input.zip_0317(); + + 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 new file mode 100644 index 00000000000..d012bfe3d9a --- /dev/null +++ b/src/Bitcoin/SigningInput.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Amount.h" +#include "DustCalculator.h" +#include "Transaction.h" +#include "UTXO.h" +#include +#include "../proto/Bitcoin.pb.h" + +#include +#include +#include +#include + +namespace TW::Bitcoin { + +/// Input for signing, info of an unsigned transaction +class SigningInput { +public: + // Hash type to use when signing + enum TWBitcoinSigHashType hashType = TWBitcoinSigHashTypeAll; + + // Amount to send. Transaction created will have this amount in its output, + // except when use_max_amount is set, in that case this amount is not relevant, maximum possible amount will be used (max avail less fee). + // If amount is equal or more than the available amount, also max amount will be used. + Amount amount = 0; + + // Transaction fee per byte + Amount byteFee = 0; + + // Whether to calculate the fee according to ZIP-0317 for the given transaction + bool zip0317 = false; + + // Recipient's address + std::string toAddress; + + // Change address + std::string changeAddress; + + // Available private keys + std::vector privateKeys; + + // Available redeem scripts indexed by script hash + std::map scripts; + + // Available unspent transaction outputs + UTXOs utxos; + + // 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; + + // Optional transaction plan + std::optional plan; + + 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(); + + SigningInput(const Proto::SigningInput& input); +}; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index bfcf6d32151..06d6467cafe 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -1,21 +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 "SegwitAddress.h" #include "Transaction.h" +#include "SegwitAddress.h" +#include "SignatureVersion.h" #include "SigHashType.h" -#include "../BinaryCoding.h" -#include "../Hash.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 { @@ -24,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) { @@ -35,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 { @@ -45,8 +43,8 @@ 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. - reinterpret_cast(inputs[index].previousOutput).encode(data); + // may already be contained in hashSequence. + reinterpret_cast(inputs[index].previousOutput).encode(data); scriptCode.encode(data); encode64LE(amount, data); @@ -59,7 +57,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, } else if (hashTypeIsSingle(hashType) && index < outputs.size()) { Data outputData; outputs[index].encode(outputData); - auto hashOutputs = TW::Hash::hash(hasher, outputData); + auto hashOutputs = Hash::hash(hasher, outputData); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); } else { fill_n(back_inserter(data), 32, 0); @@ -77,10 +75,10 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, Data Transaction::getPrevoutHash() const { Data data; for (auto& input : inputs) { - auto& outpoint = reinterpret_cast(input.previousOutput); + auto& outpoint = reinterpret_cast(input.previousOutput); outpoint.encode(data); } - auto hash = TW::Hash::hash(hasher, data); + auto hash = Hash::hash(hasher, data); return hash; } @@ -89,7 +87,7 @@ Data Transaction::getSequenceHash() const { for (auto& input : inputs) { encode32LE(input.sequence, data); } - auto hash = TW::Hash::hash(hasher, data); + auto hash = Hash::hash(hasher, data); return hash; } @@ -98,19 +96,25 @@ Data Transaction::getOutputsHash() const { for (auto& output : outputs) { output.encode(data); } - auto hash = TW::Hash::hash(hasher, data); + auto hash = Hash::hash(hasher, data); return hash; } 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. @@ -144,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. @@ -163,7 +166,7 @@ Data Transaction::getSignatureHashWitnessV0(const Script& scriptCode, size_t ind enum TWBitcoinSigHashType hashType, uint64_t amount) const { auto preimage = getPreImage(scriptCode, index, hashType, amount); - auto hash = TW::Hash::hash(hasher, preimage); + auto hash = Hash::hash(hasher, preimage); return hash; } @@ -174,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); } @@ -187,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); @@ -202,7 +205,7 @@ Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, // Sighash type encode32LE(hashType, data); - auto hash = TW::Hash::hash(hasher, data); + auto hash = Hash::hash(hasher, data); return hash; } @@ -214,7 +217,7 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size subindex = index; } - reinterpret_cast(inputs[subindex].previousOutput).encode(data); + reinterpret_cast(inputs[subindex].previousOutput).encode(data); // Serialize the script if (subindex != index) { @@ -235,11 +238,11 @@ 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) { - auto protoInput = protoTx.add_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); @@ -248,10 +251,12 @@ Proto::Transaction Transaction::proto() const { } for (const auto& output : outputs) { - auto protoOutput = protoTx.add_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::Bitcoin diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index 5da4a7faade..3f276c9ad58 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/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,18 +8,31 @@ #include "Script.h" #include "TransactionInput.h" #include "TransactionOutput.h" +#include "TransactionPlan.h" +#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 { +/// A list of transaction inputs +template +class TransactionInputs: public std::vector {}; + +/// A list of transaction outputs +template +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 /// @@ -35,13 +46,13 @@ struct Transaction { /// transaction may not be added to a block until after `lockTime`. uint32_t lockTime = 0; - /// A list of 1 or more transaction inputs or sources for coins - std::vector inputs; + // List of transaction inputs + TransactionInputs inputs; - /// A list of 1 or more transaction outputs or destinations for coins - std::vector outputs; + // List of transaction outputs + TransactionOutputs outputs; - TW::Hash::Hasher hasher = TW::Hash::sha256d; + TW::Hash::Hasher hasher = TW::Hash::HasherSha256d; /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) int previousEstimatedVirtualSize = 0; @@ -49,8 +60,8 @@ struct Transaction { public: Transaction() = default; - Transaction(int32_t version, uint32_t lockTime, TW::Hash::Hasher hasher = TW::Hash::sha256d) - : version(version), lockTime(lockTime), inputs(), outputs(), hasher(hasher) {} + Transaction(int32_t version, uint32_t lockTime = 0, TW::Hash::Hasher hasher = TW::Hash::HasherSha256d) + : _version(version), lockTime(lockTime), inputs(), outputs(), hasher(hasher) {} /// Whether the transaction is empty. bool empty() const { return inputs.empty() && outputs.empty(); } @@ -98,8 +109,3 @@ struct Transaction { }; } // namespace TW::Bitcoin - -/// Wrapper for C interface. -struct TWBitcoinTransaction { - TW::Bitcoin::Transaction impl; -}; diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp index ca397a611b3..068db948fbd 100644 --- a/src/Bitcoin/TransactionBuilder.cpp +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -1,42 +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 "TransactionBuilder.h" +#include "Script.h" #include "TransactionSigner.h" +#include "SignatureBuilder.h" #include "../Coin.h" -#include "../proto/Bitcoin.pb.h" #include #include namespace TW::Bitcoin { + +// Above this number of UTXOs a simplified selection is used (optimization) +static const auto SimpleModeLimit = 1000; +// The maximum number of UTXOs to consider. UTXOs above this limit are cut off because it cak take very long +const size_t TransactionBuilder::MaxUtxosHardLimit = 3000; + +std::optional TransactionBuilder::prepareOutputWithScript(std::string address, Amount amount, enum TWCoinType coin) { + auto lockingScript = Script::lockScriptForAddress(address, coin); + if (lockingScript.empty()) { + return {}; + } + return TransactionOutput(amount, lockingScript); +} + + /// Estimate encoded size by simple formula int64_t estimateSimpleFee(const FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, int64_t byteFee) { return feeCalculator.calculate(plan.utxos.size(), outputSize, byteFee); } /// Estimate encoded size by invoking sign(sizeOnly), get actual size -int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const Bitcoin::Proto::SigningInput& input) { - TWPurpose coinPurpose = TW::purpose(static_cast(input.coin_type())); +int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const SigningInput& input) { + TWPurpose coinPurpose = TW::purpose(static_cast(input.coinType)); if (coinPurpose != TWPurposeBIP84) { // not segwit, return default simple estimate - return estimateSimpleFee(feeCalculator, plan, outputSize, input.byte_fee()); + return estimateSimpleFee(feeCalculator, plan, outputSize, input.byteFee); } // duplicate input, with the current plan auto inputWithPlan = std::move(input); - *inputWithPlan.mutable_plan() = plan.proto(); + inputWithPlan.plan = plan; - auto signer = TransactionSigner(std::move(inputWithPlan), true); - auto result = signer.sign(); + auto result = TransactionSigner::sign(inputWithPlan, SigningMode_SizeEstimationOnly); if (!result) { // signing failed; return default simple estimate - return estimateSimpleFee(feeCalculator, plan, outputSize, input.byte_fee()); + return estimateSimpleFee(feeCalculator, plan, outputSize, input.byteFee); } // Obtain the encoded size @@ -57,61 +70,115 @@ int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionP // (in other way: 3/4 of (smaller) non-segwit + 1/4 of segwit size) vSize = sizeNonSegwit + witnessSize/4 + (witnessSize % 4 != 0); } - uint64_t fee = input.byte_fee() * vSize; + uint64_t fee = input.byteFee * vSize; return fee; } -TransactionPlan TransactionBuilder::plan(const Bitcoin::Proto::SigningInput& input) { - auto plan = TransactionPlan(); +int extraOutputCount(const SigningInput& input) { + int count = int(input.outputOpReturn.size() > 0); + return count + int(input.extraOutputs.size()); +} - const auto& feeCalculator = getFeeCalculator(static_cast(input.coin_type())); - auto unspentSelector = UnspentSelector(feeCalculator); - bool maxAmount = input.use_max_amount(); +TransactionPlan TransactionBuilder::plan(const SigningInput& input) { + TransactionPlan plan; + if (input.outputOpReturn.size() > 0) { + plan.outputOpReturn = input.outputOpReturn; + } + plan.outputOpReturnIndex = input.outputOpReturnIndex; - if (input.amount() == 0 && !maxAmount) { + bool maxAmount = input.useMaxAmount; + 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.utxo().empty()) { + } else if (input.utxos.empty()) { plan.error = Common::Proto::Error_missing_input_utxos; } else { + const auto& feeCalculator = getFeeCalculator(input.coinType, input.disableDustFilter, input.zip0317); + auto inputSelector = InputSelector(input.utxos, feeCalculator, input.dustCalculator); + auto inputSum = InputSelector::sum(input.utxos); + // select UTXOs - plan.amount = input.amount(); + 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() >= UnspentSelector::sum(input.utxo())) { + if (!maxAmount && static_cast(totalAmount) >= inputSum) { maxAmount = true; } + auto extraOutputs = extraOutputCount(input); auto output_size = 2; + UTXOs selectedInputs; if (!maxAmount) { - output_size = 2; // output + change - plan.utxos = unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); + // 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.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(totalAmount, input.byteFee, output_size); + } } else { - output_size = 1; // no change - plan.utxos = unspentSelector.selectMaxAmount(input.utxo(), input.byte_fee()); + output_size = 1 + extraOutputs; // output, no change + selectedInputs = inputSelector.selectMaxAmount(input.byteFee); + } + if (selectedInputs.size() <= MaxUtxosHardLimit) { + plan.utxos = selectedInputs; + } else { + // truncate to limit number of selected UTXOs + plan.utxos.clear(); + 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 = UnspentSelector::sum(plan.utxos); + 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.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); @@ -125,14 +192,40 @@ TransactionPlan TransactionBuilder::plan(const Bitcoin::Proto::SigningInput& inp } 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 a4221a65251..667fbe190c7 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -1,17 +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 "SigningInput.h" #include "Transaction.h" #include "TransactionPlan.h" -#include "UnspentSelector.h" +#include "InputSelector.h" +#include "../Result.h" #include "../proto/Bitcoin.pb.h" #include +#include #include namespace TW::Bitcoin { @@ -19,32 +20,66 @@ namespace TW::Bitcoin { class TransactionBuilder { public: /// Plans a transaction by selecting UTXOs and calculating fees. - static TransactionPlan plan(const Bitcoin::Proto::SigningInput& input); + static TransactionPlan plan(const SigningInput& input); - /// Builds a transaction by selecting UTXOs and calculating fees. + /// 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) { - auto lockingScriptTo = Script::lockScriptForAddress(toAddress, coin); - if (lockingScriptTo.empty()) { - return {}; - } - + static Result build(const TransactionPlan& plan, const SigningInput& input) { Transaction tx; - tx.outputs.push_back(TransactionOutput(plan.amount, lockingScriptTo)); + tx.lockTime = input.lockTime; + + 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 lockingScriptChange = Script::lockScriptForAddress(changeAddress, coin); - tx.outputs.push_back(TransactionOutput(plan.change, lockingScriptChange)); + 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()); } const auto emptyScript = Script(); for (auto& utxo : plan.utxos) { - tx.inputs.emplace_back(utxo.out_point(), emptyScript, utxo.out_point().sequence()); + tx.inputs.emplace_back(utxo.outPoint, emptyScript, utxo.outPoint.sequence); } - return tx; + // Optional OP_RETURN output + if (!plan.outputOpReturn.empty()) { + auto lockingScriptOpReturn = 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()); + } + 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 Result(tx); } + + /// Prepares a TransactionOutput with given address and amount, prepares script for it + static std::optional prepareOutputWithScript(std::string address, Amount amount, enum TWCoinType coin); + + /// The maximum number of UTXOs to consider. UTXOs above this limit are cut off because it cak take very long. + static const size_t MaxUtxosHardLimit; }; } // namespace TW::Bitcoin 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 41e33b0cc91..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 @@ -45,8 +43,3 @@ class TransactionInput { }; } // namespace TW::Bitcoin - -/// Wrapper for C interface. -struct TWBitcoinTransactionInput { - TW::Bitcoin::TransactionInput impl; -}; diff --git a/src/Bitcoin/TransactionOutput.cpp b/src/Bitcoin/TransactionOutput.cpp index 16906ab8731..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(std::vector& data) const { +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 3005c67a494..4627f5dc899 100644 --- a/src/Bitcoin/TransactionOutput.h +++ b/src/Bitcoin/TransactionOutput.h @@ -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. #pragma once #include "Amount.h" #include "Script.h" +#include "Data.h" +#include "PublicKey.h" #include @@ -29,12 +29,7 @@ struct TransactionOutput { TransactionOutput(Amount value, Script script) : value(value), script(std::move(script)) {} /// Encodes the output into the provided buffer. - void encode(std::vector& data) const; + void encode(Data& data) const; }; } // namespace TW::Bitcoin - -/// Wrapper for C interface. -struct TWBitcoinTransactionOutput { - TW::Bitcoin::TransactionOutput impl; -}; diff --git a/src/Bitcoin/TransactionPlan.h b/src/Bitcoin/TransactionPlan.h index ec1a41046d5..5124a301ab1 100644 --- a/src/Bitcoin/TransactionPlan.h +++ b/src/Bitcoin/TransactionPlan.h @@ -1,16 +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 "../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. @@ -26,12 +30,24 @@ struct TransactionPlan { Amount change = 0; /// Selected unspent transaction outputs. - std::vector utxos; + UTXOs utxos; /// Zcash branch id - std::vector branchId; + Data branchId; + + /// zen & bitcoin diamond preblockhash + Data preBlockHash; + + /// zen preblockheight + int64_t preBlockHeight = 0; - Common::Proto::SigningError error; + 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; @@ -40,10 +56,17 @@ struct TransactionPlan { , availableAmount(plan.available_amount()) , fee(plan.fee()) , change(plan.change()) - , utxos(plan.utxos().begin(), plan.utxos().end()) + , 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(); @@ -51,8 +74,16 @@ struct TransactionPlan { plan.set_available_amount(availableAmount); plan.set_fee(fee); plan.set_change(change); - *plan.mutable_utxos() = {utxos.begin(), utxos.end()}; + for (auto& utxo: utxos) { + *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 0df892c97bc..282117df361 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -1,299 +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 "TransactionSigner.h" -#include "TransactionInput.h" -#include "TransactionOutput.h" -#include "UnspentSelector.h" -#include "SigHashType.h" +#include "SignatureBuilder.h" -#include "../BinaryCoding.h" -#include "../Hash.h" -#include "../HexCoding.h" -#include "../Zcash/Transaction.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; - -template -Result TransactionSigner::sign() { - if (plan.error != Common::Proto::OK) { - // plan with error, fail - return Result::failure(plan.error); - } - if (transaction.inputs.size() == 0 || plan.utxos.size() == 0) { - return Result::failure(Common::Proto::Error_missing_input_utxos); - } - - signedInputs.clear(); - std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), - std::back_inserter(signedInputs)); - - const auto hashSingle = hashTypeIsSingle(static_cast(input.hash_type())); - for (auto i = 0; i < plan.utxos.size(); i++) { - // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output - if (hashSingle && i >= transaction.outputs.size()) { - continue; - } - auto& utxo = plan.utxos[i]; - auto script = Script(utxo.script().begin(), utxo.script().end()); - if (i < transaction.inputs.size()) { - auto result = sign(script, i, utxo); - if (!result) { - return Result::failure(result.error()); - } - } - } - - Transaction tx(transaction); - tx.inputs = move(signedInputs); - tx.outputs = transaction.outputs; - // save estimated size - if ((input.byte_fee()) > 0 && (plan.fee > 0)) { - tx.previousEstimatedVirtualSize = static_cast(plan.fee / input.byte_fee()); - } - - return Result::success(std::move(tx)); -} - -template -Result TransactionSigner::sign(Script script, size_t index, - const Bitcoin::Proto::UnspentTransaction& utxo) { - assert(index < transaction.inputs.size()); - - Script redeemScript; - std::vector results; - - uint32_t signatureVersion = [this]() { - if ((input.hash_type() & TWBitcoinSigHashTypeFork) != 0) { - return WITNESS_V0; - } else { - return BASE; - } - }(); - auto result = signStep(script, index, utxo, signatureVersion); - if (!result) { - return Result::failure(result.error()); - } - results = result.payload(); - assert(results.size() >= 1); - 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()); - } - results = result.payload(); - results.push_back(script.bytes); - redeemScript = script; - } - - std::vector witnessStack; - 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()); - } - 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()); - } - witnessStack = result.payload(); - witnessStack.push_back(move(witnessScript.bytes)); - results.clear(); - } else if (script.isWitnessProgram()) { - // Error: Unrecognized witness program. - return Result::failure(Common::Proto::Error_script_witness_program); - } - - if (!redeemScript.bytes.empty()) { - results.push_back(redeemScript.bytes); - } - - signedInputs[index] = - TransactionInput(txin.previousOutput, Script(pushAll(results)), txin.sequence); - signedInputs[index].scriptWitness = witnessStack; - return Result::success(); -} - -template -Result, Common::Proto::SigningError> TransactionSigner::signStep( - Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo, uint32_t version) const { - Transaction transactionToSign(transaction); - transactionToSign.inputs = signedInputs; - transactionToSign.outputs = transaction.outputs; - - Data data; - std::vector keys; - int required; - - if (script.matchPayToScriptHash(data)) { - auto redeemScript = scriptForScriptHash(data); - if (redeemScript.empty()) { - // Error: Missing redeem script - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_redeem); - } - return Result, Common::Proto::SigningError>::success({redeemScript}); - } - if (script.matchPayToWitnessScriptHash(data)) { - auto scripthash = TW::Hash::ripemd(data); - auto redeemScript = scriptForScriptHash(scripthash); - if (redeemScript.empty()) { - // Error: Missing redeem script - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_redeem); - } - return Result, Common::Proto::SigningError>::success({redeemScript}); - } - if (script.matchPayToWitnessPublicKeyHash(data)) { - return Result, Common::Proto::SigningError>::success({data}); - } - if (script.isWitnessProgram()) { - // Error: Invalid sutput 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) { - break; - } - auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(pubKey)); - auto key = keyForPublicKeyHash(keyHash); - if (key.empty() && !estimationMode) { - // Error: missing key - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); - } - auto signature = - createSignature(transactionToSign, script, key, index, utxo.amount(), version); - if (signature.empty()) { - // Error: Failed to sign - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); - } - results.push_back(signature); - } - results.resize(required + 1); - return Result, Common::Proto::SigningError>::success(std::move(results)); - } - if (script.matchPayToPublicKey(data)) { - auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(data)); - auto key = keyForPublicKeyHash(keyHash); - if (key.empty() && !estimationMode) { - // Error: Missing key - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); - } - auto signature = - createSignature(transactionToSign, script, key, index, utxo.amount(), version); - if (signature.empty()) { - // Error: Failed to sign - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); - } - return Result, Common::Proto::SigningError>::success({signature}); - } - if (script.matchPayToPublicKeyHash(data)) { - auto key = keyForPublicKeyHash(data); - if (key.empty() && !estimationMode) { - // Error: Missing keys - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); - } - - auto signature = - createSignature(transactionToSign, script, key, index, utxo.amount(), version); - if (signature.empty()) { - // Error: Failed to sign - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); - } - if (key.empty() && estimationMode) { - // estimation mode, key is missing: use placeholder for public key - return Result, Common::Proto::SigningError>::success({signature, Data(PublicKey::secp256k1Size)}); - } - auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); - return Result, Common::Proto::SigningError>::success({signature, pubkey.bytes}); - } - // Error: Invalid output script - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_output); -} +namespace TW::Bitcoin { template -Data TransactionSigner::createSignature(const Transaction& transaction, - const Script& script, const Data& key, - size_t index, Amount amount, - uint32_t version) const { - if (estimationMode) { - // Don't sign, only estimate signature size. It is 71-72 bytes. Return placeholder. - return Data(72); - } - Data sighash = transaction.getSignatureHash(script, index, static_cast(input.hash_type()), amount, - static_cast(version)); - auto pk = PrivateKey(key); - auto sig = pk.signAsDER(sighash, TWCurveSECP256k1); - if (!sig.empty()) { - sig.push_back(static_cast(input.hash_type())); - } - return sig; +TransactionPlan TransactionSigner::plan(const SigningInput& input) { + return TransactionBuilder::plan(input); } template -Data TransactionSigner::pushAll(const std::vector& results) { - Data data; - for (auto& result : results) { - if (result.empty()) { - data.push_back(OP_0); - } else if (result.size() == 1 && result[0] >= 1 && result[0] <= 16) { - data.push_back(Script::encodeNumber(result[0])); - } else if (result.size() < OP_PUSHDATA1) { - data.push_back(static_cast(result.size())); - } else if (result.size() <= 0xff) { - data.push_back(OP_PUSHDATA1); - data.push_back(static_cast(result.size())); - } else if (result.size() <= 0xffff) { - data.push_back(OP_PUSHDATA2); - encode16LE(static_cast(result.size()), data); - } else { - data.push_back(OP_PUSHDATA4); - encode32LE(static_cast(result.size()), data); - } - std::copy(begin(result), end(result), back_inserter(data)); - } - return data; +Result TransactionSigner::sign(const SigningInput& input, bool estimationMode, std::optional optionalExternalSigs) { + TransactionPlan plan; + if (input.plan.has_value()) { + plan = input.plan.value(); + } else { + plan = TransactionBuilder::plan(input); + } + 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; + SignatureBuilder signer(std::move(input), plan, transaction, signingMode, optionalExternalSigs); + return signer.sign(); } template -Data TransactionSigner::keyForPublicKeyHash(const Data& hash) const { - for (auto& key : input.private_key()) { - auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); - auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(publicKey.bytes)); - if (keyHash == hash) { - return Data(key.begin(), key.end()); - } - } - return {}; -} - -template -Data TransactionSigner::scriptForScriptHash(const Data& hash) const { - auto hashString = hex(hash); - auto it = input.scripts().find(hashString); - if (it == input.scripts().end()) { - // Error: Missing redeem script - return {}; - } - return Data(it->second.begin(), it->second.end()); +Result TransactionSigner::preImageHashes(const SigningInput& input) { + TransactionPlan plan; + if (input.plan.has_value()) { + plan = input.plan.value(); + } else { + plan = TransactionBuilder::plan(input); + } + 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) { + return Result::failure(signResult.error()); + } + return Result::success(signer.getHashesForSigning()); } // Explicitly instantiate a Signers for compatible transactions. -template class TW::Bitcoin::TransactionSigner; -template class TW::Bitcoin::TransactionSigner; -template class TW::Bitcoin::TransactionSigner; +template class Bitcoin::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 8ed6f98f837..8f67a93cc5c 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -1,88 +1,37 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, 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 "SigningInput.h" #include "Transaction.h" #include "TransactionBuilder.h" -#include "TransactionInput.h" -#include "../Groestlcoin/Transaction.h" -#include "../Hash.h" -#include "../PrivateKey.h" +#include "Signer.h" +#include "Data.h" +#include "../KeyPair.h" #include "../Result.h" -#include "../Zcash/Transaction.h" -#include "../Zcash/TransactionBuilder.h" #include "../proto/Bitcoin.pb.h" +#include "../CoinEntry.h" -#include -#include #include +#include +#include namespace TW::Bitcoin { -/// Helper class that performs Bitcoin transaction signing. +/// Frontend class for transaction planning, building, and signing template class TransactionSigner { - private: - /// Private key and redeem script provider for signing. - Proto::SigningInput input; +public: + // Create plan for a transaction + static TransactionPlan plan(const SigningInput& input); - public: - /// Transaction plan. - TransactionPlan plan; + // Sign an unsigned transaction. Plan it if needed beforehand. + static Result sign(const SigningInput& input, bool estimationMode = false, std::optional optionalExternalSigs = {}); - /// Transaction being signed. - Transaction transaction; - - private: - /// List of signed inputs. - std::vector signedInputs; - - bool estimationMode = false; - - public: - /// 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 - TransactionSigner(const Bitcoin::Proto::SigningInput& input, bool estimationMode = false) : - input(input), estimationMode(estimationMode) { - if (input.has_plan()) { - plan = TransactionPlan(input.plan()); - } else { - plan = TransactionBuilder::plan(input); - } - transaction = TransactionBuilder::template build( - plan, input.to_address(), input.change_address(), TWCoinType(input.coin_type()) - ); - } - - /// Signs the transaction. - /// - /// \returns the signed transaction or an error. - Result sign(); - - // helper, return binary encoded transaction (used right after sign()) - static void encodeTx(const Transaction& tx, Data& outData) { tx.encode(outData); } - - // internal, public for testability and Decred - static Data pushAll(const std::vector& results); - - private: - Result sign(Script script, size_t index, const Proto::UnspentTransaction& utxo); - Result, Common::Proto::SigningError> signStep(Script script, size_t index, - const Proto::UnspentTransaction& utxo, uint32_t version) const; - Data createSignature(const Transaction& transaction, const Script& script, const Data& key, - size_t index, Amount amount, uint32_t version) const; - - /// Returns the private key for the given public key hash. - Data keyForPublicKeyHash(const Data& hash) const; - - /// Returns the redeem script for the given script hash. - Data scriptForScriptHash(const Data& hash) const; + /// Collect pre-image hashes to be signed + static Result preImageHashes(const SigningInput& input); }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/UTXO.h b/src/Bitcoin/UTXO.h new file mode 100644 index 00000000000..02fdf2dffee --- /dev/null +++ b/src/Bitcoin/UTXO.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "OutPoint.h" +#include "Script.h" +#include "Amount.h" +#include "../proto/Bitcoin.pb.h" + +#include + +namespace TW::Bitcoin { + +class UTXO { +public: + // The unspent output + OutPoint outPoint; + + // Script for claiming this UTXO + Script script; + + // Amount of the UTXO + Amount amount; + +public: + UTXO() = default; + + UTXO(const Proto::UnspentTransaction& utxo) + : outPoint(utxo.out_point()) + , script(utxo.script().begin(), utxo.script().end()) + , amount(utxo.amount()) + {} + + Proto::UnspentTransaction proto() const { + auto utxo = Proto::UnspentTransaction(); + *utxo.mutable_out_point() = outPoint.proto(); + utxo.set_script(std::string(script.bytes.begin(), script.bytes.end())); + utxo.set_amount(amount); + return utxo; + } +}; + +/// A list of UTXO's +class UTXOs: public std::vector { +public: + UTXOs() = default; + UTXOs(const std::vector& vector): std::vector(vector) {} + UTXOs(UTXO utxo): std::vector({utxo}) {} +}; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/UnspentSelector.cpp b/src/Bitcoin/UnspentSelector.cpp deleted file mode 100644 index c5fa6dbc80c..00000000000 --- a/src/Bitcoin/UnspentSelector.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 "UnspentSelector.h" - -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - -/// A selection of unspent transactions. -struct Selection { - std::vector utxos; - int64_t total; -}; - -// Filters utxos that are dust -template -std::vector -UnspentSelector::filterDustInput(const T& selectedUtxos, int64_t byteFee) { - auto inputFeeLimit = feeCalculator.calculateSingleInput(byteFee); - std::vector filteredUtxos; - for (auto utxo: selectedUtxos) { - if (utxo.amount() > inputFeeLimit) { - filteredUtxos.push_back(utxo); - } - } - return filteredUtxos; -} - -// Slice Array -// [0,1,2,3,4,5,6,7,8,9].eachSlices(3) -// > -// [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], -// [7, 8, 9]] -template -static inline auto slice(const T& elements, size_t sliceSize) { - std::vector> slices; - for (auto i = 0; i <= elements.size() - sliceSize; i += 1) { - slices.emplace_back(); - slices[i].reserve(sliceSize); - for (auto j = i; j < i + sliceSize; j += 1) { - slices[i].push_back(elements[j]); - } - } - return slices; -} - -template -std::vector -UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs) { - // if target value is zero, no UTXOs are needed - if (targetValue == 0) { - return {}; - } - - // total values of utxos should be greater than targetValue - if (utxos.empty() || sum(utxos) < targetValue) { - return {}; - } - assert(utxos.size() >= 1); - - // definitions for the following caluculation - const auto doubleTargetValue = targetValue * 2; - - // Get all possible utxo selections up to a maximum size, sort by total amount, increasing - auto sortedUtxos = utxos; - std::sort(sortedUtxos.begin(), sortedUtxos.end(), - [](const Proto::UnspentTransaction& lhs, const Proto::UnspentTransaction& rhs) { - return lhs.amount() < rhs.amount(); - }); - // Precompute maximum amount possible to obtain with given number of UTXOs - const auto n = sortedUtxos.size(); - std::vector maxWithXInputs = std::vector(); - maxWithXInputs.push_back(0); - int64_t maxSum = 0; - for (auto i = 0; i < n; ++i) { - maxSum += sortedUtxos[n - 1 - i].amount(); - maxWithXInputs.push_back(maxSum); - } - - // difference from 2x targetValue - auto distFrom2x = [doubleTargetValue](int64_t val) -> int64_t { - if (val > doubleTargetValue) - return val - doubleTargetValue; - else - return doubleTargetValue - val; - }; - - const int64_t dustThreshold = feeCalculator.calculateSingleInput(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) { - 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(sortedUtxos, 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 filterDustInput(slices.front(), byteFee); - } - } - - // 2. If not, find a valid combination of outputs even if they produce dust change. - for (int64_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(sortedUtxos, 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 filterDustInput(slices.front(), byteFee); - } - } - - return {}; -} - -template -std::vector -UnspentSelector::selectMaxAmount(const T& utxos, int64_t byteFee) { - return filterDustInput(utxos, byteFee); -} - -template std::vector UnspentSelector::select(const ::google::protobuf::RepeatedPtrField& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs); -template std::vector UnspentSelector::select(const std::vector& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs); -template std::vector UnspentSelector::selectMaxAmount(const ::google::protobuf::RepeatedPtrField& utxos, int64_t byteFee); -template std::vector UnspentSelector::selectMaxAmount(const std::vector& utxos, int64_t byteFee); diff --git a/src/Bitcoin/UnspentSelector.h b/src/Bitcoin/UnspentSelector.h deleted file mode 100644 index 150fda9b400..00000000000 --- a/src/Bitcoin/UnspentSelector.h +++ /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. - -#pragma once - -#include -#include - -#include "FeeCalculator.h" -#include -#include "../proto/Bitcoin.pb.h" - -namespace TW::Bitcoin { - -class UnspentSelector { - public: - /// Selects unspent transactions to use given a target transaction value. - /// - /// \returns the list of selected utxos or an empty list if there are - /// insufficient funds. - template - std::vector select(const T& utxos, int64_t targetValue, - int64_t byteFee, int64_t numOutputs = 2); - - /// Selects UTXOs for max amount; select all except those which would reduce output (dust). - /// One output and no change is assumed. - template - std::vector selectMaxAmount(const T& utxos, int64_t byteFee); - - /// Construct, using provided feeCalculator (see getFeeCalculator()). - explicit UnspentSelector(const FeeCalculator& feeCalculator) : feeCalculator(feeCalculator) {} - UnspentSelector() : UnspentSelector(getFeeCalculator(TWCoinTypeBitcoin)) {} - - template - static inline int64_t sum(const T& utxos) { - int64_t sum = 0; - for (auto& utxo : utxos) { - sum += utxo.amount(); - } - return sum; - } - - private: - const FeeCalculator& feeCalculator; - template std::vector filterDustInput(const T& selectedUtxos, int64_t byteFee); -}; - -} // namespace TW::Bitcoin diff --git a/src/BitcoinCash/Entry.h b/src/BitcoinCash/Entry.h new file mode 100644 index 00000000000..cc5e78fe61b --- /dev/null +++ b/src/BitcoinCash/Entry.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Bitcoin/Entry.h" + +namespace TW::BitcoinCash { + +/// Entry point for BitcoinCash coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +/// +/// Currently, we must support the legacy `Bitcoin.proto` API, +/// but `BitcoinV2.proto` still can be used through `Bitcoin.SigningInput.signing_v2`. +/// TODO inherit Rust::RustCoinEntry directly when `Bitcoin.proto` is deprecated. +class Entry : public Bitcoin::Entry { +}; + +} // namespace TW::BitcoinCash + 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 5d26a9c2d7d..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,18 +66,18 @@ AddressV2::AddressV2(const std::string& string) { AddressV2::AddressV2(const PublicKey& publicKey) { // input is extended pubkey, 64-byte - if (publicKey.type != TWPublicKeyTypeED25519Extended) { + if (publicKey.type != TWPublicKeyTypeED25519Cardano || publicKey.bytes.size() != PublicKey::cardanoKeySize) { throw std::invalid_argument("Invalid public key type"); } type = 0; // public key - root = keyHash(publicKey.bytes); + root = keyHash(subData(publicKey.bytes, 0, 64)); // address attributes: empty map for V2, for V1 encrypted derivation path Cbor::Encode emptyMap = Cbor::Encode::map({}); attrs = emptyMap.encoded(); } 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 0f3d6ef5a62..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; @@ -77,8 +75,3 @@ inline bool operator==(const AddressV2& lhs, const AddressV2& rhs) { } } // namespace TW::Cardano - -/// Wrapper for C interface. -struct TWCardanoAddressV2 { - TW::Cardano::AddressV2 impl; -}; diff --git a/src/Cardano/AddressV3.cpp b/src/Cardano/AddressV3.cpp index 70ea2d4768b..08a66a8fcda 100644 --- a/src/Cardano/AddressV3.cpp +++ b/src/Cardano/AddressV3.cpp @@ -1,127 +1,170 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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, Discrimination& discrimination, Kind& kind, Data& key1, Data& key2) { +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); + case Kind_DRep: + // In case of DRep, the address can be either 28 (CIP105) or 29 bytes (CIP129) long + return (length == EncodedSize1 || length == HashSize); + + default: + // accept other types as well + return true; + } +} + +// In case of DRep, this function does not compute the kind, but expects it to be provided +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 = (kind != Kind_DRep) ? kindFromFirstByte(raw[0]) : Kind_DRep; + bytes = Data(); + switch (kind) { + case Kind_DRep: + if (raw.size() == EncodedSize1) { + std::copy(cbegin(raw) + 1, cend(raw), std::back_inserter(bytes)); + } else if (raw.size() == HashSize) { + std::copy(cbegin(raw), cend(raw), std::back_inserter(bytes)); + } else { + return false; + } + break; + case Kind_Enterprise: + case Kind_Base: + case Kind_Reward: + default: + std::copy(cbegin(raw) + 1, cend(raw), std::back_inserter(bytes)); + networkId = networkIdFromFirstByte(raw[0]); + if (networkId != Network_Production) { + return false; + } + break; + } + 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 (bech.second.size() == 0) { + if (std::get<1>(bech).empty()) { // empty Bech data return false; } // Bech bits conversion Data conv; - auto success = Bech32::convertBits<5, 8, false>(conv, bech.second); + auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(bech)); if (!success) { return false; } - if (conv.size() != 33 && conv.size() != 65) { - return false; - } - discrimination = (Discrimination)((conv[0] & 0b10000000) >> 7); - kind = (Kind)(conv[0] & 0b01111111); - if (kind <= Kind_Sentinel_Low || kind >= Kind_Sentinel_High) { + + if (!parseAndCheckV3(conv, networkId, kind, bytes)) { return false; } - if ((kind == Kind_Group && conv.size() != 65) || - (kind != Kind_Group && conv.size() != 33)) { + + // check prefix + if (const auto expectedHrp = getHrp(kind); !addr.starts_with(expectedHrp)) { return false; } - switch (kind) { - case Kind_Single: - case Kind_Account: - case Kind_Multisig: - assert(conv.size() == 33); - key1 = Data(32); - std::copy(conv.begin() + 1, conv.begin() + 33, key1.begin()); - return true; - - case Kind_Group: - assert(conv.size() == 65); - key1 = Data(32); - key2 = Data(32); - std::copy(conv.begin() + 1, conv.begin() + 33, key1.begin()); - std::copy(conv.begin() + 33, conv.begin() + 65, key2.begin()); - return true; - - default: - return false; - } + return true; } catch (...) { return false; } } bool AddressV3::isValid(const std::string& addr) { - Discrimination discrimination; - Kind kind; - Data key1; - Data key2; - if (parseAndCheckV3(addr, discrimination, kind, key1, key2)) { + NetworkId networkId; + Kind kind = Kind_Base; + Data key; + return parseAndCheckV3(addr, networkId, kind, key); +} + +bool AddressV3::isValidLegacy(const std::string& addr) { + if (isValid(addr)) { return true; } // not V3, try older return AddressV2::isValid(addr); } -AddressV3 AddressV3::createSingle(Discrimination discrimination_in, const Data& spendingKey) { - if (spendingKey.size() != 32) { - throw std::invalid_argument("Wrong spending key size"); +Data blakeHash(Data d) { + assert(d.size() == 32); + return Hash::blake2b(d.data(), d.size(), AddressV3::HashSize); +} + +AddressV3 AddressV3::createBase(NetworkId networkId, const TW::Data& spendingKeyHash, const TW::Data& stakingKeyHash) { + if (spendingKeyHash.size() != HashSize) { + throw std::invalid_argument("Wrong spending key hash size"); + } + if (stakingKeyHash.size() != HashSize) { + throw std::invalid_argument("Wrong spending key hash size"); } auto addr = AddressV3(); - addr.discrimination = discrimination_in; - addr.kind = Kind_Single; - addr.key1 = spendingKey; + addr.networkId = networkId; + addr.kind = Kind_Base; + + addr.bytes = Data(); + append(addr.bytes, spendingKeyHash); + append(addr.bytes, stakingKeyHash); + return addr; } -AddressV3 AddressV3::createGroup(Discrimination discrimination_in, const Data& spendingKey, const Data& groupKey) { - if (spendingKey.size() != 32) { +AddressV3 AddressV3::createBase(NetworkId networkId, const PublicKey& spendingKey, const PublicKey& stakingKey) { + if (spendingKey.bytes.size() != 32) { throw std::invalid_argument("Wrong spending key size"); } - if (groupKey.size() != 32) { - throw std::invalid_argument("Wrong group key size"); + if (stakingKey.bytes.size() != 32) { + throw std::invalid_argument("Wrong spending key size"); } - auto addr = AddressV3(); - addr.discrimination = discrimination_in; - addr.kind = Kind_Group; - addr.key1 = spendingKey; - addr.groupKey = groupKey; - return addr; + + const Data hash1 = blakeHash(spendingKey.bytes); + const Data hash2 = blakeHash(stakingKey.bytes); + return createBase(networkId, hash1, hash2); } -AddressV3 AddressV3::createAccount(Discrimination discrimination_in, const Data& accountKey) { - if (accountKey.size() != 32) { - throw std::invalid_argument("Wrong spending key size"); +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.discrimination = discrimination_in; - addr.kind = Kind_Account; - addr.key1 = accountKey; + addr.networkId = networkId; + addr.kind = Kind_Reward; + addr.bytes = stakingKeyHash; return addr; } +AddressV3 AddressV3::createDRep(const std::string& addr) { + auto address = AddressV3(); + address.kind = Kind_DRep; + if (!address.parseAndCheckV3(addr, address.networkId, address.kind, address.bytes)) { + throw std::invalid_argument("Invalid DRep address"); + } + return address; +} + AddressV3::AddressV3(const std::string& addr) { - if (parseAndCheckV3(addr, discrimination, kind, key1, groupKey)) { + if (parseAndCheckV3(addr, networkId, kind, bytes)) { // values stored return; } @@ -131,115 +174,82 @@ AddressV3::AddressV3(const std::string& addr) { } AddressV3::AddressV3(const PublicKey& publicKey) { - // input is extended pubkey, 64-byte - if (publicKey.type != TWPublicKeyTypeED25519Extended) { + // input is double extended pubkey + if (publicKey.type != TWPublicKeyTypeED25519Cardano || publicKey.bytes.size() != PublicKey::cardanoKeySize) { throw std::invalid_argument("Invalid public key type"); } - discrimination = Discrim_Test; - kind = Kind_Group; - key1 = Data(32); - groupKey = Data(32); - std::copy(publicKey.bytes.begin(), publicKey.bytes.begin() + 32, key1.begin()); - std::copy(publicKey.bytes.begin() + 32, publicKey.bytes.begin() + 64, groupKey.begin()); + *this = createBase(Network_Production, PublicKey(subData(publicKey.bytes, 0, 32), TWPublicKeyTypeED25519), PublicKey(subData(publicKey.bytes, 64, 32), TWPublicKeyTypeED25519)); } AddressV3::AddressV3(const Data& data) { - // min 4 bytes, 2 prefix + 2 len - if (data.size() < 4) { throw std::invalid_argument("Address data too short"); } - assert(data.size() >= 4); - int index = 0; - discrimination = (Discrimination)data[index++]; - kind = (Kind)data[index++]; - // key1: - byte len1 = data[index++]; - if (data.size() < 4 + len1) { throw std::invalid_argument("Address data too short"); } - assert(data.size() >= 4 + len1); - key1 = Data(len1); - std::copy(data.begin() + index, data.begin() + index + len1, key1.begin()); - index += len1; - // groupKey: - byte len2 = data[index++]; - if (data.size() < 4 + len1 + len2) { throw std::invalid_argument("Address data too short"); } - assert(data.size() >= 4 + len1 + len2); - groupKey = Data(len2); - std::copy(data.begin() + index, data.begin() + index + len2, groupKey.begin()); -} - -AddressV3::AddressV3(const AddressV3& other) : - discrimination(other.discrimination), - kind(other.kind), - key1(other.key1), - groupKey(other.groupKey), - legacyAddressV2(other.legacyAddressV2) -{} - -void AddressV3::operator=(const AddressV3& other) -{ - discrimination = other.discrimination; - kind = other.kind; - key1 = other.key1; - groupKey = other.groupKey; - legacyAddressV2 = other.legacyAddressV2; -} - -string AddressV3::string() const { - std::string hrp; + parseAndCheckV3(data, networkId, kind, bytes); +} + +AddressV3::AddressV3(const AddressV3& other) = default; + +uint8_t AddressV3::firstByte(NetworkId networkId, Kind kind) { + auto first = (byte)(((byte)kind << 4) + networkId); + return first; +} + +AddressV3::NetworkId AddressV3::networkIdFromFirstByte(uint8_t first) { + return (NetworkId)(first & 0x0F); +} + +AddressV3::Kind AddressV3::kindFromFirstByte(uint8_t first) { + return (Kind)((first & 0xF0) >> 4); +} + +std::string AddressV3::getHrp(Kind kind) noexcept { switch (kind) { - case Kind_Single: - case Kind_Group: - case Kind_Account: - hrp = stringForHRP(TWHRPCardano); break; - default: - hrp = ""; break; + case Kind_Base: + case Kind_Enterprise: + default: + return stringForHRP(TWHRPCardano); + case Kind_Reward: + return "stake"; + case Kind_DRep: + return "drep"; } +} + +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(); } - byte first = (byte)kind; - if (discrimination == Discrim_Test) first = first | 0b10000000; - Data keys; - TW::append(keys, first); - TW::append(keys, key1); - if (groupKey.size() > 0) { - TW::append(keys, groupKey); - } + const Data raw = data(); // bech Data bech; - if (!Bech32::convertBits<8, 5, true>(bech, keys)) { + if (!Bech32::convertBits<8, 5, true>(bech, raw)) { return ""; } - return Bech32::encode(hrp, bech); + return Bech32::encode(hrp, bech, Bech32::ChecksumVariant::Bech32); } -string AddressV3::stringBase32() const { +Data AddressV3::data() const noexcept { if (legacyAddressV2.has_value()) { - return legacyAddressV2->string(); + return legacyAddressV2->getCborData(); } - byte first = (byte)kind; - if (discrimination == Discrim_Test) first = first | 0b10000000; - Data keys; - TW::append(keys, first); - TW::append(keys, key1); - if (groupKey.size() > 0) { - TW::append(keys, groupKey); - } - std::string base32 = Base32::encode(keys, "abcdefghijklmnopqrstuvwxyz23456789"); - return base32; + const byte first = firstByte(networkId, kind); + Data raw; + TW::append(raw, first); + TW::append(raw, bytes); + return raw; } -Data AddressV3::data() const { - Data data; - TW::append(data, (uint8_t)discrimination); - TW::append(data, (uint8_t)kind); - TW::append(data, (uint8_t)key1.size()); - TW::append(data, key1); - TW::append(data, (uint8_t)groupKey.size()); - TW::append(data, groupKey); - return data; +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 863815bae3c..8f6d03db86b 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 @@ -18,42 +16,59 @@ namespace TW::Cardano { /// A Cardano-Shelley address, V3 or V2. class AddressV3 { public: - enum Discrimination: uint8_t { - Discrim_Production = 0, - Discrim_Test = 1, + enum NetworkId: uint8_t { + Network_Test = 0, + Network_Production = 1, }; enum Kind: uint8_t { - Kind_Sentinel_Low = 2, - Kind_Single = 3, - Kind_Group = 4, - Kind_Account = 5, - Kind_Multisig = 6, - Kind_Sentinel_High = 7, + 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, + Kind_DRep = 99, // DRep key }; - Discrimination discrimination; + static const uint8_t HashSize = 28; - Kind kind; + // First byte header (kind, netowrkId) and 2 hashes + static const uint8_t EncodedSize1 = 1 + HashSize; + static const uint8_t EncodedSize2 = 1 + 2 * HashSize; - /// key1: spending key or account key - Data key1; + NetworkId networkId{Network_Production}; - /// group key (in case of Group address, empty otherwise) - Data groupKey; + Kind kind{Kind_Base}; + + /// raw key/hash bytes + Data bytes; /// Used in case of legacy address (V2) std::optional legacyAddressV2; - /// Determines whether a string makes a valid address. + /// Determines whether a string makes a valid address (V3). static bool isValid(const std::string& addr); - /// Create a single spending key address - static AddressV3 createSingle(Discrimination discrimination_in, const TW::Data& spendingKey); - /// Create a group address - static AddressV3 createGroup(Discrimination discrimination_in, const TW::Data& spendingKey, const TW::Data& groupKey); - /// Create an account address - static AddressV3 createAccount(Discrimination discrimination_in, const TW::Data& accountKey); + /// Determines whether a string makes a valid address, V3 or earlier legacy. + static bool isValidLegacy(const std::string& addr); + + /// Create a base address, given public key hashes + static AddressV3 createBase(NetworkId networkId, const TW::Data& spendingKeyHash, const TW::Data& stakingKeyHash); + + /// 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); + + /// Create a DRep address, given a DRep key. Throws if invalid. + static AddressV3 createDRep(const std::string& addr); /// Initializes a Cardano address with a string representation. Throws if invalid. explicit AddressV3(const std::string& addr); @@ -67,32 +82,38 @@ 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; - /// Returns the internal Base32 string representation of the address. - std::string stringBase32() 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, Discrimination& discrimination, Kind& kind, TW::Data& key1, TW::Data& key2); + 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); + static NetworkId networkIdFromFirstByte(uint8_t first); + static Kind kindFromFirstByte(uint8_t first); private: - AddressV3() : discrimination(Discrim_Production), kind(Kind_Single) {} + AddressV3() = default; }; inline bool operator==(const AddressV3& lhs, const AddressV3& rhs) { - return lhs.discrimination == rhs.discrimination && lhs.kind == rhs.kind && lhs.key1 == rhs.key1 && lhs.groupKey == rhs.groupKey; + return lhs.networkId == rhs.networkId && lhs.kind == rhs.kind && lhs.bytes == rhs.bytes; } } // namespace TW::Cardano - -/// Wrapper for C interface. -struct TWCardanoAddress { - TW::Cardano::AddressV3 impl; -}; diff --git a/src/Cardano/Entry.cpp b/src/Cardano/Entry.cpp index 0cf599dcc39..688ac1ed355 100644 --- a/src/Cardano/Entry.cpp +++ b/src/Cardano/Entry.cpp @@ -1,29 +1,63 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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 "Signer.h" +#include "../proto/Cardano.pb.h" +#include "../proto/TransactionCompiler.pb.h" -#include - -using namespace TW::Cardano; -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 { - return AddressV3::isValid(address); +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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - // not implemented yet +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + return AddressV3(address).data(); +} + +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); } + +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 c30df02bfba..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeCardano}; } - 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; + 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 new file mode 100644 index 00000000000..886499d5286 --- /dev/null +++ b/src/Cardano/Signer.cpp @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "AddressV3.h" + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include +#include +#include +#include +#include + +namespace TW::Cardano { + +static const Data placeholderPrivateKey = parse_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); +static const auto PlaceholderFee = 170000; +static const auto ExtraInputAmount = 500000; + +Proto::SigningOutput Signer::sign() { + // plan if needed + if (input.has_plan()) { + _plan = TransactionPlan::fromProto(input.plan()); + } else { + // no plan supplied, plan it + _plan = doPlan(); + } + + return signWithPlan(); +} + +Common::Proto::SigningError Signer::buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan) { + tx = Transaction(); + for (const auto& i : plan.utxos) { + tx.inputs.emplace_back(i.txHash, i.outputIndex); + } + + // Spending output + if (!AddressV3::isValidLegacy(input.transfer_message().to_address())) { + return Common::Proto::Error_invalid_address; + } + 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) { + if (!AddressV3::isValidLegacy(input.transfer_message().change_address())) { + return Common::Proto::Error_invalid_address; + } + const auto changeAddress = AddressV3(input.transfer_message().change_address()); + tx.outputs.emplace_back(changeAddress.data(), plan.change, plan.changeTokens); + } + 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(), std::nullopt}); + } + 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, std::nullopt}); + } + 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(), std::nullopt}); + } + if (input.has_vote_delegation()) { + const auto stakingAddress = AddressV3(input.vote_delegation().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + Data dRepKey; + if (!input.vote_delegation().drep_id().empty()) { + const auto dRepAddress = AddressV3::createDRep(input.vote_delegation().drep_id()); + dRepKey = dRepAddress.bytes; + } + const DRepKey dRepKeyArg { + static_cast(input.vote_delegation().drep_type()), + dRepKey + }; + tx.certificates.emplace_back(Certificate{Certificate::VoteDelegation, {CertificateKey{CertificateKey::AddressKeyHash, key}}, Data(), dRepKeyArg}); + } + + return Common::Proto::OK; +} + +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 + 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, TWCurveED25519ExtendedCardano); + 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, preserving order + std::vector addresses; + for (auto& u : plan.utxos) { + if (!AddressV3::isValidLegacy(u.address)) { + return Common::Proto::Error_invalid_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()); + } + if (input.has_vote_delegation()) { + addresses.emplace_back(input.vote_delegation().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 : addressesUnique) { + const auto privKeyFind = privateKeys.find(a); + Data privateKeyData; + if (privKeyFind != privateKeys.end()) { + privateKeyData = privKeyFind->second; + } else { + // private key not found + if (sizeEstimationOnly) { + privateKeyData = placeholderPrivateKey; + } else { + return Common::Proto::Error_missing_private_key; + } + } + const auto privateKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto signature = privateKey.sign(txId); + signatures.emplace_back(publicKey.bytes, signature); + } + + return Common::Proto::OK; +} + +Cbor::Encode cborizeSignatures(const std::vector>& signatures, const bool addByronSignatures) { + std::map cborizeSigs; + // signatures as Cbor + // 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(cborizeSigs); + // clang-format on +} + +Proto::SigningOutput Signer::signWithPlan() const { + auto ret = Proto::SigningOutput(); + if (_plan.error != Common::Proto::OK) { + // plan has error + ret.set_error(_plan.error); + return ret; + } + + Data encoded; + Data txId; + const auto buildRet = encodeTransaction(encoded, txId, input, _plan); + if (buildRet != Common::Proto::OK) { + ret.set_error(buildRet); + return ret; + } + + 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; +} + +Common::Proto::SigningError Signer::encodeTransaction(Data& encoded, Data& txId, const Proto::SigningInput& input, const TransactionPlan& plan, bool sizeEstimationOnly) { + if (plan.error != Common::Proto::OK) { + return plan.error; + } + + Transaction txAux; + const auto buildRet = buildTransactionAux(txAux, input, plan); + if (buildRet != Common::Proto::OK) { + return buildRet; + } + txId = txAux.getId(); + + std::vector> signatures; + const auto sigError = assembleSignatures(signatures, input, plan, txId, sizeEstimationOnly); + if (sigError != Common::Proto::OK) { + return sigError; + } + + bool hasLegacyUtxos = false; + for (const auto& utxo : input.utxos()) { + if (AddressV2::isValid(utxo.address())) { + hasLegacyUtxos = true; + break; + } + } + + const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos); + + std::vector cbor; + cbor.emplace_back(Cbor::Encode::fromRaw(txAux.encode())); + cbor.emplace_back(sigsCbor); + // Add a spec version for the vote delegation message + if (input.has_vote_delegation()) { + cbor.emplace_back(Cbor::Encode::version(21)); + } + // Add a null value for the auxiliary data + cbor.emplace_back(Cbor::Encode::null()); + + // Cbor-encode txAux & signatures + encoded = Cbor::Encode::array(cbor).encoded(); + return Common::Proto::OK; +} + +// 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 = std::vector(); + Amount selectedAmount = 0; + + for (const auto& i : ii) { + selected.emplace_back(i); + selectedAmount += i.amount; + if (selectedAmount >= amount) { + break; + } + } + return selected; +} + +// 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 = 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.emplace_back(i); + selectedAmount += i.amount; + if (selectedAmount >= amount) { + return; + } + } + // not enough +} + +// 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&& [_, 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 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, .availableTokens{}, .outputTokens{}, .changeTokens{}}; + // Sum availableAmount + plan.availableAmount = 0; + for (auto& u : plan.utxos) { + plan.availableAmount += u.amount; + 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 = std::max(Amount(0), std::min(plan.amount, availAfterDeposit - plan.fee)); + plan.outputTokens = requestedTokens; + } else { + // max available amount + plan.amount = std::max(Amount(0), availAfterDeposit - plan.fee); + plan.outputTokens = plan.availableTokens; // use all + } + + // compute change + 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); + assert(changeAmount >= 0); + plan.changeTokens.bundle[key] = iter->second; + plan.changeTokens.bundle[key].amount = changeAmount; + } + 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 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 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; + const auto encodeError = Signer::encodeTransaction(encoded, txId, input, _simplePlan, true); + if (encodeError != Common::Proto::OK) { + return 0; + } + + return encoded.size(); +} + +// Compute fee from tx size, with some over-estimation +Amount txFeeFunction(uint64_t txSizeInBytes) { + const double fixedTerm = 155381 + 500; + const double linearTerm = 43.946 + 0.1; + + const auto fee = (Amount)(ceil(fixedTerm + (double)txSizeInBytes * linearTerm)); + return fee; +} + +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 { + auto plan = TransactionPlan(); + + bool maxAmount = input.transfer_message().use_max_amount(); + if (input.transfer_message().amount() == 0 && !maxAmount && input.transfer_message().token_amount().token_size() == 0) { + plan.error = Common::Proto::Error_zero_amount_requested; + return plan; + } + // Check input UTXOs, process, sum ADA and token amounts + auto utxos = std::vector(); + uint64_t inputSum = 0; + for (auto i = 0; i < input.utxos_size(); ++i) { + const auto& utxo = input.utxos(i); + utxos.emplace_back(TxInput::fromProto(utxo)); + inputSum += utxo.amount(); + } + if (inputSum == 0 || input.utxos_size() == 0) { + plan.error = Common::Proto::Error_missing_input_utxos; + 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 || 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 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 satisfied, but + // treat this case as MaxAmount, and send maximum available (which will be less) + 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 + plan.deposit - plan.undeposit + PlaceholderFee + requestedTokens.minAdaAmount() + ExtraInputAmount; + plan.utxos = selectInputsWithTokens(utxos, targetInputAmount, requestedTokens); + } else { + // maxAmount, select all + plan.utxos = utxos; + } + assert(!plan.utxos.empty()); + + // Sum availableAmount + plan.availableAmount = 0; + for (auto& u : plan.utxos) { + plan.availableAmount += u.amount; + for (auto && [_, curAmount] : u.tokenBundle.bundle) { + plan.availableTokens.add(curAmount); + } + } + if (plan.availableAmount == 0) { + plan.error = Common::Proto::Error_missing_input_utxos; + 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 > availableAmountAfterDeposit) { + plan.error = Common::Proto::Error_low_balance; + return plan; + } + assert(plan.amount <= availableAmountAfterDeposit); + // check that there are enough tokens in the inputs + for (auto && [_, curAmount] : requestedTokens.bundle) { + if (curAmount.amount > plan.availableTokens.getAmount(curAmount.key())) { + plan.error = Common::Proto::Error_low_balance; + return plan; + } + } + + // compute fee + if (input.transfer_message().force_fee() == 0) { + plan.fee = estimateFee(input, plan.amount, requestedTokens, plan.utxos, plan.extraOutputs); + } else { + // fee provided, use it (capped) + plan.fee = std::max(Amount(0), std::min(availableAmountAfterDeposit - plan.amount - extraAmountSum, input.transfer_message().force_fee())); + } + assert(plan.fee >= 0 && plan.fee < availableAmountAfterDeposit); + + // adjust/compute output amount + if (!maxAmount) { + // reduce amount if needed + plan.amount = std::max(Amount(0), std::min(plan.amount, availableAmountAfterDeposit - plan.fee - extraAmountSum)); + } else { + // max available amount + plan.amount = std::max(Amount(0), availableAmountAfterDeposit - plan.fee - extraAmountSum); + } + assert(plan.amount >= 0 && plan.amount <= availableAmountAfterDeposit); + + if (plan.amount + extraAmountSum + plan.fee > availableAmountAfterDeposit) { + plan.error = Common::Proto::Error_low_balance; + return plan; + } + assert(plan.amount + extraAmountSum + plan.fee <= availableAmountAfterDeposit); + + // compute output token amounts + if (!maxAmount) { + plan.outputTokens = requestedTokens; + } else { + plan.outputTokens = plan.availableTokens; // send all + } + + // compute change + 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); + if (changeAmount > 0) { // omit 0-amount tokens + plan.changeTokens.bundle[key] = iter->second; + plan.changeTokens.bundle[key].amount = changeAmount; + } + } + + assert(plan.change >= 0 && plan.change <= availableAmountAfterDeposit); + assert(!maxAmount || plan.change == 0); // change is 0 in max amount case + 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 new file mode 100644 index 00000000000..d6036e8d2d8 --- /dev/null +++ b/src/Cardano/Signer.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Transaction.h" +#include "../proto/Cardano.pb.h" +#include "Data.h" + +#include +#include +#include + +namespace TW::Cardano { + +class Signer { +public: + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept { + Signer signer = Signer(input); + return signer.sign(); + } + +public: + Proto::SigningInput input; + TransactionPlan _plan; + + explicit Signer(Proto::SigningInput input): input(std::move(input)) {} + + Proto::SigningOutput sign(); + // Sign using existing plan + Proto::SigningOutput signWithPlan() const; + // Create plan from signing input + TransactionPlan doPlan() const; + /// Returns a transaction plan (utxo selection, fee estimation) + static Proto::TransactionPlan plan(const Proto::SigningInput& input) noexcept { + const auto signer = Signer(input); + const auto plan = signer.doPlan(); + return plan.toProto(); + } + // 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 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); +}; + +} // namespace TW::Cardano diff --git a/src/Cardano/Transaction.cpp b/src/Cardano/Transaction.cpp new file mode 100644 index 00000000000..795189c453c --- /dev/null +++ b/src/Cardano/Transaction.cpp @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Transaction.h" +#include "AddressV3.h" + +#include "Cbor.h" +#include "Hash.h" +#include "HexCoding.h" +#include "Numeric.h" +#include "rust/Wrapper.h" + +namespace TW::Cardano { + +TokenAmount TokenAmount::fromProto(const Proto::TokenAmount& proto) { + Data assetName; + if (!proto.asset_name().empty()) { + assetName = data(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_hex(assetNameHex.data(), assetNameHex.size()); + const auto amountData = store(amount); + tokenAmount.set_amount(amountData.data(), amountData.size()); + + if (const auto assetNameStr = assetNameToString(); assetNameStr.has_value()) { + tokenAmount.set_asset_name(assetNameStr.value().data(), assetNameStr.value().size()); + } + return tokenAmount; +} + +std::string TokenAmount::displayAssetName() const { + if (const auto assetNameStr = assetNameToString(); assetNameStr.has_value()) { + return std::move(assetNameStr.value()); + } + return hex(assetName); +} + +std::optional TokenAmount::assetNameToString() const { + if (!Rust::tw_string_is_utf8_bytes(assetName.data(), assetName.size())) { + return std::nullopt; + } + std::string assetNameStr; + assetNameStr.assign(assetName.data(), assetName.data() + assetName.size()); + return assetNameStr; +} + +TokenBundle TokenBundle::fromProto(const Proto::TokenBundle& proto) { + 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) { + *(proto.add_token()) = t.second.toProto(); + } + return proto; +} + +void TokenBundle::add(const TokenAmount& ta) { + const auto key = ta.key(); + 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); + 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 filtered; +} + +uint64_t roundupBytesToWords(uint64_t b) { + return ((b + 7) / 8); +} + +const uint64_t TokenBundle::MinUtxoValue = 1000000; + +uint64_t TokenBundle::minAdaAmountHelper(uint64_t numPids, uint64_t numAssets, uint64_t sumAssetNameLengths) { + if (numPids == 0) { + return MinUtxoValue; + } + + static const uint64_t coinSize = 0; + static const uint64_t utxoEntrySizeWithoutVal = 27; + static const uint64_t adaOnlyUTxOSize = utxoEntrySizeWithoutVal + coinSize; // 27 + static const uint64_t pidSize = 28; + + uint64_t sizeB = 6 + roundupBytesToWords((numAssets * 12) + sumAssetNameLengths + (numPids * pidSize)); + return std::max(MinUtxoValue, (MinUtxoValue / adaOnlyUTxOSize) * (utxoEntrySizeWithoutVal + sizeB)); +} + +uint64_t TokenBundle::minAdaAmount() const { + if (size() == 0) { + // ADA only + return MinUtxoValue; + } + + std::unordered_set policyIdRegistry; + std::unordered_set assetNameRegistry; + uint64_t sumAssetNameLengths = 0; + for (const auto& t : bundle) { + policyIdRegistry.emplace(t.second.policyId); + if (!t.second.assetName.empty()) { + assetNameRegistry.emplace(t.second.assetName); + } + } + + auto numPids = uint64_t(policyIdRegistry.size()); + auto numAssets = uint64_t(assetNameRegistry.size()); + for_each(assetNameRegistry.begin(), assetNameRegistry.end(), [&sumAssetNameLengths](auto&& a){ sumAssetNameLengths += a.size(); }); + + return minAdaAmountHelper(numPids, numAssets, sumAssetNameLengths); +} + +TxInput TxInput::fromProto(const Cardano::Proto::TxInput& proto) { + auto ret = TxInput(); + ret.txHash = data(proto.out_point().tx_hash()); + ret.outputIndex = proto.out_point().output_index(); + ret.address = 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::TxInput TxInput::toProto() const { + Proto::TxInput txInput; + txInput.mutable_out_point()->set_tx_hash(txHash.data(), txHash.size()); + txInput.mutable_out_point()->set_output_index(outputIndex); + txInput.set_address(address.data(), address.size()); + txInput.set_amount(amount); + for (const auto& token : tokenBundle.bundle) { + *txInput.add_token_amount() = token.second.toProto(); + } + return txInput; +} + +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(); + ret.availableAmount = proto.available_amount(); + 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))); + } + for (auto i = 0; i < proto.output_tokens_size(); ++i) { + ret.outputTokens.add(TokenAmount::fromProto(proto.output_tokens(i))); + } + for (auto i = 0; i < proto.change_tokens_size(); ++i) { + ret.changeTokens.add(TokenAmount::fromProto(proto.change_tokens(i))); + } + for (auto i = 0; i < proto.utxos_size(); ++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; +} + +Proto::TransactionPlan TransactionPlan::toProto() const { + Proto::TransactionPlan plan; + plan.set_available_amount(availableAmount); + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(change); + plan.set_deposit(deposit); + plan.set_undeposit(undeposit); + for (const auto& token : availableTokens.bundle) { + *plan.add_available_tokens() = token.second.toProto(); + } + for (const auto& token : outputTokens.bundle) { + *plan.add_output_tokens() = token.second.toProto(); + } + for (const auto& token : changeTokens.bundle) { + *plan.add_change_tokens() = token.second.toProto(); + } + 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.emplace_back(Cbor::Encode::array({ + Cbor::Encode::bytes(i.txHash), + Cbor::Encode::uint(i.outputIndex) + })); + } + // clang-format on + return Cbor::Encode::array(ii); +} + +Cbor::Encode cborizeOutputAmounts(const Amount& amount, const TokenBundle& tokenBundle) { + if (tokenBundle.size() == 0) { + // native amount only + return Cbor::Encode::uint(amount); + } + // native and token amounts + // 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(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.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 cborizeDRepKey(const DRepKey& drepKey) { + std::vector c; + c.emplace_back(Cbor::Encode::uint(static_cast(drepKey.type))); + if (drepKey.type == DRepKey::KeyType::AddressKeyHash) { + c.emplace_back(Cbor::Encode::bytes(drepKey.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)); + } + if (cert.drepKey.has_value()) { + c.emplace_back(cborizeDRepKey(cert.drepKey.value())); + } + 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 + 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(); + 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 new file mode 100644 index 00000000000..9a4b3dc824d --- /dev/null +++ b/src/Cardano/Transaction.h @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "AddressV3.h" + +#include "Data.h" +#include "uint256.h" +#include "../proto/Cardano.pb.h" +#include "../proto/Common.pb.h" + +#include +#include +#include +#include +#include +#include + +namespace TW::Cardano { + +typedef uint64_t Amount; + +class TokenAmount { +public: + std::string policyId; + Data assetName; + uint256_t amount; + + TokenAmount() = default; + TokenAmount(std::string policyId, Data 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 + "_" + displayAssetName(); } + std::string displayAssetName() const; + /// Tries to convert the `assetName` to a UTF-8 string. Returns `std::nullopt` otherwise. + std::optional assetNameToString() const; +}; + +class TokenBundle { +public: + std::map bundle; + + TokenBundle() = default; + explicit TokenBundle(const std::vector& tokens) { + for (const auto& t : tokens) { + add(t); + } + } + + static TokenBundle fromProto(const Proto::TokenBundle& proto); + Proto::TokenBundle toProto() const; + + 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; + // The minimum ADA amount needed for a UTXO with this token bundle. See https://docs.cardano.org/native-tokens/minimum-ada-value-requirement + uint64_t minAdaAmount() const; + static uint64_t minAdaAmountHelper(uint64_t numPids, uint64_t numAssets, uint64_t sumAssetNameLengths); +}; + +class OutPoint { +public: + Data txHash; + uint64_t outputIndex{}; + + OutPoint() = default; + OutPoint(Data txHash, uint64_t outputIndex) + : txHash(std::move(txHash)), outputIndex(outputIndex) {} +}; + +class TxInput : public OutPoint { +public: + std::string address; + + /// ADA amount + Amount amount; + + /// Token amounts (optional) + TokenBundle tokenBundle; + + static TxInput fromProto(const Proto::TxInput& proto); + Proto::TxInput toProto() const; +}; + +bool operator==(const TxInput& i1, const TxInput& i2); + +class TxOutput { +public: + Data address; + + /// ADA 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(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; + 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; +}; + +class DRepKey { +public: + enum KeyType : uint8_t { + AddressKeyHash = 0, + // ScriptHash = 1, + DRepAlwaysAbstain = 2, + DRepNoConfidence = 3, + }; + KeyType type; + Data key; +}; + +/// Certificate, mainly used for staking +class Certificate { +public: + enum CertificateType : uint8_t { + SkatingKeyRegistration = 0, + StakingKeyDeregistration = 1, + Delegation = 2, + VoteDelegation = 9, + }; + CertificateType type; + CertificateKey certKey; + /// Optional PoolId, used in delegation + Data poolId; + /// Optional DRepKey, used in DRep delegation + std::optional drepKey; +}; + +/// 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; + + // Derive Transaction ID from hashed encoded data + Data getId() const; +}; + +} // namespace TW::Cardano diff --git a/src/Cbor.cpp b/src/Cbor.cpp index a22e8213469..c604048c6f3 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) { @@ -70,6 +69,18 @@ Encode Encode::tag(uint64_t value, const Encode& elem) { return e; } +Encode Encode::null() { + Encode e; + e.appendValue(Decode::MT_special, 0x16); + return e; +} + +Encode Encode::version(uint64_t value) { + Encode e; + e.appendValue(Decode::MT_special, value); + return e; +} + Encode Encode::indefArray() { Encode e; e.appendIndefinite(Decode::MT_array); @@ -90,7 +101,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; @@ -123,9 +134,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; } @@ -135,7 +146,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))); } @@ -273,7 +284,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 @@ -298,17 +309,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; @@ -317,8 +331,8 @@ 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) { - map.push_back(make_pair(elems[i], elems[i + 1])); + for (auto i = 0ul; i < elems.size(); i += 2) { + map.emplace_back(make_pair(elems[i], elems[i + 1])); } return map; } @@ -363,7 +377,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; } @@ -410,7 +424,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(); } @@ -426,7 +440,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(); } @@ -443,7 +457,11 @@ string Decode::dumpToStringInternal() const { if (typeDesc.isIndefiniteValue) { // skip break command } else { - s << "spec " << typeDesc.value; + if (typeDesc.value == 0x16) { + s << "null"; + } else { + s << "spec " << typeDesc.value; + } } break; } diff --git a/src/Cbor.h b/src/Cbor.h index 71de1adb2e0..4c3c629148b 100644 --- a/src/Cbor.h +++ b/src/Cbor.h @@ -1,15 +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. #pragma once #include "Data.h" +#include #include #include +#include +#include namespace TW::Cbor { @@ -37,9 +38,13 @@ 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) + static Encode null(); + /// encode a version + static Encode version(uint64_t value); /// Stateful building (for indefinite length) /// Start an indefinite-length array @@ -51,22 +56,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 90d6a2c000b..3aa2d14d4ac 100644 --- a/src/Coin.cpp +++ b/src/Coin.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 "Coin.h" @@ -16,41 +14,62 @@ #include "Aeternity/Entry.h" #include "Aion/Entry.h" #include "Algorand/Entry.h" -#include "Bitcoin/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 "Elrond/Entry.h" #include "EOS/Entry.h" +#include "MultiversX/Entry.h" #include "Ethereum/Entry.h" -#include "Filecoin/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 "Nano/Entry.h" #include "NEAR/Entry.h" -#include "Nebulas/Entry.h" #include "NEO/Entry.h" -#include "Nimiq/Entry.h" #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" +#include "THORChain/Entry.h" #include "Tezos/Entry.h" #include "Theta/Entry.h" -#include "TON/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 "Oasis/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" +#include "BitcoinCash/Entry.h" +#include "Pactus/Entry.h" +#include "Komodo/Entry.h" +#include "Polymesh/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -60,11 +79,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; @@ -73,6 +93,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; @@ -85,87 +106,98 @@ Ontology::Entry ontologyDP; Oasis::Entry oasisDP; Polkadot::Entry polkadotDP; Ripple::Entry rippleDP; +Ronin::Entry roninDP; Solana::Entry solanaDP; Stellar::Entry stellarDP; Tezos::Entry tezosDP; Theta::Entry thetaDP; -TON::Entry tonDP; +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; +BitcoinCash::Entry BitcoinCashDP; +Pactus::Entry PactusDP; +Komodo::Entry KomodoDP; +Polymesh::Entry PolymeshDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { // switch is preferred instead of a data structure, due to initialization issues CoinEntry* entry = nullptr; - switch (coinType) { + const auto blockchain = TW::blockchain(coinType); + switch (blockchain) { // #coin-list# - case TWCoinTypeAeternity: entry = &aeternityDP; break; - case TWCoinTypeAion: entry = &aionDP; break; - case TWCoinTypeAlgorand: entry = &algorandDP; break; - case TWCoinTypeBinance: entry = &binanceDP; break; - case TWCoinTypeBitcoin: entry = &bitcoinDP; break; - case TWCoinTypeBitcoinCash: entry = &bitcoinDP; break; - case TWCoinTypeBitcoinGold: entry = &bitcoinDP; break; - case TWCoinTypeDash: entry = &bitcoinDP; break; - case TWCoinTypeDigiByte: entry = &bitcoinDP; break; - case TWCoinTypeDogecoin: entry = &bitcoinDP; break; - case TWCoinTypeLitecoin: entry = &bitcoinDP; break; - case TWCoinTypeMonacoin: entry = &bitcoinDP; break; - case TWCoinTypeQtum: entry = &bitcoinDP; break; - case TWCoinTypeRavencoin: entry = &bitcoinDP; break; - case TWCoinTypeViacoin: entry = &bitcoinDP; break; - case TWCoinTypeZcoin: entry = &bitcoinDP; break; - case TWCoinTypeCardano: entry = &cardanoDP; break; - case TWCoinTypeCosmos: entry = &cosmosDP; break; - case TWCoinTypeKava: entry = &cosmosDP; break; - case TWCoinTypeTerra: entry = &cosmosDP; break; - case TWCoinTypeBandChain: entry = &cosmosDP; break; - case TWCoinTypeElrond: entry = &elrondDP; break; - case TWCoinTypeEOS: entry = &eosDP; break; - case TWCoinTypeCallisto: entry = ðereumDP; break; - case TWCoinTypeEthereum: entry = ðereumDP; break; - case TWCoinTypeEthereumClassic: entry = ðereumDP; break; - case TWCoinTypeGoChain: entry = ðereumDP; break; - case TWCoinTypePOANetwork: entry = ðereumDP; break; - case TWCoinTypeThunderToken: entry = ðereumDP; break; - case TWCoinTypeTomoChain: entry = ðereumDP; break; - case TWCoinTypeSmartChainLegacy: entry = ðereumDP; break; - case TWCoinTypeSmartChain: entry = ðereumDP; break; - case TWCoinTypeDecred: entry = &decredDP; break; - case TWCoinTypeFilecoin: entry = &filecoinDP; break; - case TWCoinTypeFIO: entry = &fioDP; break; - case TWCoinTypeGroestlcoin: entry = &groestlcoinDP; break; - case TWCoinTypeHarmony: entry = &harmonyDP; break; - case TWCoinTypeICON: entry = &iconDP; break; - case TWCoinTypeIoTeX: entry = &iotexDP; break; - case TWCoinTypeKusama: entry = &kusamaDP; break; - case TWCoinTypeNano: entry = &nanoDP; break; - case TWCoinTypeNEAR: entry = &nearDP; break; - case TWCoinTypeNebulas: entry = &nebulasDP; break; - case TWCoinTypeNEO: entry = &neoDP; break; - case TWCoinTypeNimiq: entry = &nimiqDP; break; - case TWCoinTypeNULS: entry = &nulsDP; break; - case TWCoinTypeOasis: entry = &oasisDP; break; - case TWCoinTypeOntology: entry = &ontologyDP; break; - case TWCoinTypePolkadot: entry = &polkadotDP; break; - case TWCoinTypeXRP: entry = &rippleDP; break; - case TWCoinTypeSolana: entry = &solanaDP; break; - case TWCoinTypeStellar: entry = &stellarDP; break; - case TWCoinTypeKin: entry = &stellarDP; break; - case TWCoinTypeTezos: entry = &tezosDP; break; - case TWCoinTypeTheta: entry = &thetaDP; break; - case TWCoinTypeTON: entry = &tonDP; break; - case TWCoinTypeTron: entry = &tronDP; break; - case TWCoinTypeVeChain: entry = &vechainDP; break; - case TWCoinTypeWanchain: entry = ðereumDP; break; - case TWCoinTypeWaves: entry = &wavesDP; break; - case TWCoinTypeZcash: entry = &zcashDP; break; - case TWCoinTypeZelcash: entry = &zcashDP; break; - case TWCoinTypeZilliqa: entry = &zilliqaDP; break; - case TWCoinTypePolygon: entry = ðereumDP; break; + 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; + case TWBlockchainIcon: entry = &iconDP; break; + case TWBlockchainBinance: entry = &binanceDP; break; + case TWBlockchainRipple: entry = &rippleDP; break; + case TWBlockchainTezos: entry = &tezosDP; break; + case TWBlockchainNimiq: entry = &nimiqDP; break; + case TWBlockchainStellar: entry = &stellarDP; break; + case TWBlockchainAion: entry = &aionDP; break; + case TWBlockchainCosmos: entry = &cosmosDP; break; + case TWBlockchainTheta: entry = &thetaDP; break; + case TWBlockchainOntology: entry = &ontologyDP; break; + case TWBlockchainZilliqa: entry = &zilliqaDP; break; + case TWBlockchainIoTeX: entry = &iotexDP; break; + case TWBlockchainEOS: entry = &eosDP; break; + case TWBlockchainNano: entry = &nanoDP; break; + case TWBlockchainNULS: entry = &nulsDP; break; + case TWBlockchainWaves: entry = &wavesDP; break; + case TWBlockchainAeternity: entry = &aeternityDP; break; + case TWBlockchainNebulas: entry = &nebulasDP; break; + case TWBlockchainFIO: entry = &fioDP; break; + case TWBlockchainSolana: entry = &solanaDP; break; + case TWBlockchainHarmony: entry = &harmonyDP; break; + case TWBlockchainNEAR: entry = &nearDP; break; + case TWBlockchainAlgorand: entry = &algorandDP; break; + case TWBlockchainPolkadot: entry = &polkadotDP; break; + case TWBlockchainCardano: entry = &cardanoDP; break; + case TWBlockchainNEO: entry = &neoDP; break; + case TWBlockchainFilecoin: entry = &filecoinDP; 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; + case TWBlockchainBitcoinCash: entry = &BitcoinCashDP; break; + case TWBlockchainPactus: entry = &PactusDP; break; + case TWBlockchainKomodo: entry = &KomodoDP; break; + case TWBlockchainPolymesh: entry = &PolymeshDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; @@ -174,68 +206,148 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { return entry; } +const Derivation CoinInfo::derivationByName(TWDerivation nameIn) const { + if (nameIn == TWDerivationDefault && derivation.size() > 0) { + return derivation[0]; + } + 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); - auto hrp = stringForHRP(TW::hrp(coin)); // dispatch - auto dispatcher = coinDispatcher(coin); + 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) { + return TW::deriveAddress(coin, privateKey, TWDerivationDefault); +} + +std::string TW::deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDerivation derivation) { auto keyType = TW::publicKeyType(coin); - return TW::deriveAddress(coin, privateKey.getPublicKey(keyType)); + return TW::deriveAddress(coin, privateKey.getPublicKey(keyType), derivation); } -std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) { - auto p2pkh = TW::p2pkhPrefix(coin); - auto hrp = stringForHRP(TW::hrp(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, publicKey, derivation, addressPrefix); +} - // dispatch - auto dispatcher = coinDispatcher(coin); +PrivateKey TW::decodePrivateKey(TWCoinType coin, const std::string& privateKey) { + auto const* dispatcher = coinDispatcher(coin); + assert(dispatcher != nullptr); + return dispatcher->decodePrivateKey(coin, privateKey); +} + +Data TW::addressToData(TWCoinType coin, const std::string& address) { + const auto* dispatcher = coinDispatcher(coin); assert(dispatcher != nullptr); - return dispatcher->deriveAddress(coin, publicKey, p2pkh, hrp); + return dispatcher->addressToData(coin, address); } void TW::anyCoinSign(TWCoinType coinType, const Data& dataIn, Data& dataOut) { - auto dispatcher = coinDispatcher(coinType); + auto* dispatcher = coinDispatcher(coinType); assert(dispatcher != nullptr); dispatcher->sign(coinType, dataIn, dataOut); } std::string TW::anySignJSON(TWCoinType coinType, const std::string& json, const Data& key) { - auto dispatcher = coinDispatcher(coinType); + auto* dispatcher = coinDispatcher(coinType); assert(dispatcher != nullptr); return dispatcher->signJSON(coinType, json, key); } bool TW::supportsJSONSigning(TWCoinType coinType) { - auto dispatcher = coinDispatcher(coinType); + auto* dispatcher = coinDispatcher(coinType); assert(dispatcher != nullptr); return dispatcher->supportsJSONSigning(); } void TW::anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut) { - auto dispatcher = coinDispatcher(coinType); + auto* dispatcher = coinDispatcher(coinType); assert(dispatcher != nullptr); dispatcher->plan(coinType, dataIn, dataOut); } +Data TW::anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData) { + auto* dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + return dispatcher->preImageHashes(coinType, txInputData); +} + +void TW::anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& txOutputOut) { + auto* dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + dispatcher->compile(coinType, txInputData, signatures, publicKeys, txOutputOut); +} + // Coin info accessors extern const CoinInfo getCoinInfo(TWCoinType coin); // in generated CoinInfoData.cpp file @@ -253,15 +365,31 @@ TWCurve TW::curve(TWCoinType coin) { } TWHDVersion TW::xpubVersion(TWCoinType coin) { - return getCoinInfo(coin).xpubVersion; + return getCoinInfo(coin).defaultDerivation().xpubVersion; } TWHDVersion TW::xprvVersion(TWCoinType coin) { - return getCoinInfo(coin).xprvVersion; + return getCoinInfo(coin).defaultDerivation().xprvVersion; +} + +TWHDVersion TW::xpubVersionDerivation(TWCoinType coin, TWDerivation derivation) { + return getCoinInfo(coin).derivationByName(derivation).xpubVersion; +} + +TWHDVersion TW::xprvVersionDerivation(TWCoinType coin, TWDerivation derivation) { + return getCoinInfo(coin).derivationByName(derivation).xprvVersion; } DerivationPath TW::derivationPath(TWCoinType coin) { - return DerivationPath(getCoinInfo(coin).derivationPath); + return DerivationPath(getCoinInfo(coin).defaultDerivation().path); +} + +DerivationPath TW::derivationPath(TWCoinType coin, TWDerivation derivation) { + return DerivationPath(getCoinInfo(coin).derivationByName(derivation).path); +} + +const char* TW::derivationName(TWCoinType coin, TWDerivation derivation) { + return getCoinInfo(coin).derivationByName(derivation).nameString; } enum TWPublicKeyType TW::publicKeyType(TWCoinType coin) { @@ -284,6 +412,10 @@ enum TWHRP TW::hrp(TWCoinType coin) { return getCoinInfo(coin).hrp; } +const char* TW::chainId(TWCoinType coin) { + return getCoinInfo(coin).chainId; +} + Hash::Hasher TW::publicKeyHasher(TWCoinType coin) { return getCoinInfo(coin).publicKeyHasher; } @@ -292,11 +424,19 @@ Hash::Hasher TW::base58Hasher(TWCoinType coin) { return getCoinInfo(coin).base58Hasher; } +Hash::Hasher TW::addressHasher(TWCoinType coin) { + return getCoinInfo(coin).addressHasher; +} + uint32_t TW::slip44Id(TWCoinType coin) { return getCoinInfo(coin).slip44; } -TWString *_Nullable TWCoinTypeConfigurationGetSymbol(enum TWCoinType coin) { +std::uint32_t TW::ss58Prefix(TWCoinType coin) { + return getCoinInfo(coin).ss58Prefix; +} + +TWString* _Nullable TWCoinTypeConfigurationGetSymbol(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).symbol); } @@ -304,29 +444,22 @@ int TWCoinTypeConfigurationGetDecimals(enum TWCoinType coin) { return getCoinInfo(coin).decimals; } -TWString *_Nullable TWCoinTypeConfigurationGetTransactionURL(enum TWCoinType coin, TWString *_Nonnull transactionID) { +TWString* _Nullable TWCoinTypeConfigurationGetTransactionURL(enum TWCoinType coin, TWString* _Nonnull transactionID) { std::string txId = TWStringUTF8Bytes(transactionID); std::string url = getCoinInfo(coin).explorerTransactionUrl + txId; return TWStringCreateWithUTF8Bytes(url.c_str()); } -TWString *_Nullable TWCoinTypeConfigurationGetAccountURL(enum TWCoinType coin, TWString *_Nonnull accountID) { +TWString* _Nullable TWCoinTypeConfigurationGetAccountURL(enum TWCoinType coin, TWString* _Nonnull accountID) { std::string accId = TWStringUTF8Bytes(accountID); std::string url = getCoinInfo(coin).explorerAccountUrl + accId; return TWStringCreateWithUTF8Bytes(url.c_str()); } -TWString *_Nonnull TWCoinTypeConfigurationGetID(enum TWCoinType coin) { +TWString* _Nonnull TWCoinTypeConfigurationGetID(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).id); } -TWString *_Nonnull TWCoinTypeConfigurationGetName(enum TWCoinType coin) { +TWString* _Nonnull TWCoinTypeConfigurationGetName(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).name); } - -const std::vector TW::getSimilarCoinTypes(TWCoinType coinType) { - const auto dispatcher = coinDispatcher(coinType); - assert(dispatcher != nullptr); - return dispatcher->coinTypes(); -} - diff --git a/src/Coin.h b/src/Coin.h index d1ea0a1213c..5bbc029ecf9 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 @@ -11,12 +9,15 @@ #include "DerivationPath.h" #include "PrivateKey.h" #include "PublicKey.h" +#include "uint256.h" +#include "CoinEntry.h" #include #include #include #include #include +#include #include #include @@ -29,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); @@ -41,30 +46,54 @@ TWPurpose purpose(TWCoinType coin); /// Returns the curve that should be used for a coin type. TWCurve curve(TWCoinType coin); -/// Returns the xpub HD version that should be used for a coin type. +/// Returns the default xpub HD version that should be used for a coin type. TWHDVersion xpubVersion(TWCoinType coin); -/// Returns the xprv HD version that should be used for a coin type. +/// Returns the default xprv HD version that should be used for a coin type. TWHDVersion xprvVersion(TWCoinType coin); +/// Returns the xpub HD version for a TWDerivation. +TWHDVersion xpubVersionDerivation(TWCoinType coin, TWDerivation derivation); + +/// Returns the xprv HD version for a TWDerivation. +TWHDVersion xprvVersionDerivation(TWCoinType coin, TWDerivation derivation); + /// Returns the default derivation path for a particular coin. DerivationPath derivationPath(TWCoinType coin); +/// Returns an alternative derivation path for a particular coin, TWDerivationDefault for default. +DerivationPath derivationPath(TWCoinType coin, TWDerivation derivation); + +/// Returns the string name of a derivation for a particular coin. +const char* derivationName(TWCoinType coin, TWDerivation derivation); + /// Returns the public key type for a particular coin. enum TWPublicKeyType publicKeyType(TWCoinType coin); /// Derives the address for a particular coin from the private key. std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey); -/// 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 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, with given derivation and addressPrefix. +std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation = TWDerivationDefault, const PrefixVariant& addressPrefix = std::monostate()); + +/// Decodes a private key for a particular coin. +PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey); -/// Hasher for deriving the public key hash. +/// Returns the binary representation of a string address +Data addressToData(TWCoinType coin, const std::string& address); + +/// Hasher for deriving the extended public key Hash::Hasher publicKeyHasher(TWCoinType coin); -/// Hasher to use for base 58 checksums. +/// Hasher to use for base 58 checksums in keys (extended private, public) Hash::Hasher base58Hasher(TWCoinType coin); +/// Hasher used inside address generation (hash of public key) +Hash::Hasher addressHasher(TWCoinType coin); + /// Returns static prefix for a coin type. byte staticPrefix(TWCoinType coin); @@ -77,6 +106,12 @@ 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); + // Note: use output parameter to avoid unneeded copies void anyCoinSign(TWCoinType coinType, const Data& dataIn, Data& dataOut); @@ -88,8 +123,18 @@ bool supportsJSONSigning(TWCoinType coinType); void anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut); -// Return coins handled by the same dispatcher as the given coin (mostly for testing) -const std::vector getSimilarCoinTypes(TWCoinType coinType); +Data anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData); + +void anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& txOutputOut); + +// Describes a derivation: path + optional format + optional name +struct Derivation { + TWDerivation name = TWDerivationDefault; + const char* path = ""; + const char* nameString = ""; + TWHDVersion xpubVersion = TWHDVersionNone; + TWHDVersion xprvVersion = TWHDVersionNone; +}; // Contains only simple types. struct CoinInfo { @@ -98,21 +143,28 @@ struct CoinInfo { TWBlockchain blockchain; TWPurpose purpose; TWCurve curve; - TWHDVersion xpubVersion; - TWHDVersion xprvVersion; - const char* derivationPath; + std::vector derivation; TWPublicKeyType publicKeyType; byte staticPrefix; byte p2pkhPrefix; byte p2shPrefix; TWHRP hrp; + const char* chainId; Hash::Hasher publicKeyHasher; Hash::Hasher base58Hasher; + Hash::Hasher addressHasher; const char* symbol; int decimals; const char* explorerTransactionUrl; const char* explorerAccountUrl; uint32_t slip44; + std::uint32_t ss58Prefix; + + // returns default derivation + const Derivation defaultDerivation() const { + return (derivation.size() > 0) ? derivation[0] : Derivation(); + } + const Derivation derivationByName(TWDerivation name) const; }; } // namespace TW diff --git a/src/CoinEntry.cpp b/src/CoinEntry.cpp new file mode 100644 index 00000000000..31614fd41bf --- /dev/null +++ b/src/CoinEntry.cpp @@ -0,0 +1,38 @@ +// 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); +} + +PrivateKey CoinEntry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const { + auto data = parse_hex(privateKey); + return PrivateKey(data, TW::curve(coin)); +} + +} // namespace TW diff --git a/src/CoinEntry.h b/src/CoinEntry.h index c42223d9d2a..c7f21184060 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -1,40 +1,75 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, 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" #include "PrivateKey.h" +#include "proto/Common.pb.h" +#include "uint256.h" #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: - // Report the coin types this implementation is responsible of - virtual const std::vector coinTypes() const = 0; - 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; } - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const = 0; + 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([[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([[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([[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 {} + // Optional method for decoding a private key. Could throw an exception if the encoded private key is invalid. + virtual PrivateKey decodePrivateKey([[maybe_unused]] TWCoinType coin, const std::string& privateKey) const; }; // In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement: @@ -57,4 +92,65 @@ void planTemplate(const Data& dataIn, Data& dataOut) { dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } +// This template will be used for preImageHashes and compile in each coin's Entry.cpp. +// It is a helper function to simplify exception handle. +template +Data txCompilerTemplate(const Data& dataIn, 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()); + } + + try { + // each coin function handler + fnHandler(input, output); + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_internal); + output.set_error_message(e.what()); + } + 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 49bf640d5cb..ed4c7bf61eb 100644 --- a/src/Cosmos/Address.h +++ b/src/Cosmos/Address.h @@ -1,29 +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. #pragma once #include "../Bech32Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" +#include "../Coin.h" +#include +#include #include namespace TW::Cosmos { -/// A Bech32 Cosmos address. Hrp has to be specified (e.g. "cosmos", "terra"...), hash is HASHER_SHA2_RIPEMD. +/// A Bech32 Cosmos address. Hrp has to be specified (e.g. "cosmos", "terra"...), hash is coin-specific (from config, usually sha256ripemd). class Address: public Bech32Address { public: Address() : Bech32Address("") {} - /// Initializes an address with a key hash. + /// Initializes an address with a key hash, with given prefix. Address(const std::string& hrp, const Data& keyHash) : Bech32Address(hrp, keyHash) {} - /// Initializes an address with a public key. - Address(const std::string& hrp, const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2_RIPEMD, publicKey) {} + /// Initializes an address with a public key, with prefix of the given coin. + Address(TWCoinType coin, const PublicKey& publicKey) : Bech32Address(stringForHRP(TW::hrp(coin)), TW::addressHasher(coin), publicKey) {} + + /// Initializes an address with a public key, with given prefix. + Address(const std::string& hrp, const PublicKey& publicKey, TWCoinType coin = TWCoinTypeCosmos) : Bech32Address(hrp, TW::addressHasher(coin), publicKey) {} + + /// Determines whether a string makes a valid Bech32 address, and the HRP matches to the coin. + static bool isValid(TWCoinType coin, const std::string& addr) { + const auto* const hrp = stringForHRP(TW::hrp(coin)); + 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) { diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 27e4ef3c8ce..69a4b4c2707 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -1,31 +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; -using namespace std; -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. +#include +#include +#include -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char* hrp) const { - return Address::isValid(address, hrp); -} +using namespace TW; +using namespace std; -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char* hrp) const { - return Address(hrp, publicKey).string(); -} +namespace TW::Cosmos { -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 signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return output.json(); } + ); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} +} // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index 7f90d52ec5f..77d8efec01e 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -1,32 +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 const std::vector coinTypes() const { - return { - TWCoinTypeCosmos, - TWCoinTypeKava, - TWCoinTypeTerra, - TWCoinTypeBandChain, - }; - } - 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; + ~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/Serialization.cpp b/src/Cosmos/Serialization.cpp deleted file mode 100644 index 64104df731c..00000000000 --- a/src/Cosmos/Serialization.cpp +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "../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; - -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"; - -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", std::to_string(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()} - }} - }; -} - -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())); - } - } - 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 Cosmos::signaturePreimage(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 Cosmos::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()); -} diff --git a/src/Cosmos/Serialization.h b/src/Cosmos/Serialization.h deleted file mode 100644 index bd049eb903b..00000000000 --- a/src/Cosmos/Serialization.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 "../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_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 signaturePreimage(const Proto::SigningInput& input); -json transactionJSON(const Proto::SigningInput& input, const Data& signature); - -} // namespace diff --git a/src/Cosmos/Signer.cpp b/src/Cosmos/Signer.cpp deleted file mode 100644 index 7bdbe7b4494..00000000000 --- a/src/Cosmos/Signer.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 "Signer.h" -#include "PrivateKey.h" -#include "Serialization.h" - -#include "Data.h" -#include "Hash.h" - -#include - -using namespace TW; -using namespace TW::Cosmos; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(input.private_key()); - auto preimage = signaturePreimage(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()); - 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 output.json(); -} diff --git a/src/Cosmos/Signer.h b/src/Cosmos/Signer.h deleted file mode 100644 index 4af4193d17f..00000000000 --- a/src/Cosmos/Signer.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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" - -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) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); -}; - -} // namespace TW::Cosmos diff --git a/src/Crc.cpp b/src/Crc.cpp index 7a388a7c03c..7024532f4ee 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,29 @@ 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(); +// 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; } -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(); +Data Crc::crc16_xmodem(const Data& data) { + uint16_t crc16 = 0x0; + + for (size_t i = 0; i < data.size(); i++) { + uint8_t byte = data[i]; + uint8_t lookupIndex = (crc16 >> 8) ^ byte; + crc16 = static_cast((crc16 << 8) ^ crc16_xmodem_table[lookupIndex]); + crc16 &= 0xffff; + } + + Data checksum(2); + checksum[0] = crc16 & 0xff; + checksum[1] = (crc16 >> 8) & 0xff; + return checksum; } + diff --git a/src/Crc.h b/src/Crc.h index 88bb3b28854..173dc6ec2f0 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,88 @@ uint16_t crc16(uint8_t* bytes, uint32_t length); uint32_t crc32(const TW::Data& data); -uint32_t crc32C(const TW::Data& data); +/// CRC16-XModem implementation compatible with the Stellar version +// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/strkey.js#L353 +// Computes the CRC16-XModem checksum of `payload` in little-endian order +Data crc16_xmodem(const 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}; + +// CRC16-XModem lookup table +static const uint16_t crc16_xmodem_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, + 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, + 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, + 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, + 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, + 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, + 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, + 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, + 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, + 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, + 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, + 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, + 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, + 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, + 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, + 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, + 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, + 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, + 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, + 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, + 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, + 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, + 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; } // 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 166f80646e3..67b2427b6b8 100644 --- a/src/Data.cpp +++ b/src/Data.cpp @@ -1,17 +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 "Data.h" namespace TW { -Data subData(const Data& data, size_t index, size_t length) { - size_t subLength = length; - if (index + subLength > data.size()) { subLength = data.size() - index; } // guard against over-length - return TW::data(data.data() + index, subLength); +Data subData(const Data& data, size_t startIndex, size_t length) { + if (startIndex >= data.size()) { + return Data(); + } + const size_t subLength = std::min(length, data.size() - startIndex); // guard against over-length + return TW::data(data.data() + startIndex, subLength); +} + +Data subData(const Data& data, size_t startIndex) { + if (startIndex >= data.size()) { + return Data(); + } + const size_t subLength = data.size() - startIndex; + return TW::data(data.data() + startIndex, subLength); } } // namespace TW diff --git a/src/Data.h b/src/Data.h index 4e80e18865a..7fe4a5112db 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,28 +14,55 @@ 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 std::vector(data.begin(), data.end()); + return Data(data.begin(), data.end()); } inline Data data(const byte* data, size_t size) { - return std::vector(data, data + size); + return Data(data, data + size); } 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); } -/// Return a part (subdata) of the requested size of the input data. -Data subData(const Data& data, size_t index, size_t length); +/// Return a part (subdata) from the requested start position and size of the input data. +Data subData(const Data& data, size_t startIndex, size_t length); + +/// Return the tail part (subdata) from the requested start position of the input data. +Data subData(const Data& data, size_t startIndex); /// Determines if a byte array has a specific prefix. template @@ -45,4 +70,14 @@ inline bool has_prefix(const Data& data, T& prefix) { return std::equal(prefix.begin(), prefix.end(), data.begin(), data.begin() + std::min(data.size(), prefix.size())); } +// Custom hash function for `Data` type. +struct DataHash { + std::size_t operator()(const Data& data) const { + // Create a string_view from the vector's data. + std::string_view ss(reinterpret_cast(data.data()), data.size()); + // Use the hash function for std::string_view + return std::hash{}(ss); + } +}; + } // namespace TW 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 1a7496c7a13..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::blake256d); + 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::blake256d); + 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::blake256d); + 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 e6ecaf91aae..4e6c17f60a6 100644 --- a/src/Decred/Entry.cpp +++ b/src/Decred/Entry.cpp @@ -1,29 +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 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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) 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([[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 c840d0d9648..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 const std::vector coinTypes() const { return {TWCoinTypeDecred}; } - 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::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 e53749076c0..8ebd58a643b 100644 --- a/src/Decred/OutPoint.h +++ b/src/Decred/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 "Data.h" +#include "../Bitcoin/OutPoint.h" #include "../proto/Bitcoin.pb.h" #include @@ -43,7 +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 = other.tree; } /// Encodes the out-point into the provided buffer. diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index a3f99a61434..12b535698bb 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -1,36 +1,42 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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 "TransactionInput.h" #include "TransactionOutput.h" #include "../Bitcoin/SigHashType.h" -#include "../Bitcoin/TransactionSigner.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)); + if (input.has_signing_v2()) { + return Bitcoin::Signer::planAsV2(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 { + if (input.has_signing_v2()) { + const auto output = Bitcoin::Signer::signAsV2(input); + Proto::SigningOutput decredOutput; + *decredOutput.mutable_signing_result_v2() = output.signing_result_v2(); + return decredOutput; + } + + 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,39 +52,67 @@ Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noe return output; } +Bitcoin::Proto::PreSigningOutput Signer::preImageHashes(const Bitcoin::Proto::SigningInput& input) noexcept { + if (input.has_signing_v2()) { + return Bitcoin::Signer::preImageHashesAsV2(input); + } + + 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.clear(); - std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), - std::back_inserter(signedInputs)); + 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 script = Bitcoin::Script(utxo.script().begin(), utxo.script().end()); - auto result = sign(script, i); + auto result = sign(utxo.script, i); if (!result) { return Result::failure(result.error()); } signedInputs[i].script = result.payload(); } - Transaction tx(transaction); - tx.inputs = move(signedInputs); - tx.outputs = transaction.outputs; + Transaction tx(_transaction); + tx.inputs = std::move(signedInputs); + 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; @@ -89,27 +123,27 @@ 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); } - return Result::success(Bitcoin::Script(Bitcoin::TransactionSigner::pushAll(results))); + return Result::success(Bitcoin::Script(Bitcoin::SignatureBuilder::pushAll(results))); } 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; @@ -118,11 +152,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); @@ -130,18 +164,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, TWCurveSECP256k1).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()) { @@ -152,16 +200,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); @@ -177,10 +225,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())); - auto pk = PrivateKey(key); - auto signature = pk.signAsDER(Data(begin(sighash), end(sighash)), TWCurveSECP256k1); + + 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, TWCurveSECP256k1); + auto signature = pk.signAsDER(Data(begin(sighash), end(sighash))); if (script.empty()) { return {}; } @@ -191,7 +275,7 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri Data Signer::keyForPublicKeyHash(const Data& hash) const { for (auto& key : input.private_key()) { - auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); + auto publicKey = PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1); auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(publicKey.bytes)); if (std::equal(std::begin(keyHash), std::end(keyHash), std::begin(hash), std::end(hash))) { return Data(key.begin(), key.end()); @@ -201,7 +285,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 @@ -209,3 +293,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 822fd0086b3..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,35 +36,50 @@ 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. - std::vector signedInputs; + Bitcoin::TransactionInputs signedInputs; public: /// Initializes a transaction 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 9be21fbe88f..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. @@ -40,7 +34,8 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz auto inputsToSign = inputs; auto signIndex = index; if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0) { - inputsToSign = {inputs[index]}; + inputsToSign.clear(); + inputsToSign.push_back(inputs[index]); signIndex = 0; } @@ -51,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 @@ -85,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); @@ -99,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; @@ -132,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 { @@ -197,7 +192,7 @@ Proto::Transaction Transaction::proto() const { protoTx.set_locktime(lockTime); for (const auto& input : inputs) { - auto protoInput = protoTx.add_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); @@ -206,7 +201,7 @@ Proto::Transaction Transaction::proto() const { } for (const auto& output : outputs) { - auto protoOutput = protoTx.add_outputs(); + auto* protoOutput = protoTx.add_outputs(); protoOutput->set_value(output.value); protoOutput->set_script(output.script.bytes.data(), output.script.bytes.size()); } @@ -238,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 57aa1650ca0..eb2cb9983e2 100644 --- a/src/Decred/Transaction.h +++ b/src/Decred/Transaction.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 #include "TransactionInput.h" #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" @@ -28,10 +27,10 @@ struct Transaction { uint16_t version = 1; /// A list of 1 or more transaction inputs or sources for coins - std::vector inputs; + Bitcoin::TransactionInputs inputs; /// A list of 1 or more transaction outputs or destinations for coins - std::vector outputs; + Bitcoin::TransactionOutputs outputs; /// The time when a transaction can be spent (usually zero, in which case it /// has no effect). diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index 9c612ac8288..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.out_point(); - input.sequence = utxo.out_point().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 new file mode 100644 index 00000000000..d3d3665f636 --- /dev/null +++ b/src/Defer.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#ifndef defer // https://stackoverflow.com/a/42060129/411431 + +struct defer_dummy {}; +template struct deferrer { F f; ~deferrer() { f(); } }; +template deferrer operator*(defer_dummy, F f) { return {f}; } + +#define DEFER_(LINE) zz_defer##LINE +#define DEFER(LINE) DEFER_(LINE) +#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]() + +#endif // defer diff --git a/src/DerivationPath.cpp b/src/DerivationPath.cpp index e0f97ed8c71..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" @@ -12,8 +10,8 @@ using namespace TW; DerivationPath::DerivationPath(const std::string& string) { - auto it = string.data(); - const auto end = string.data() + string.size(); + const auto* it = string.data(); + const auto* end = string.data() + string.size(); if (it != end && *it == 'm') { ++it; 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 7490e3aa37b..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,14 +36,14 @@ 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.push_back(PermissionLevel(Name(from), Name("active"))); + authorization.emplace_back(PermissionLevel(Name(from), Name("active"))); setData(from, to, asset, memo); } @@ -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 f6e9f4fcd56..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeEOS}; } - 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 a38537c4737..1c1286e17c4 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -1,39 +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 "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); + // values for Legacy and ModernK1 + TWCurve curve = TWCurveSECP256k1; // get key type EOS::Type type = Type::Legacy; switch (input.private_key_type()) { @@ -46,6 +31,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { break; case Proto::KeyType::MODERNR1: + curve = TWCurveNIST256p1; type = Type::ModernR1; break; default: @@ -53,9 +39,8 @@ 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); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), curve); + signer.sign(key, type, tx); // Pack the transaction and add the json encoding to Signing outputput PackedTransaction ptx{tx, CompressionType::None}; @@ -73,38 +58,96 @@ void Signer::sign(const PrivateKey& privateKey, Type type, Transaction& transact throw std::invalid_argument("Invalid transaction!"); } - // values for Legacy and ModernK1 - TWCurve curve = TWCurveSECP256k1; auto canonicalChecker = isCanonical; // Values for ModernR1 if (type == Type::ModernR1) { - curve = TWCurveNIST256p1; canonicalChecker = nullptr; } - const Data result = privateKey.sign(hash(transaction), curve, canonicalChecker); + const Data result = privateKey.sign(hash(transaction), canonicalChecker); - transaction.signatures.push_back(Signature(result, type)); + transaction.signatures.emplace_back(Signature(result, type)); } 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 e7ae58700aa..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" @@ -13,12 +10,12 @@ #include #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!"); } @@ -54,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 { @@ -85,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); @@ -99,14 +96,19 @@ void Transaction::serialize(Data& os) const noexcept{ encodeCollection(transactionExtensions, os); } -json Transaction::serialize() const { +std::string Transaction::formatDate(int32_t date) { + // format is "2022-06-02T08:53:20", always 19 characters long + constexpr size_t DateSize = 19; + constexpr size_t BufferSize = DateSize + 1; + char formattedDate[BufferSize]; + auto time = static_cast(date); + const size_t len = strftime(formattedDate, BufferSize, "%FT%T", std::gmtime(&time)); + assert(len == DateSize); + return {formattedDate, len}; +} - // get a formatted date - char formattedDate[20]; - time_t time = expiration; - if (strftime(formattedDate, 19, "%FT%T", std::gmtime(&time)) != 19) { - std::runtime_error("Error creating a formatted string!"); - } +json Transaction::serialize() const { + const auto expirationDateFormatted = formatDate(expiration); // create a json array of signatures json sigs = json::array(); @@ -118,7 +120,7 @@ json Transaction::serialize() const { json obj; obj["ref_block_num"] = refBlockNumber; obj["ref_block_prefix"] = refBlockPrefix; - obj["expiration"] = std::string(formattedDate, 19); + obj["expiration"] = expirationDateFormatted; obj["max_net_usage_words"] = maxNetUsageWords; obj["max_cpu_usage_ms"] = maxCPUUsageInMS; obj["delay_sec"] = delaySeconds; @@ -130,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 5d6660c462a..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,6 +65,8 @@ 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); }; } // namespace TW::EOS \ No newline at end of file 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 1f96edc6700..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 "coins.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/Entry.cpp b/src/Elrond/Entry.cpp deleted file mode 100644 index 76b806ff128..00000000000 --- a/src/Elrond/Entry.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 "Entry.h" - -#include "Address.h" -#include "Signer.h" - -using namespace TW::Elrond; -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(); -} - -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 d0cc67d631e..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 const std::vector coinTypes() const { return {TWCoinTypeElrond}; } - 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; -}; - -} // namespace TW::Elrond diff --git a/src/Elrond/Serialization.cpp b/src/Elrond/Serialization.cpp deleted file mode 100644 index eaccaabf417..00000000000 --- a/src/Elrond/Serialization.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 "Serialization.h" - -#include "../Elrond/Address.h" -#include "../proto/Elrond.pb.h" -#include "Base64.h" -#include "PrivateKey.h" - -using namespace TW; - -std::map fields_order { - {"nonce", 1}, - {"value", 2}, - {"receiver", 3}, - {"sender", 4}, - {"gasPrice", 5}, - {"gasLimit", 6}, - {"data", 7}, - {"chainID", 8}, - {"version", 9}, - {"signature", 10} -}; - -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::Proto::TransactionMessage& message) { - sorted_json payload { - {"nonce", json(message.nonce())}, - {"value", json(message.value())}, - {"receiver", json(message.receiver())}, - {"sender", json(message.sender())}, - {"gasPrice", json(message.gas_price())}, - {"gasLimit", json(message.gas_limit())}, - }; - - if (!message.data().empty()) { - payload["data"] = json(TW::Base64::encode(TW::data(message.data()))); - } - - payload["chainID"] = json(message.chain_id()); - payload["version"] = json(message.version()); - - return payload; -} - -string Elrond::serializeTransaction(const Proto::TransactionMessage& message) { - sorted_json payload = preparePayload(message); - return payload.dump(); -} - -string Elrond::serializeSignedTransaction(const Proto::TransactionMessage& message, string signature) { - sorted_json payload = preparePayload(message); - payload["signature"] = json(signature); - return payload.dump(); -} diff --git a/src/Elrond/Serialization.h b/src/Elrond/Serialization.h deleted file mode 100644 index e56d8a5bf87..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 "../proto/Elrond.pb.h" -#include "Data.h" -#include - -using string = std::string; -using json = nlohmann::json; - -namespace TW::Elrond { - -string serializeTransaction(const Proto::TransactionMessage& message); -string serializeSignedTransaction(const Proto::TransactionMessage& message, string encodedSignature); - -} // namespace diff --git a/src/Elrond/Signer.cpp b/src/Elrond/Signer.cpp deleted file mode 100644 index 20519c8b80a..00000000000 --- a/src/Elrond/Signer.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 "Signer.h" -#include "Address.h" -#include "Serialization.h" -#include "../PublicKey.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Elrond; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { - auto privateKey = PrivateKey(input.private_key()); - auto signableAsString = serializeTransaction(input.transaction()); - auto signableAsData = TW::data(signableAsString); - auto signature = privateKey.sign(signableAsData, TWCurveED25519); - auto encodedSignature = hex(signature); - auto encoded = serializeSignedTransaction(input.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/Encrypt.cpp b/src/Encrypt.cpp index 2b89542f94b..a3a3c8a78a0 100644 --- a/src/Encrypt.cpp +++ b/src/Encrypt.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 "Encrypt.h" #include "Data.h" #include #include +#include +#include namespace TW::Encrypt { diff --git a/src/Encrypt.h b/src/Encrypt.h index 5b514b3b8c5..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 @@ -11,7 +9,7 @@ namespace TW::Encrypt { -/// Determind needed padding size (used internally) +/// Determined needed padding size (used internally) size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode); /// Encrypts a block of data using AES in Cipher Block Chaining (CBC) mode. diff --git a/src/Ethereum/ABI.h b/src/Ethereum/ABI.h deleted file mode 100644 index e00285c5a21..00000000000 --- a/src/Ethereum/ABI.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 - -#include "ABI/ParamBase.h" -#include "ABI/ParamNumber.h" -#include "ABI/Parameters.h" -#include "ABI/Array.h" -#include "ABI/Bytes.h" -#include "ABI/ParamAddress.h" -#include "ABI/Function.h" -#include "ABI/ParamFactory.h" diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp deleted file mode 100644 index 5357965f894..00000000000 --- a/src/Ethereum/ABI/Array.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 "Array.h" -#include "ParamFactory.h" -#include "ValueEncoder.h" - -#include - -using namespace TW::Ethereum::ABI; - -int ParamArray::addParam(const std::shared_ptr& param) { - assert(param != nullptr); - if (param == nullptr) { return -1; } - if (_params.getCount() >= 1 && param->getType() != getFirstType()) { 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::string ParamArray::getFirstType() const { - if (_params.getCount() == 0) { return "empty"; } - return _params.getParamUnsafe(0)->getType(); -} - -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 - size_t 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; -} diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h deleted file mode 100644 index 4adc96a05ad..00000000000 --- a/src/Ethereum/ABI/Array.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 "ParamBase.h" -#include "ParamNumber.h" -#include "Parameters.h" - -namespace TW::Ethereum::ABI { - -/// Dynamic array of the same types, "[]" -class ParamArray: public ParamCollection -{ -private: - ParamSet _params; - -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); - std::string getFirstType() const; - std::shared_ptr getParam(int paramIndex) { return _params.getParamUnsafe(paramIndex); } - virtual std::string getType() const { return getFirstType() + "[]"; } - 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); -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp deleted file mode 100644 index 5fb23381ef5..00000000000 --- a/src/Ethereum/ABI/Bytes.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 "Bytes.h" -#include "ParamNumber.h" -#include "ValueEncoder.h" - -namespace TW::Ethereum::ABI { - -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 - size_t 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; -} - -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; -} - -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; -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.h b/src/Ethereum/ABI/Bytes.h deleted file mode 100644 index 69eb5aaede8..00000000000 --- a/src/Ethereum/ABI/Bytes.h +++ /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. - -#pragma once - -#include "ParamBase.h" -#include "ValueEncoder.h" -#include "../../Data.h" - -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); - } -}; - -/// 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) { _bytes = 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); - } -}; - -/// 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); - } -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Function.cpp b/src/Ethereum/ABI/Function.cpp index b2c067fcb26..c28c24918bb 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_type(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 11d020c8ab7..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 temaplate 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.h b/src/Ethereum/ABI/ParamAddress.h deleted file mode 100644 index e926d30ad50..00000000000 --- a/src/Ethereum/ABI/ParamAddress.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 "Bytes.h" -#include "../../Data.h" - -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, TW::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 { - Data data = TW::store(getVal()); - if (data.size() >= bytes) { return data; } - // need to pad - Data padded(bytes - data.size()); - TW::append(padded, data); - return padded; - } -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamBase.h b/src/Ethereum/ABI/ParamBase.h deleted file mode 100644 index 500bf032824..00000000000 --- a/src/Ethereum/ABI/ParamBase.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 "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; -}; - -/// Collection 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 47696ac43b7..00000000000 --- a/src/Ethereum/ABI/ParamFactory.cpp +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "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 (ends_with(arrayType, "[]") && arrayType.length() >= 3) { - 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 (auto i = 0; i < strings.size(); ++i) { - // parse to prevent quotes on simple values - auto value = json::parse(strings[i], nullptr, false); - if (value.is_discarded()) { - // fallback - value = json(strings[i]); - } - array.push_back(value); - } - return array.dump(); -} - -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 a15f0e249ac..00000000000 --- a/src/Ethereum/ABI/ParamFactory.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 "Array.h" -#include "Bytes.h" -#include "ParamAddress.h" - -#include -#include - -namespace TW::Ethereum::ABI { - -/// Factory creates concrete ParamBase class from string type. -class ParamFactory -{ -public: - static std::shared_ptr make(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 fceae0b4317..00000000000 --- a/src/Ethereum/ABI/ParamNumber.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 "ParamNumber.h" - -#include "../../Data.h" -#include "../../uint256.h" - -#include -#include - -using namespace TW; -using namespace TW::Ethereum::ABI; - -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 5a56c783933..00000000000 --- a/src/Ethereum/ABI/ParamNumber.h +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "../../Data.h" -#include "../../uint256.h" - -#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); - } -private: - 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(); } -}; - -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(); } -}; - -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(); } -}; - -class ParamUInt8 : public ParamNumberType -{ -public: - ParamUInt8() : ParamNumberType(0) {} - ParamUInt8(uint8_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint8"; } -}; - -class ParamInt8 : public ParamNumberType -{ -public: - ParamInt8() : ParamNumberType(0) {} - ParamInt8(int8_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int8"; } -}; - -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); - -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); - -private: - void init(); - int256_t _val; - uint256_t _mask; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp deleted file mode 100644 index 627570b8fc8..00000000000 --- a/src/Ethereum/ABI/Parameters.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 "Parameters.h" -#include "ValueEncoder.h" - -#include -#include - -using namespace TW::Ethereum::ABI; - -ParamSet::~ParamSet() { - _params.clear(); -} - -/// Returns the index of the parameter -int ParamSet::addParam(const std::shared_ptr& param) { - assert(param.get() != nullptr); - if (param.get() == nullptr) { - return -1; - } - _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(); - ++cnt; - } - t += ")"; - return t; -} - -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; -} diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h deleted file mode 100644 index 4fd433ed16b..00000000000 --- a/src/Ethereum/ABI/Parameters.h +++ /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. - -#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; - size_t getSize() const; - virtual void encode(Data& data) const; - virtual bool decode(const Data& encoded, size_t& offset_inout); - -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); } -}; - -} // 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/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 44cb7aa7f76..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" @@ -46,7 +44,7 @@ inline Data paddedOnLeft(const Data& inout) { } void ValueEncoder::encodeUInt256(const uint256_t& value, Data& inout) { - append(inout, paddedOnLeft(store(value))); + append(inout, store(value, 32)); } /// Encoding primitive: encode a number of bytes by taking hash 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 09a7c893f54..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') { @@ -38,10 +35,12 @@ Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("Ethereum::Address needs an extended SECP256k1 public key."); } - const auto data = publicKey.hash({}, static_cast(Hash::keccak256), true); + const auto data = publicKey.hash({}, Hash::HasherKeccak256, true); std::copy(data.end() - Address::size, data.end(), bytes.begin()); } 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 1f9e25d3490..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 @@ -40,6 +38,8 @@ class Address { /// Returns a string representation of the address. std::string string() const; + protected: + Address() = default; }; inline bool operator==(const Address& lhs, const Address& rhs) { @@ -47,8 +47,3 @@ inline bool operator==(const Address& lhs, const Address& rhs) { } } // namespace TW::Ethereum - -/// Wrapper for C interface. -struct TWEthereumAddress { - TW::Ethereum::Address impl; -}; 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/ContractCall.cpp b/src/Ethereum/ContractCall.cpp index e08a2cdcdee..727a21d192b 100644 --- a/src/Ethereum/ContractCall.cpp +++ b/src/Ethereum/ContractCall.cpp @@ -1,103 +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. #include "ContractCall.h" -#include "ABI.h" #include "HexCoding.h" -#include "uint256.h" -#include -#include +#include "proto/EthereumAbi.pb.h" +#include "TrustWalletCore/TWCoinType.h" using namespace std; using json = nlohmann::json; namespace TW::Ethereum::ABI { -static void fillArray(Function& func, const string& type) { - auto param = make_shared(); - auto baseType = string(type.begin(), type.end() - 2); - auto value = ParamFactory::make(baseType); - param->addParam(value); - func.addParam(param, false); -} - -static void fill(Function& func, const string& type) { - if (boost::algorithm::ends_with(type, "[]")) { - fillArray(func, type); - } else { - auto param = ParamFactory::make(type); - func.addParam(param, false); - } -} +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); -static vector getArrayValue(Function& func, const string& type, int idx) { - shared_ptr param; - func.getInParam(idx, param); - return ParamFactory::getArrayValue(param, type); -} - -static string getValue(Function& func, const string& type, int idx) { - shared_ptr param; - func.getInParam(idx, param); - return ParamFactory::getValue(param, type); -} + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_contract_call(TWCoinTypeEthereum, inputData.get()); -static json buildInputs(Function& func, const json& registry) { - auto inputs = json::array(); - for (int i = 0; i < registry["inputs"].size(); i++) { - auto info = registry["inputs"][i]; - auto type = info["type"]; - auto input = json{ - {"name", info["name"]}, - {"type", type} - }; - if (boost::algorithm::ends_with(type.get(), "[]")) { - input["value"] = json(getArrayValue(func, type, i)); - } else if (type == "bool") { - input["value"] = getValue(func, type, i) == "true" ? json(true) : json(false); - } else { - input["value"] = getValue(func, type, i); - } - inputs.push_back(input); - } - return inputs; -} - -optional decodeCall(const Data& call, const json& abi) { - // check bytes length - if (call.size() <= 4) { + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return {}; } - auto methodId = hex(Data(call.begin(), call.begin() + 4)); - - if (abi.find(methodId) == abi.end()) { - return {}; - } - - // build Function with types - const auto registry = abi[methodId]; - auto func = Function(registry["name"]); - for (auto& input : registry["inputs"]) { - fill(func, input["type"]); - } + 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, registry)}, - }; - 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/Entry.cpp b/src/Ethereum/Entry.cpp index 35297782d23..6560b3969f7 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -1,34 +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" - -using namespace TW::Ethereum; -using namespace std; - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); +#include "HexCoding.h" +#include "proto/Ethereum.pb.h" + +namespace TW::Ethereum { + +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::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(); -} - -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); -} +} // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index 36bbda6dfd2..69d67808596 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -1,40 +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 const std::vector coinTypes() const { - return { - TWCoinTypeCallisto, - TWCoinTypeEthereum, - TWCoinTypeEthereumClassic, - TWCoinTypeGoChain, - TWCoinTypePOANetwork, - TWCoinTypeThunderToken, - TWCoinTypeTomoChain, - TWCoinTypeSmartChainLegacy, - TWCoinTypeSmartChain, - TWCoinTypePolygon, - TWCoinTypeWanchain, - }; - } - 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 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 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..c65e5a894e8 --- /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(TWCoinTypeEthereum, inputData.get()); + + 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(TWCoinTypeEthereum, inputData.get()); + + 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(TWCoinTypeEthereum, inputData.get()); +} + +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 e549793fecf..8a543c1e8de 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -1,252 +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 "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}; - } - - return encode(bytes); -} - -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; -} - -Data RLP::encode(const Transaction& transaction) noexcept { - auto encoded = Data(); - append(encoded, encode(transaction.nonce)); - append(encoded, encode(transaction.gasPrice)); - append(encoded, encode(transaction.gasLimit)); - append(encoded, encode(transaction.to)); - append(encoded, encode(transaction.amount)); - append(encoded, encode(transaction.payload)); - append(encoded, encode(transaction.v)); - append(encoded, encode(transaction.r)); - append(encoded, encode(transaction.s)); - return encodeList(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::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept { - if (size < 56) { - return {static_cast(smallTag + size)}; + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; } - const auto sizeData = putint(size); + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.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; + return data(output.encoded()); } -Data RLP::putint(uint64_t i) noexcept { - // clang-format off - if (i < (1ULL << 8)) - return {static_cast(i)}; - if (i < (1ULL << 16)) - return { - static_cast(i >> 8), - static_cast(i), - }; - if (i < (1ULL << 24)) - return { - static_cast(i >> 16), - static_cast(i >> 8), - static_cast(i), - }; - if (i < (1ULL << 32)) - return { - static_cast(i >> 24), - static_cast(i >> 16), - static_cast(i >> 8), - static_cast(i), - }; - if (i < (1ULL << 40)) - return { - static_cast(i >> 32), - static_cast(i >> 24), - static_cast(i >> 16), - static_cast(i >> 8), - static_cast(i), - }; - if (i < (1ULL << 48)) - return { - static_cast(i >> 40), - static_cast(i >> 32), - static_cast(i >> 24), - static_cast(i >> 16), - static_cast(i >> 8), - static_cast(i), - }; - if (i < (1ULL << 56)) - return { - static_cast(i >> 48), - static_cast(i >> 40), - static_cast(i >> 32), - static_cast(i >> 24), - static_cast(i >> 16), - static_cast(i >> 8), - static_cast(i), - }; - - return { - static_cast(i >> 56), - static_cast(i >> 48), - static_cast(i >> 40), - static_cast(i >> 32), - static_cast(i >> 24), - static_cast(i >> 16), - static_cast(i >> 8), - static_cast(i), - }; - // clang-format on +Data RLP::encodeString(const std::string& s) { + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_string_item(s); + return encode(input); } -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; -} +Data RLP::encodeU256(const uint256_t & num) { + auto numData = store(num); -uint64_t RLP::decodeLength(const Data& data) { - size_t index = 0; - auto decodedLen = decodeVarInt(data, index); - if (!std::get<0>(decodedLen)) { - throw std::invalid_argument("can't decode length of string/list length"); - } - return std::get<1>(decodedLen); + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_number_u256(numData.data(), numData.size()); + return encode(input); } -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) { - // 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 = Data(input.begin() + 1, input.end()); - return item; - } - if (prefix <= 0xb7) { - // 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.push_back(Data()); - item.remainder = Data(input.begin() + 1, input.end()); - 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"); - } - - item.decoded.push_back(subData(input, 1, strLen)); - item.remainder = Data(input.begin() + 1 + strLen, input.end()); - - return item; - } - if (prefix <= 0xbf) { - // long string - auto lenOfStrLen = prefix - 0xb7; - auto strLen = static_cast(decodeLength(subData(input, 1, lenOfStrLen))); - if (inputLen < lenOfStrLen || inputLen < lenOfStrLen + strLen) { - throw std::invalid_argument("Invalid rlp encoding length"); - } - auto data = subData(input, 1 + lenOfStrLen, strLen); - item.decoded.push_back(data); - item.remainder = Data(input.begin() + 1 + lenOfStrLen + strLen, input.end()); - return item; - } - if (prefix <= 0xf7) { - // a list between 0-55 bytes long - auto listLen = prefix - 0xc0; - if (inputLen < listLen) { - throw std::invalid_argument("Invalid rlp string length"); - } - // empty list - if (listLen == 0) { - item.remainder = Data(input.begin() + 1, input.end()); - return item; - } - - // decode list - auto listItem = decodeList(subData(input, 1, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = Data(input.begin() + 1 + listLen, input.end()); - return item; - } - if (prefix <= 0xff) { - auto lenOfListLen = prefix - 0xf7; - auto listLen = static_cast(decodeLength(subData(input, 1, lenOfListLen))); - if (inputLen < lenOfListLen || inputLen < lenOfListLen + listLen) { - throw std::invalid_argument("Invalid rlp list length"); - } - if (input[1] == 0) { - throw std::invalid_argument("multi-byte length must have no leading zero"); - } - if (listLen < 56) { - throw std::invalid_argument("length below 56 must be encoded in one byte"); - } - // decode list - auto listItem = decodeList(subData(input, 1 + lenOfListLen, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = Data(input.begin() + 1 + lenOfListLen + listLen, input.end()); - return item; - } - throw std::invalid_argument("input don't conform RLP encoding form"); -} +} // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index d65a2d9aaa7..5163d339514 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.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 "Transaction.h" -#include "../Data.h" -#include "../uint256.h" +#include "Data.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" #include #include @@ -20,90 +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; - - /// Encodes a transaction. - static Data encode(const Transaction& transaction) 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; - - /// Returns the representation of an integer using the least number of bytes - /// needed. - static Data putint(uint64_t i) noexcept; - - struct DecodedItem { - std::vector decoded; - Data remainder; - }; - - static DecodedItem decodeList(const Data& input); - static uint64_t decodeLength(const Data& data); - /// Decodes data, remainder from RLP encoded data - static DecodedItem decode(const Data& data); + 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 609cd6704af..00000000000 --- a/src/Ethereum/Signer.cpp +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 - -using namespace TW; -using namespace TW::Ethereum; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - try { - auto signer = Signer(load(input.chain_id())); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Signer::build(input); - - signer.sign(key, transaction); - - auto output = Proto::SigningOutput(); - - auto encoded = RLP::encode(transaction); - output.set_encoded(encoded.data(), encoded.size()); - - auto v = store(transaction.v); - output.set_v(v.data(), v.size()); - - auto r = store(transaction.r); - output.set_r(r.data(), r.size()); - - auto s = store(transaction.s); - 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()); -} - -std::tuple Signer::values(const uint256_t &chainID, - 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); - v += 27; - - boost::multiprecision::uint256_t newV; - if (chainID != 0) { - import_bits(newV, signature.begin() + 64, signature.begin() + 65); - newV += 35 + chainID + chainID; - } else { - newV = v; - } - return std::make_tuple(r, s, newV); -} - -std::tuple -Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept { - auto signature = privateKey.sign(hash, TWCurveSECP256k1); - return values(chainID, signature); -} - -// May throw -Data addressStringToData(const std::string& asString) { - if (asString.empty()) { - return {}; - } - auto address = Address(asString); - Data asData; - asData.resize(20); - std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); - return asData; -} - -Transaction 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()); - switch (input.transaction().transaction_oneof_case()) { - case Proto::Transaction::kTransfer: - { - auto transaction = Transaction( - /* nonce: */ nonce, - /* gasPrice: */ gasPrice, - /* gasLimit: */ gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optionalTransaction: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - return transaction; - } - - case Proto::Transaction::kErc20Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc20_transfer().to()); - auto transaction = Transaction::buildERC20Transfer( - /* nonce: */ nonce, - /* gasPrice: */ gasPrice, - /* gasLimit: */ gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - return transaction; - } - - case Proto::Transaction::kErc20Approve: - { - Data spenderAddress = addressStringToData(input.transaction().erc20_approve().spender()); - auto transaction = Transaction::buildERC20Approve( - /* nonce: */ nonce, - /* gasPrice: */ gasPrice, - /* gasLimit: */ gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - return transaction; - } - - case Proto::Transaction::kErc721Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc721_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc721_transfer().from()); - auto transaction = Transaction::buildERC721Transfer( - /* nonce: */ nonce, - /* gasPrice: */ gasPrice, - /* gasLimit: */ gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - return transaction; - } - - case Proto::Transaction::kErc1155Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc1155_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc1155_transfer().from()); - auto transaction = Transaction::buildERC1155Transfer( - /* nonce: */ nonce, - /* gasPrice: */ gasPrice, - /* gasLimit: */ 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()) - ); - return transaction; - } - - case Proto::Transaction::kContractGeneric: - default: - { - auto transaction = Transaction( - /* nonce: */ nonce, - /* gasPrice: */ gasPrice, - /* gasLimit: */ gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - return transaction; - } - } -} - -void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept { - auto hash = this->hash(transaction); - auto tuple = Signer::sign(chainID, privateKey, hash); - - transaction.r = std::get<0>(tuple); - transaction.s = std::get<1>(tuple); - transaction.v = std::get<2>(tuple); -} - -Data Signer::hash(const Transaction &transaction) const noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); - append(encoded, RLP::encode(transaction.to)); - append(encoded, RLP::encode(transaction.amount)); - append(encoded, RLP::encode(transaction.payload)); - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); - return Hash::keccak256(RLP::encodeList(encoded)); -} diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h deleted file mode 100644 index b041b64417f..00000000000 --- a/src/Ethereum/Signer.h +++ /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. - -#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: - uint256_t chainID; - - /// Initializes a signer with a chain identifier. - explicit Signer(uint256_t chainID) : chainID(std::move(chainID)) {} - - /// Signs the given transaction. - void sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept; - - public: - /// build Transaction from signing input - static Transaction 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 std::tuple - 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 - static std::tuple values(const uint256_t &chainID, - const Data& signature) noexcept; - - protected: - /// Computes the transaction hash. - Data hash(const Transaction &transaction) const noexcept; -}; - -} // namespace TW::Ethereum - -/// Wrapper for C interface. -struct TWEthereumSigner { - TW::Ethereum::Signer impl; -}; diff --git a/src/Ethereum/Transaction.cpp b/src/Ethereum/Transaction.cpp deleted file mode 100644 index 7b09ec38fe6..00000000000 --- a/src/Ethereum/Transaction.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 "Transaction.h" -#include "ABI/Function.h" -#include "ABI/ParamBase.h" -#include "ABI/ParamAddress.h" - -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; -using namespace TW; - -Transaction Transaction::buildERC20Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& toAddress, uint256_t amount) { - return Transaction(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20TransferCall(toAddress, amount)); -} - -Transaction Transaction::buildERC20Approve(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& spenderAddress, uint256_t amount) { - return Transaction(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20ApproveCall(spenderAddress, amount)); -} - -Transaction Transaction::buildERC721Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& from, const Data& to, uint256_t tokenId) { - return Transaction(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC721TransferFromCall(from, to, tokenId)); -} - -Transaction Transaction::buildERC1155Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& from, const Data& to, uint256_t tokenId, uint256_t value, const Data& data) { - return Transaction(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC1155TransferFromCall(from, to, tokenId, value, data)); -} - -Data Transaction::buildERC20TransferCall(const Data& to, 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 Transaction::buildERC20ApproveCall(const Data& spender, 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 Transaction::buildERC721TransferFromCall(const Data& from, const Data& to, 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 Transaction::buildERC1155TransferFromCall(const Data& from, const Data& to, uint256_t tokenId, 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; -} diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h deleted file mode 100644 index 1f8bb516f88..00000000000 --- a/src/Ethereum/Transaction.h +++ /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. - -#pragma once - -#include "Address.h" -#include "../uint256.h" - -namespace TW::Ethereum { - -class Transaction { -public: - uint256_t nonce; - uint256_t gasPrice; - uint256_t gasLimit; - // Public key hash (Address.bytes) - Data to; - uint256_t amount; - Data payload; - - // Signature values - uint256_t v = uint256_t(); - uint256_t r = uint256_t(); - uint256_t s = uint256_t(); - - // Factory methods - // Create an ERC20 token transfer transaction - static Transaction buildERC20Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& toAddress, uint256_t amount); - - // Create an ERC20 approve transaction - static Transaction buildERC20Approve(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& spenderAddress, uint256_t amount); - - // Create an ERC721 NFT transfer transaction - static Transaction buildERC721Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& from, const Data& to, uint256_t tokenId); - - // Create an ERC1155 NFT transfer transaction - static Transaction buildERC1155Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, - const Data& tokenContract, const Data& from, const Data& to, uint256_t tokenId, uint256_t value, const Data& data); - - // Helpers for building contract calls - static Data buildERC20TransferCall(const Data& to, uint256_t amount); - static Data buildERC20ApproveCall(const Data& spender, uint256_t amount); - static Data buildERC721TransferFromCall(const Data& from, const Data& to, uint256_t tokenId); - static Data buildERC1155TransferFromCall(const Data& from, const Data& to, uint256_t tokenId, uint256_t value, const Data& data); - -public: - Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, uint256_t amount, const Data& payload = {}) - : nonce(std::move(nonce)) - , gasPrice(std::move(gasPrice)) - , gasLimit(std::move(gasLimit)) - , to(std::move(to)) - , amount(std::move(amount)) - , payload(std::move(payload)) {} -}; - -} // 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..b1325a06c57 --- /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(), static_cast(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..226e0c2bd7d --- /dev/null +++ b/src/Everscale/CommonTON/Cell.cpp @@ -0,0 +1,442 @@ +// 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) { + const auto index = static_cast(i); + if (data[index] == 0) { + bitLen -= 8; + } else { + auto skip = 1; + uint8_t mask = 1; + while ((data[index] & 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..b3cbcbcdc57 --- /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(), TWCurveED25519); + 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 dbea7aac7b1..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" @@ -91,7 +89,8 @@ Data Encryption::getSharedSecret(const PrivateKey& privateKey1, const PublicKey& // See https://github.com/fioprotocol/fiojs/blob/master/src/ecc/key_private.js curve_point KBP; - assert(ecdsa_read_pubkey(&secp256k1, publicKey2.bytes.data(), &KBP)); + [[maybe_unused]] int read_res = ecdsa_read_pubkey(&secp256k1, publicKey2.bytes.data(), &KBP); + assert(read_res); bignum256 privBN; bn_read_be(privateKey1.bytes.data(), &privBN); 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 3978da39256..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeFIO}; } - 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 71eaa4764ea..3061accf7b2 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,8 +46,31 @@ 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()); + PrivateKey privateKey(in.private_key(), TWCurveSECP256k1); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); Address owner(publicKey); @@ -56,7 +85,7 @@ string TransactionBuilder::sign(Proto::SigningInput in) { // process addresses std::vector> addresses; for (int i = 0; i < action.public_addresses_size(); ++i) { - addresses.push_back(std::make_pair(action.public_addresses(i).coin_symbol(), action.public_addresses(i).address())); + addresses.emplace_back(std::make_pair(action.public_addresses(i).coin_symbol(), action.public_addresses(i).address())); } json = TransactionBuilder::createAddPubAddress(owner, privateKey, action.fio_address(), addresses, @@ -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 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; +} - const auto apiName = "addaddress"; +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()); + } + + 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 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 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 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 f12887c612d..cae8b677bdb 100644 --- a/src/Filecoin/Address.cpp +++ b/src/Filecoin/Address.cpp @@ -1,141 +1,283 @@ -// 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; + +static constexpr uint64_t charMask = 0x80; -bool Address::isValid(const Data& data) { - if (data.size() < 2) { +/// 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); +} - // Extract raw address. - Data address; - address.push_back(static_cast(type)); - address.insert(address.end(), decoded.data(), decoded.data() + size); +/// 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; +} - // Verify checksum. - Data should_sum = Hash::blake2b(address, checksumSize); - return std::memcmp(should_sum.data(), decoded.data() + size, checksumSize) == 0; +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; } -bool Address::isValid(const std::string& string) { - if (string.length() < 3) { - return false; +MaybeAddress Address::fromBytes(const Data& encoded) { + // Should contain at least one byte (address type). + if (encoded.empty()) { + return std::nullopt; + } + + // 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; + + 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; + } +} + +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 { @@ -143,36 +285,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 f972089aa03..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,16 +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 const std::vector coinTypes() const { return {TWCoinTypeFilecoin}; } - 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..8db3a82cebd 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()), TWCurveSECP256k1); + 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()), TWCurveSECP256k1); + 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); + + // 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 3d20617bcb4..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,11 +26,22 @@ class Signer { /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; -}; -} // namespace TW::Filecoin + /// 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); -/// Wrapper for C interface. -struct TWFilecoinSigner { - TW::Filecoin::Signer impl; + /// 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 bf77a0f7ddb..74253e2799b 100644 --- a/src/Groestlcoin/Address.cpp +++ b/src/Groestlcoin/Address.cpp @@ -1,29 +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 "Address.h" +#include "Base58.h" -#include "../Base58.h" +#include #include -#include - -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin { bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::groestl512d); + 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::groestl512d); + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { return false; } @@ -34,7 +30,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::groestl512d); + 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 +54,7 @@ Address::Address(const PublicKey& publicKey, uint8_t prefix) { } std::string Address::string() const { - return Base58::bitcoin.encodeCheck(bytes, Hash::groestl512d); + 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 20878ff6eef..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, 0, 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 33470bbc05d..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,13 +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 const std::vector coinTypes() const { return {TWCoinTypeGroestlcoin}; } - 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 01e16a7256f..6afa90bc8ec 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,20 +10,25 @@ #include "HexCoding.h" #include "Transaction.h" -using namespace TW; -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin { using TransactionBuilder = Bitcoin::TransactionBuilder; TransactionPlan Signer::plan(const SigningInput& input) noexcept { - auto signer = Bitcoin::TransactionSigner(std::move(input)); - return signer.plan.proto(); + if (input.has_signing_v2()) { + return Bitcoin::Signer::planAsV2(input); + } + 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 { + if (input.has_signing_v2()) { + return Bitcoin::Signer::signAsV2(input); + } + SigningOutput output; - auto signer = Bitcoin::TransactionSigner(std::move(input)); - auto result = signer.sign(); + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); if (!result) { output.set_error(result.error()); return output; @@ -34,7 +37,7 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { *output.mutable_transaction() = tx.proto(); Data encoded; - signer.encodeTx(tx, encoded); + tx.encode(encoded); output.set_encoded(encoded.data(), encoded.size()); Data txHashData = encoded; @@ -47,3 +50,28 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { output.set_transaction_id(hex(txHash)); return output; } + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + if (input.has_signing_v2()) { + return Bitcoin::Signer::preImageHashesAsV2(input); + } + + 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 ab86f51db11..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 @@ -11,9 +9,9 @@ namespace TW::Groestlcoin { struct Transaction : public Bitcoin::Transaction { - Transaction() : Bitcoin::Transaction(1, 0, static_cast(Hash::sha256)) {} - Transaction(int32_t version, uint32_t lockTime) : - Bitcoin::Transaction(version, lockTime, static_cast(Hash::sha256)) {} + Transaction() : Bitcoin::Transaction(1, 0, Hash::HasherSha256) {} + Transaction(int32_t version, uint32_t lockTime = 0) : + Bitcoin::Transaction(version, lockTime, Hash::HasherSha256) {} }; } // namespace TW::Groestlcoin diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index d655f687a5f..a329f6634da 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -1,141 +1,270 @@ -// 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 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 -HDWallet::HDWallet(int strength, const std::string& passphrase) - : seed(), mnemonic(), passphrase(passphrase) { - const char* mnemonic_chars = mnemonic_generate(strength); - mnemonic_to_seed(mnemonic_chars, passphrase.c_str(), seed.data(), nullptr); - mnemonic = mnemonic_chars; - updateEntropy(); +const int MnemonicBufLength = Mnemonic::MaxWords * (BIP39_MAX_WORD_LENGTH + 3) + 20; // some extra slack + +template +HDWallet::HDWallet(const Data& seed) { + std::copy_n(seed.begin(), seedSize, this->seed.begin()); } -HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase) - : seed(), mnemonic(mnemonic), passphrase(passphrase) { +template +void HDWallet::updateSeedAndEntropy([[maybe_unused]] bool check) { + assert(!check || Mnemonic::isValid(mnemonic)); // precondition + + // generate seed from mnemonic mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed.data(), nullptr); - updateEntropy(); + + // 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); } -HDWallet::HDWallet(const Data& data, const std::string& passphrase) - : seed(), mnemonic(), passphrase(passphrase) { - const char* mnemonic_chars = mnemonic_from_data(data.data(), static_cast(data.size())); - if (mnemonic_chars) { - mnemonic_to_seed(mnemonic_chars, passphrase.c_str(), seed.data(), nullptr); - mnemonic = mnemonic_chars; - updateEntropy(); +template +HDWallet::HDWallet(int strength, const std::string& passphrase) + : passphrase(passphrase) { + char buf[MnemonicBufLength]; + const char* mnemonic_chars = mnemonic_generate(strength, buf, MnemonicBufLength); + if (mnemonic_chars == nullptr) { + throw std::invalid_argument("Invalid strength"); } + mnemonic = mnemonic_chars; + 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(const std::string& mnemonic, const std::string& passphrase, const bool check) + : mnemonic(mnemonic), passphrase(passphrase) { + if (mnemonic.length() == 0 || + (check && !Mnemonic::isValid(mnemonic))) { + throw std::invalid_argument("Invalid mnemonic"); + } + updateSeedAndEntropy(check); } -void HDWallet::updateEntropy() { - // generate entropy (from mnemonic) - Data entropyRaw(32 + 1); - auto entropyBits = mnemonic_to_bits(mnemonic.c_str(), entropyRaw.data()); - // copy to truncate - entropy = data(entropyRaw.data(), entropyBits / 8); +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); + if (mnemonic_chars == nullptr) { + throw std::invalid_argument("Invalid mnemonic data"); + } + mnemonic = mnemonic_chars; + TW::memzero(buf, MnemonicBufLength); + updateSeedAndEntropy(); +} + +template +HDWallet::~HDWallet() { + memzero(seed.data(), seed.size()); + memzero(mnemonic.data(), mnemonic.size()); + memzero(passphrase.data(), passphrase.size()); } -PrivateKey HDWallet::getMasterKey(TWCurve curve) const { +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; +} + +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; +} + +template +PrivateKey HDWallet::getMasterKey(TWCurve curve) const { auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key, node.private_key + PrivateKey::size); - return PrivateKey(data); + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); + return PrivateKey(data, curve); } -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); - return PrivateKey(data); + auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); + return PrivateKey(data, curve); } -PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { - const auto curve = TWCoinTypeCurve(coin); - const auto privateKeyType = getPrivateKeyType(curve); - auto node = getNode(*this, curve, derivationPath); +template +DerivationPath HDWallet::cardanoStakingDerivationPath(const DerivationPath& path) { + DerivationPath stakingPath = path; + stakingPath.indices[3].value = 2; + stakingPath.indices[4].value = 0; + return stakingPath; +} + +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 PrivateKeyTypeExtended96: - { - 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); - return PrivateKey(pkData, extData, chainCode); - } - - 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), curve); + } + 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, curve); + } + 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, curve)); + } + return PrivateKey(data, curve); } } -std::string HDWallet::deriveAddress(TWCoinType coin) const { - const auto derivationPath = TW::derivationPath(coin); - return TW::deriveAddress(coin, getKey(coin, derivationPath)); +template +PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { + const auto curve = TWCoinTypeCurve(coin); + return getKeyByCurve(curve, derivationPath); } -std::string HDWallet::getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { +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)); +} + +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); +} + +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, 0x80000000); + hdnode_private_ckd(&node, account + 0x80000000); return serialize(&node, fingerprintValue, version, false, base58Hasher(coin)); } -std::string HDWallet::getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) 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, 0x80000000); + hdnode_private_ckd(&node, account + 0x80000000); hdnode_fill_public_key(&node); 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); @@ -151,17 +280,28 @@ 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 && keyType == TWPublicKeyTypeSECP256k1) { - return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); - } else if (curve == TWCurveNIST256p1 && keyType == TWPublicKeyTypeNIST256p1) { - return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeNIST256p1); + if (curve == TWCurveSECP256k1) { + auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); + if (keyType == TWPublicKeyTypeSECP256k1Extended) { + return pubkey.extended(); + } else { + return pubkey; + } + } else if (curve == TWCurveNIST256p1) { + auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeNIST256p1); + if (keyType == TWPublicKeyTypeNIST256p1Extended) { + return pubkey.extended(); + } else { + return pubkey; + } } 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); @@ -172,31 +312,25 @@ std::optional HDWallet::getPrivateKeyFromExtended(const std::string& hdnode_private_ckd(&node, path.change()); hdnode_private_ckd(&node, path.address()); - return PrivateKey(Data(node.private_key, node.private_key + 32)); + return PrivateKey(Data(node.private_key, node.private_key + 32), curve); } -HDWallet::PrivateKeyType HDWallet::getPrivateKeyType(TWCurve curve) { - switch (curve) { - case TWCurve::TWCurveED25519Extended: - // used by Cardano - return PrivateKeyTypeExtended96; - case TWCurve::TWCurveED25519HD: - return PrivateKeyTypeHD; - 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 = hasher(node->public_key, 33); - return ((uint32_t) digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; + auto digest = Hash::hash(hasher, node->public_key, 33); + 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); @@ -212,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; } @@ -246,55 +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::PrivateKeyTypeHD: - case HDWallet::PrivateKeyTypeExtended96: - // special handling for extended - 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::PrivateKeyTypeExtended96: - // special handling for extended, use entropy (not seed) - hdnode_from_entropy_cardano_icarus((const uint8_t*)"", 0, wallet.entropy.data(), (int)wallet.entropy.size(), &node); - break; - case HDWallet::PrivateKeyTypeHD: - hdnode_from_seed_hd(wallet.seed.data(), HDWallet::seedSize, curveName(curve), &node); - break; - case HDWallet::PrivateKeyTypeDefault32: - default: - hdnode_from_seed(wallet.seed.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 TWCurveED25519HD: - return ED25519_HD_NAME; case TWCurveED25519Blake2bNano: return ED25519_BLAKE2B_NANO_NAME; - case TWCurveED25519Extended: + case TWCurveED25519ExtendedCardano: return ED25519_CARDANO_NAME; case TWCurveNIST256p1: return NIST256P1_NAME; @@ -307,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 08f0af501f9..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 @@ -16,6 +14,7 @@ #include #include #include +#include #include #include @@ -23,34 +22,47 @@ 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; - public: - /// Wallet seed. + private: + /// Wallet seed, derived one-way from the mnemonic and passphrase std::array seed; - /// Mnemonic word list. + /// Mnemonic word list (aka. recovery phrase). std::string mnemonic; - /// Mnemonic passphrase. + /// Passphrase for mnemonic encryption. std::string passphrase; - /// Entropy bytes (11 bits from each word) + /// Entropy is the binary 1-to-1 representation of the mnemonic (11 bits from each word) TW::Data entropy; +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 a new random HDWallet with the provided strength in bits. + /// 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); - /// Initializes an HDWallet from a mnemonic seed. - HDWallet(const std::string& mnemonic, const std::string& passphrase); + /// Initializes an HDWallet from a BIP39 mnemonic and a passphrase, check English dict by default. + /// Throws on invalid mnemonic. + HDWallet(const std::string& mnemonic, const std::string& passphrase, const bool check = true); - /// Initializes an HDWallet from a seed. - HDWallet(const Data& data, const std::string& passphrase); + /// Initializes an HDWallet from an entropy. + /// Throws on invalid data. + HDWallet(const Data& entropy, const std::string& passphrase); HDWallet(const HDWallet& other) = default; HDWallet(HDWallet&& other) = default; @@ -59,47 +71,79 @@ class HDWallet { virtual ~HDWallet(); - void updateEntropy(); - /// Returns master key. PrivateKey getMasterKey(TWCurve curve) const; /// Returns the master private key extension (32 byte). PrivateKey getMasterKeyExtension(TWCurve curve) const; + /// Returns the private key with the given derivation. + PrivateKey getKey(const TWCoinType coin, TWDerivation derivation) const; + /// Returns the private key at the given derivation path. PrivateKey getKey(const TWCoinType coin, const DerivationPath& derivationPath) const; - /// Derives the address for a coin. + /// 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; - /// Returns the extended private key. - std::string getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) 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 exteded public key. - std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const; + /// 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, TWDerivationDefault, version, 0); + } - /// Computes the public key from an exteded public key representation. + /// 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, 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, 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, TWDerivation derivation, TWHDVersion version, uint32_t account) const; + + /// Returns the BIP32 Root Key (private) + std::string getRootKey(TWCoinType coin, TWHDVersion version) const; + + /// Computes the public key from an extended public key representation. static std::optional getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); - /// Computes the private key from an exteded private key representation. + /// 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 - PrivateKeyTypeExtended96 = 1, // 3*32-byte extended private key - PrivateKeyTypeHD = 2, // 32-byte private key - }; - - // 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 + static DerivationPath cardanoStakingDerivationPath(const DerivationPath& path); }; } // namespace TW /// 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 a36d8507ec1..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 @@ -30,7 +28,7 @@ class Address: public Bech32Address { } /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA3K, publicKey) { + Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherKeccak256, publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("address may only be an extended SECP256k1 public key"); } diff --git a/src/Harmony/Entry.cpp b/src/Harmony/Entry.cpp index df752cb965e..e9ad9fdd624 100644 --- a/src/Harmony/Entry.cpp +++ b/src/Harmony/Entry.cpp @@ -1,27 +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 "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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!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) { + 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 b3d54ecfd7b..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeHarmony}; } - 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; + 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 200ca662d3a..8a1cb459306 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -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. #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)); @@ -22,18 +22,18 @@ 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); - auto r = store(transaction.r); - auto s = store(transaction.s); + auto v = store(transaction.v, 1); + auto r = store(transaction.r, 32); + auto s = store(transaction.s, 32); protoOutput.set_encoded(encoded.data(), encoded.size()); protoOutput.set_v(v.data(), v.size()); @@ -44,48 +44,51 @@ 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; } -Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput &input) noexcept { - 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())); +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()); +} + +Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) { + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); + + auto transaction = Signer::buildUnsignedTransaction(input); auto signer = Signer(uint256_t(load(input.chain_id()))); auto hash = signer.hash(transaction); @@ -96,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 { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); +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()), TWCurveSECP256k1); + 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(), @@ -141,19 +192,14 @@ Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput &inpu auto commissionRates = CommissionRate(rate, maxRate, maxChangeRate); std::vector slotPubKeys; for (auto pk : input.staking_message().create_validator_message().slot_pub_keys()) { - slotPubKeys.push_back(Data(pk.begin(), pk.end())); + slotPubKeys.emplace_back(Data(pk.begin(), pk.end())); } std::vector slotKeySigs; for (auto sig : input.staking_message().create_validator_message().slot_key_sigs()) { - slotKeySigs.push_back(Data(sig.begin(), sig.end())); + 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, @@ -164,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(), @@ -198,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, @@ -223,280 +256,401 @@ 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 { - 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 70d048838b9..5eea695bea4 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 @@ -25,25 +23,31 @@ 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); 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; @@ -52,29 +56,37 @@ 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) 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); + } /// 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; @@ -87,16 +99,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; -} // namespace TW::Harmony + template + EthereumRlp::Proto::RlpItem rlpPrepareDescription(const Staking& transaction) const noexcept; -/// Wrapper for C interface. -struct TWHarmonySigner { - TW::Harmony::Signer impl; + 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 56c43735389..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 @@ -37,14 +34,14 @@ class Staking { Staking(uint8_t directive, Directive stakeMsg, uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t v, uint256_t r, uint256_t s) - : directive(move(directive)) - , stakeMsg(move(stakeMsg)) - , nonce(move(nonce)) - , gasPrice(move(gasPrice)) - , gasLimit(move(gasLimit)) - , v(move(v)) - , r(move(r)) - , s(move(s)) {} + : directive(std::move(directive)) + , stakeMsg(std::move(stakeMsg)) + , nonce(std::move(nonce)) + , gasPrice(std::move(gasPrice)) + , gasLimit(std::move(gasLimit)) + , v(std::move(v)) + , r(std::move(r)) + , s(std::move(s)) {} }; enum Directive : uint8_t { @@ -65,11 +62,11 @@ class Description { Description(string name, string identity, string website, string securityContact, string details) - : name(move(name)) - , identity(move(identity)) - , website(move(website)) - , securityContact(move(securityContact)) - , details(move(details)) {} + : name(std::move(name)) + , identity(std::move(identity)) + , website(std::move(website)) + , securityContact(std::move(securityContact)) + , details(std::move(details)) {} }; const uint256_t MAX_PRECISION = 18; @@ -95,7 +92,7 @@ class CommissionRate { Decimal maxChangeRate; CommissionRate(Decimal rate, Decimal maxRate, Decimal maxChangeRate) - : rate(move(rate)), maxRate(move(maxRate)), maxChangeRate(move(maxChangeRate)) {} + : rate(std::move(rate)), maxRate(std::move(maxRate)), maxChangeRate(std::move(maxChangeRate)) {} }; class CreateValidator { @@ -113,14 +110,14 @@ class CreateValidator { CommissionRate commissionRates, uint256_t minSelfDelegation, uint256_t maxTotalDelegation, vector> slotPubKeys, vector> slotKeySigs,uint256_t amount) - : validatorAddress(move(validatorAddress)) - , description(move(description)) - , commissionRates(move(commissionRates)) - , minSelfDelegation(move(minSelfDelegation)) - , maxTotalDelegation(move(maxTotalDelegation)) - , amount(move(amount)) - , slotPubKeys(move(slotPubKeys)) - , slotKeySigs(move(slotKeySigs)) {} + : validatorAddress(std::move(validatorAddress)) + , description(std::move(description)) + , commissionRates(std::move(commissionRates)) + , minSelfDelegation(std::move(minSelfDelegation)) + , maxTotalDelegation(std::move(maxTotalDelegation)) + , amount(std::move(amount)) + , slotPubKeys(std::move(slotPubKeys)) + , slotKeySigs(std::move(slotKeySigs)) {} }; class EditValidator { @@ -139,15 +136,15 @@ class EditValidator { uint256_t minSelfDelegation, uint256_t maxTotalDelegation, vector slotKeyToRemove, vector slotKeyToAdd, vector slotKeyToAddSig, uint256_t active) - : validatorAddress(move(validatorAddress)) - , description(move(description)) - , commissionRate(move(commissionRate)) - , minSelfDelegation(move(minSelfDelegation)) - , maxTotalDelegation(move(maxTotalDelegation)) - , slotKeyToRemove(move(slotKeyToRemove)) - , slotKeyToAdd(move(slotKeyToAdd)) - , slotKeyToAddSig(move(slotKeyToAddSig)) - , active(move(active)){} + : validatorAddress(std::move(validatorAddress)) + , description(std::move(description)) + , commissionRate(std::move(commissionRate)) + , minSelfDelegation(std::move(minSelfDelegation)) + , maxTotalDelegation(std::move(maxTotalDelegation)) + , slotKeyToRemove(std::move(slotKeyToRemove)) + , slotKeyToAdd(std::move(slotKeyToAdd)) + , slotKeyToAddSig(std::move(slotKeyToAddSig)) + , active(std::move(active)){} }; class Delegate { @@ -157,9 +154,9 @@ class Delegate { uint256_t amount; Delegate(Address delegatorAddress, Address validatorAddress, uint256_t amount) - : delegatorAddress(move(delegatorAddress)) - , validatorAddress(move(validatorAddress)) - , amount(move(amount)) {} + : delegatorAddress(std::move(delegatorAddress)) + , validatorAddress(std::move(validatorAddress)) + , amount(std::move(amount)) {} }; class Undelegate { @@ -169,16 +166,16 @@ class Undelegate { uint256_t amount; Undelegate(Address delegatorAddress, Address validatorAddress, uint256_t amount) - : delegatorAddress(move(delegatorAddress)) - , validatorAddress(move(validatorAddress)) - , amount(move(amount)) {} + : delegatorAddress(std::move(delegatorAddress)) + , validatorAddress(std::move(validatorAddress)) + , amount(std::move(amount)) {} }; class CollectRewards { public: Address delegatorAddress; - CollectRewards(Address delegatorAddress) : delegatorAddress(move(delegatorAddress)) {} + CollectRewards(Address delegatorAddress) : delegatorAddress(std::move(delegatorAddress)) {} }; } // namespace TW::Harmony 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 925d3c28fe5..80a2a7d2b6a 100644 --- a/src/Hash.cpp +++ b/src/Hash.cpp @@ -1,129 +1,125 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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 "XXHash64.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; + 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; -} - -uint64_t Hash::xxhash(const byte* data, size_t size, uint64_t seed) -{ - return XXHash64::hash(data, size, seed); -} - -Data Hash::xxhash64(const byte* data, size_t size, uint64_t seed) -{ - const auto hash = XXHash64::hash(data, size, seed); - Data result; - encode64LE(hash, result); - return result; -} - -Data Hash::xxhash64concat(const byte* data, size_t size) -{ - auto key1 = xxhash64(data, size, 0); - const auto key2 = xxhash64(data, size, 1); - TW::append(key1, key2); - return key1; + 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 47171dedcb6..bcc46a756bf 100644 --- a/src/Hash.h +++ b/src/Hash.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,33 @@ namespace TW::Hash { +/// Enum selector for the supported hash functions +enum Hasher { + HasherSha1 = 0, // SHA1 + HasherSha256, // SHA256 + HasherSha512, // SHA512 + HasherSha512_256, // SHA512/256 + HasherKeccak256, // Keccak SHA256 + HasherKeccak512, // Keccak SHA512 + 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 + HasherSha256ripemd, // ripemd hash of the SHA256 hash + HasherSha3_256ripemd, // ripemd hash of the SHA256 hash + HasherBlake256d, // Blake256 hash of the Blake256 hash + HasherBlake256ripemd, // ripemd hash of the Blake256 hash + HasherGroestl512d, // Groestl512 hash of the Groestl512 hash +}; + /// Hashing function. typedef TW::Data (*HasherSimpleType)(const TW::byte*, size_t); -using Hasher = std::function; + +/// Hash function (pointer type) from enum +TW::Hash::HasherSimpleType functionPointerFromEnum(TW::Hash::Hasher hasher); // Digest size constants, duplicating constants from underlying lib /// Number of bytes in a SHA1 hash. @@ -59,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); @@ -67,32 +92,21 @@ Data blake2b(const byte* data, size_t dataSize, size_t hsshSize, const Data& per /// Computes the Groestl 512 hash. Data groestl512(const byte* data, size_t size); -/// Computes the XXHash hash. -uint64_t xxhash(const byte* data, size_t size, uint64_t seed); - -/// Computes the XXHash hash with 64 encoding. -Data xxhash64(const byte* data, size_t size, uint64_t seed); - -/// Computes the XXHash hash concatenated, xxhash64 with seed 0 and 1, -Data xxhash64concat(const byte* data, size_t size); - -/// Computes the XXHash hash. -uint64_t xxhash(const byte* data, const byte* end, uint64_t seed); - -/// Computes the XXHash hash with 64 encoding. -Data xxhash64(const byte* data, const byte* end, uint64_t seed); - -/// Computes the XXHash hash concatenated, xxhash64 with seed 0 and 1, -Data xxhash64concat(const byte* data, const byte* end); - -// Templated versions for any type with data() and size() +/// Computes requested hash for data (hasher enum, bytes) +inline Data hash(Hasher hasher, const byte* data, size_t dataSize) { + const auto func = functionPointerFromEnum(hasher); + return func(data, dataSize); +} -/// Computes requested hash for data. +/// Computes requested hash for data (hasher enum) template Data hash(Hasher hasher, const T& data) { - return hasher(reinterpret_cast(data.data()), data.size()); + const auto func = functionPointerFromEnum(hasher); + return func(reinterpret_cast(data.data()), data.size()); } +// Templated versions for any type with data() and size() + /// Computes the SHA1 hash. template Data sha1(const T& data) { 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/Hedera/Protobuf/.gitignore b/src/Hedera/Protobuf/.gitignore new file mode 100644 index 00000000000..c96d61208c0 --- /dev/null +++ b/src/Hedera/Protobuf/.gitignore @@ -0,0 +1,3 @@ +*.cc +*.h + 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..13d2a93f9e0 --- /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()), TWCurveED25519); + 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 198691bbe60..581da5e08cb 100644 --- a/src/HexCoding.h +++ b/src/HexCoding.h @@ -1,89 +1,142 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, 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)); + 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. /// /// \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; +inline Data parse_hex(const std::string& string, bool padLeft = false) { + if (string.size() % 2 != 0 && padLeft) { + std::string temp = string; + if (temp.compare(0, 2, "0x") == 0) { + temp.erase(0, 2); + } + temp.insert(0, 1, '0'); + return internal::parse_hex(temp); } - try { - std::string temp; - boost::algorithm::unhex(it, end, std::back_inserter(temp)); - return Data(temp.begin(), temp.end()); - } catch (...) { - return {}; + 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 ""; } } -/// 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& string) { - return parse_hex(string.begin(), string.end()); +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 ss.str(); } } // namespace TW diff --git a/src/IOST/Account.cpp b/src/IOST/Account.cpp new file mode 100644 index 00000000000..24b17e61a54 --- /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, curve).sign(digest); +} + +Data Account::publicActiveKey() const { + return PrivateKey(activeKey, TWCurveED25519).getPublicKey(TWPublicKeyTypeED25519).bytes; +} + +Data Account::publicOwnerKey() const { + return PrivateKey(ownerKey, TWCurveED25519).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 699cb8be0d7..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeICON}; } - 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..e24f30feeb7 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(); @@ -81,8 +79,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput Signer::sign() const noexcept { const auto hash = Hash::sha3_256(Signer::preImage()); - const auto key = PrivateKey(input.private_key()); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto key = PrivateKey(input.private_key(), TWCurveSECP256k1); + const auto signature = key.sign(hash); auto output = Proto::SigningOutput(); output.set_signature(signature.data(), signature.size()); @@ -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..5adb08cc96e --- /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, TWCurveStarkex); +} + +PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey) { + return PrivateKey(parse_hex(ImmutableX::grindKey(ethPrivKey.bytes), true), TWCurveStarkex); +} + +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 a76743a4250..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 @@ -30,7 +28,7 @@ class Address: public Bech32Address { } /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA3K, publicKey) { + Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherKeccak256, publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("address may only be an extended SECP256k1 public key"); } 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 8695c74e08e..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeIoTeX}; } - 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..9c139888d1c 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); @@ -19,17 +16,17 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::sign() const { - auto key = PrivateKey(input.privatekey()); - return key.sign(hash(), TWCurveSECP256k1); + auto key = PrivateKey(input.privatekey(), TWCurveSECP256k1); + return key.sign(hash()); } Proto::SigningOutput Signer::build() const { auto signedAction = Proto::Action(); signedAction.mutable_core()->MergeFrom(action); - auto key = PrivateKey(input.privatekey()); + auto key = PrivateKey(input.privatekey(), TWCurveSECP256k1); auto pk = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended).bytes; signedAction.set_senderpubkey(pk.data(), pk.size()); - auto sig = key.sign(hash(), TWCurveSECP256k1); + auto sig = key.sign(hash()); signedAction.set_signature(sig.data(), sig.size()); auto output = Proto::SigningOutput(); @@ -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 e59ef74afd7..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" @@ -84,7 +82,7 @@ Data stakingTransfer(uint64_t index, const Data& voterAddress, const Data& paylo Data candidateRegister(const Data& name, const Data& operatorAddress, const Data& rewardAddress, const Data& amount, uint32_t duration, bool autoStake, const Data& ownerAddress, const Data& payload) { - auto cbi = new IoTeX::Proto::Staking_CandidateBasicInfo(); + auto* cbi = new IoTeX::Proto::Staking_CandidateBasicInfo(); cbi->set_name(FromData(name).c_str()); cbi->set_operatoraddress(FromData(operatorAddress).c_str()); cbi->set_rewardaddress(FromData(rewardAddress).c_str()); 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 new file mode 100644 index 00000000000..d7387c3db2d --- /dev/null +++ b/src/KeyPair.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include "PrivateKey.h" +#include "PublicKey.h" + +namespace TW { + +typedef std::tuple KeyPair; + +} // namespace diff --git a/src/Keystore/AESParameters.cpp b/src/Keystore/AESParameters.cpp index 146e1a12727..f2156144a6c 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, .iv{}}}, + {TWStoredKeyEncryptionAes128Cbc, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Cbc, .mCipherEncryption = TWStoredKeyEncryptionAes128Cbc, .iv{}}}, + {TWStoredKeyEncryptionAes192Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A192, .mCipher = Keystore::gAes192Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes192Ctr, .iv{}}}, + {TWStoredKeyEncryptionAes256Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A256, .mCipher = Keystore::gAes256Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes256Ctr, .iv{}}} +}; +} // 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 c27b7dbfca7..c97b8eb66d1 100644 --- a/src/Keystore/Account.cpp +++ b/src/Keystore/Account.cpp @@ -1,29 +1,33 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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 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 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) { + if (json.find(CodingKeys::derivation) != json.end()) { + derivation = TWDerivation(json[CodingKeys::derivation].get()); + } + if (json[CodingKeys::derivationPath].is_object()) { const auto indices = json[CodingKeys::derivationPath][CodingKeys::indices]; for (auto& indexJSON : indices) { @@ -51,15 +55,28 @@ Account::Account(const nlohmann::json& json) { json[CodingKeys::extendedPublicKey].is_string()) { extendedPublicKey = json[CodingKeys::extendedPublicKey].get(); } + + if (json.count(CodingKeys::publicKey) > 0 && + json[CodingKeys::publicKey].is_string()) { + publicKey = json[CodingKeys::publicKey].get(); + } } nlohmann::json Account::json() const { nlohmann::json j; j[CodingKeys::address] = address; + if (derivation != TWDerivationDefault) { + j[CodingKeys::derivation] = static_cast(derivation); + } j[CodingKeys::derivationPath] = derivationPath.string(); j[CodingKeys::coin] = coin; if (!extendedPublicKey.empty()) { j[CodingKeys::extendedPublicKey] = extendedPublicKey; } + if (!publicKey.empty()) { + j[CodingKeys::publicKey] = publicKey; + } return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/Account.h b/src/Keystore/Account.h index fe5c137fe9a..22a55c5d48d 100644 --- a/src/Keystore/Account.h +++ b/src/Keystore/Account.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 "../DerivationPath.h" +#include #include #include @@ -16,24 +15,32 @@ namespace TW::Keystore { /// Account for a particular coin within a wallet. class Account { public: + /// Coin this account is for + TWCoinType coin; + /// Account public address std::string address; - /// Account derivation path, only relevant for HD wallets. + /// Account derivation. May be missing or unreliable in Json stored format. + TWDerivation derivation = TWDerivationDefault; + + /// Account derivation path, only relevant for HD wallets; info only. DerivationPath derivationPath; - /// Extended public key. - std::string extendedPublicKey; + /// Account public key in hex format. + std::string publicKey; - /// Coin this account is for. - TWCoinType coin; + /// Extended public key, info only. + std::string extendedPublicKey; Account() = default; - Account(std::string address, TWCoinType coin, DerivationPath derivationPath, std::string extendedPublicKey = "") - : address(std::move(address)) + Account(std::string address, TWCoinType coin, TWDerivation derivation, DerivationPath derivationPath, std::string publicKey, std::string extendedPublicKey) + : coin(coin) + , address(std::move(address)) + , derivation(derivation) , derivationPath(std::move(derivationPath)) - , extendedPublicKey(std::move(extendedPublicKey)) - , coin(coin) {} + , publicKey(std::move(publicKey)) + , extendedPublicKey(std::move(extendedPublicKey)) {} /// Initializes `Account` with a JSON object. Account(const nlohmann::json& json); diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 271c9322eb0..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) { @@ -28,71 +24,124 @@ static Data computeMAC(Iter begin, Iter end, const Data& key) { return Hash::keccak256(data); } -EncryptionParameters::EncryptionParameters(const Data& password, const Data& data) : mac() { - auto scryptParams = boost::get(kdfParams); +// ----------------- +// Encoding/Decoding +// ----------------- + +namespace CodingKeys { +static const auto encrypted = "ciphertext"; +static const auto cipher = "cipher"; +static const auto cipherParams = "cipherparams"; +static const auto kdf = "kdf"; +static const auto kdfParams = "kdfparams"; +static const auto mac = "mac"; +} // namespace CodingKeys + +EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { + auto cipher = json[CodingKeys::cipher].get(); + cipherParams = AESParameters::AESParametersFromJson(json[CodingKeys::cipherParams], cipher); + + auto kdf = json[CodingKeys::kdf].get(); + if (kdf == "scrypt") { + kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); + } else if (kdf == "pbkdf2") { + kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); + } +} + +nlohmann::json EncryptionParameters::json() const { + nlohmann::json j; + j[CodingKeys::cipher] = cipher(); + j[CodingKeys::cipherParams] = cipherParams.json(); + + if (auto* scryptParams = std::get_if(&kdfParams); scryptParams) { + j[CodingKeys::kdf] = "scrypt"; + j[CodingKeys::kdfParams] = scryptParams->json(); + } else if (auto* pbkdf2Params = std::get_if(&kdfParams); pbkdf2Params) { + j[CodingKeys::kdf] = "pbkdf2"; + 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 = 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 = 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); } } -EncryptionParameters::~EncryptionParameters() { +EncryptedPayload::~EncryptedPayload() { std::fill(encrypted.begin(), encrypted.end(), 0); + std::fill(_mac.begin(), _mac.end(), 0); } -Data EncryptionParameters::decrypt(const Data& password) const { +Data EncryptedPayload::decrypt(const Data& password) const { auto derivedKey = Data(); auto mac = Data(); - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(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 (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(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 = cipherParams.iv; - if (cipher == "aes-128-ctr") { + Data iv = params.cipherParams.iv; + const auto encryption = params.cipherParams.mCipherEncryption; + if (encryption == TWStoredKeyEncryptionAes128Ctr || encryption == TWStoredKeyEncryptionAes256Ctr) { aes_encrypt_ctx ctx; - auto 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 (cipher == "aes-128-cbc") { + } else if (encryption == TWStoredKeyEncryptionAes128Cbc) { aes_decrypt_ctx ctx; - auto 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; @@ -101,50 +150,17 @@ Data EncryptionParameters::decrypt(const Data& password) const { return decrypted; } -// ----------------- -// Encoding/Decoding -// ----------------- - -namespace CodingKeys { -static const auto encrypted = "ciphertext"; -static const auto cipher = "cipher"; -static const auto cipherParams = "cipherparams"; -static const auto kdf = "kdf"; -static const auto kdfParams = "kdfparams"; -static const auto mac = "mac"; -} // namespace CodingKeys - -EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { +EncryptedPayload::EncryptedPayload(const nlohmann::json& json) { + params = EncryptionParameters(json); encrypted = parse_hex(json[CodingKeys::encrypted].get()); - cipher = json[CodingKeys::cipher].get(); - cipherParams = AESParameters(json[CodingKeys::cipherParams]); - mac = parse_hex(json[CodingKeys::mac].get()); - - auto kdf = json[CodingKeys::kdf].get(); - if (kdf == "scrypt") { - kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); - } else if (kdf == "pbkdf2") { - kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); - } + _mac = parse_hex(json[CodingKeys::mac].get()); } -nlohmann::json EncryptionParameters::json() const { - nlohmann::json j; +nlohmann::json EncryptedPayload::json() const { + nlohmann::json j = params.json(); j[CodingKeys::encrypted] = hex(encrypted); - j[CodingKeys::cipher] = cipher; - j[CodingKeys::cipherParams] = cipherParams.json(); - j[CodingKeys::mac] = hex(mac); - - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(kdfParams); - j[CodingKeys::kdf] = "scrypt"; - j[CodingKeys::kdfParams] = scryptParams.json(); - } else if (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(kdfParams); - j[CodingKeys::kdf] = "pbkdf2"; - j[CodingKeys::kdfParams] = pbkdf2Params.json(); - - } - + j[CodingKeys::mac] = hex(_mac); return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index e1e68933977..d12de1b90a2 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -1,22 +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. #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, enum TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { + switch (preset) { + case TWStoredKeyEncryptionLevelMinimal: + return { AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::minimal() }; + case TWStoredKeyEncryptionLevelWeak: + case TWStoredKeyEncryptionLevelDefault: + default: + return { AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::weak() }; + case TWStoredKeyEncryptionLevelStandard: + return { AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::standard() }; + } + } + + 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. + std::variant kdfParams = ScryptParameters(); + + EncryptionParameters() = default; + + /// Initializes with standard values. + EncryptionParameters(AESParameters cipherParams, std::variant kdfParams) + : cipherParams(std::move(cipherParams)), kdfParams(std::move(kdfParams)) { + } + + /// Initializes with a JSON object. + explicit EncryptionParameters(const nlohmann::json& json); + + /// Saves `this` as a JSON object. + nlohmann::json json() const; + + EncryptionParameters(const EncryptionParameters& other) = default; + EncryptionParameters(EncryptionParameters&& other) = default; + EncryptionParameters& operator=(const EncryptionParameters& other) = default; + EncryptionParameters& operator=(EncryptionParameters&& other) = default; + + virtual ~EncryptionParameters() = default; +}; + /// Errors thrown when decrypting a key. enum class DecryptionError { unsupportedKDF, @@ -27,37 +77,31 @@ enum class DecryptionError { invalidPassword, }; -struct EncryptionParameters { +/// An encrypted payload data +struct EncryptedPayload { +public: + EncryptionParameters params; + /// Encrypted data. Data encrypted; - /// Cipher algorithm. - std::string cipher = "aes-128-ctr"; - - /// Cipher parameters. - AESParameters cipherParams = AESParameters(); - - /// Key derivation function parameters. - boost::variant kdfParams = ScryptParameters(); - /// Message authentication code. - Data mac; + Data _mac; - EncryptionParameters() = default; + EncryptedPayload() = default; - /// Initializes `EncryptionParameters` with standard values. - EncryptionParameters(const Data& encrypted, AESParameters cipherParams, boost::variant kdfParams, const Data& mac) - : encrypted(std::move(encrypted)) - , cipherParams(std::move(cipherParams)) - , kdfParams(std::move(kdfParams)) - , mac(std::move(mac)) {} + /// Initializes with standard values. + EncryptedPayload(EncryptionParameters params, Data encrypted, Data mac) + : params(std::move(params)) + , encrypted(std::move(encrypted)) + , _mac(std::move(mac)) {} - /// Initializes `EncryptionParameters` by encrypting data with a password + /// Initializes by encrypting data with a password /// using standard values. - EncryptionParameters(const Data& password, const Data& data); + EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params); - /// Initializes `EncryptionParameters` with a JSON object. - EncryptionParameters(const nlohmann::json& json); + /// Initializes with a JSON object. + explicit EncryptedPayload(const nlohmann::json& json); /// Decrypts the payload with the given password. Data decrypt(const Data& password) const; @@ -65,12 +109,12 @@ struct EncryptionParameters { /// Saves `this` as a JSON object. nlohmann::json json() const; - EncryptionParameters(const EncryptionParameters& other) = default; - EncryptionParameters(EncryptionParameters&& other) = default; - EncryptionParameters& operator=(const EncryptionParameters& other) = default; - EncryptionParameters& operator=(EncryptionParameters&& other) = default; + EncryptedPayload(const EncryptedPayload& other) = default; + EncryptedPayload(EncryptedPayload&& other) = default; + EncryptedPayload& operator=(const EncryptedPayload& other) = default; + EncryptedPayload& operator=(EncryptedPayload&& other) = default; - virtual ~EncryptionParameters(); + virtual ~EncryptedPayload(); }; } // namespace TW::Keystore 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 16ccd489c4c..4ec194dd5e8 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,16 +8,39 @@ #include using namespace TW; -using namespace TW::Keystore; -ScryptParameters::ScryptParameters() : salt(32) { +namespace TW::Keystore { + +namespace internal { + +Data randomSalt() { + Data salt(32); random_buffer(salt.data(), salt.size()); + return salt; +} + +} // namespace internal + +ScryptParameters ScryptParameters::minimal() { + return { internal::randomSalt(), minimalN, defaultR, minimalP, defaultDesiredKeyLength }; +} + +ScryptParameters ScryptParameters::weak() { + return { internal::randomSalt(), weakN, defaultR, weakP, defaultDesiredKeyLength }; +} + +ScryptParameters ScryptParameters::standard() { + return { internal::randomSalt(), standardN, defaultR, standardP, defaultDesiredKeyLength }; +} + +ScryptParameters::ScryptParameters() + : salt(internal::randomSalt()) { } #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)) { @@ -39,32 +60,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 10e7c019bd6..8c8a3b3de99 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 @@ -23,21 +21,18 @@ enum class ScryptValidationError { /// Scrypt function parameters. struct ScryptParameters { - /// The N parameter of Scrypt encryption algorithm, using 256MB memory and + /// The N and P parameters of Scrypt encryption algorithm, using 256MB memory and /// taking approximately 1s CPU time on a modern processor. static const uint32_t standardN = 1 << 18; - - /// The P parameter of Scrypt encryption algorithm, using 256MB memory and - /// taking approximately 1s CPU time on a modern processor. static const uint32_t standardP = 1; - /// The N parameter of Scrypt encryption algorithm, using 4MB memory and - /// taking approximately 100ms CPU time on a modern processor. - static const uint32_t lightN = 1 << 12; + static const uint32_t weakN = 1 << 14; + static const uint32_t weakP = 4; - /// The P parameter of Scrypt encryption algorithm, using 4MB memory and + /// The N and P parameters of Scrypt encryption algorithm, using 4MB memory and /// taking approximately 100ms CPU time on a modern processor. - static const uint32_t lightP = 6; + static const uint32_t minimalN = 1 << 12; + static const uint32_t minimalP = 6; /// Default `R` parameter of Scrypt encryption algorithm. static const uint32_t defaultR = 8; @@ -52,21 +47,28 @@ struct ScryptParameters { std::size_t desiredKeyLength = defaultDesiredKeyLength; /// CPU/Memory cost factor. - uint32_t n = lightN; + uint32_t n = minimalN; /// Parallelization factor (1..232-1 * hLen/MFlen). - uint32_t p = lightP; + uint32_t p = minimalP; /// Block size factor. uint32_t r = defaultR; + /// Generates Scrypt encryption parameters with the minimal sufficient level (4096), and with a random salt. + static ScryptParameters minimal(); + /// Generates Scrypt encryption parameters with the weak sufficient level (16k), and with a random salt. + static ScryptParameters weak(); + /// Generates Scrypt encryption parameters with the standard sufficient level (262k), and with a random salt. + static ScryptParameters standard(); + /// Initializes with default scrypt parameters and a random salt. ScryptParameters(); /// Initializes `ScryptParameters` with all values. /// /// @throws ScryptValidationError if the parameters are invalid. - ScryptParameters(const Data& salt, uint32_t n, uint32_t r, uint32_t p, std::size_t desiredKeyLength) + ScryptParameters(Data salt, uint32_t n, uint32_t r, uint32_t p, std::size_t desiredKeyLength) : salt(std::move(salt)), desiredKeyLength(desiredKeyLength), n(n), p(p), r(r) { auto error = validate(); if (error) { @@ -80,7 +82,7 @@ struct ScryptParameters { std::optional validate() const; /// Initializes `ScryptParameters` with a JSON object. - ScryptParameters(const nlohmann::json& json); + explicit ScryptParameters(const nlohmann::json& json); /// Saves `this` as a JSON object. nlohmann::json json() const; diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 5916d54df3e..83dfdc1aafb 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -1,182 +1,354 @@ -// 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" #include "Coin.h" +#include "HexCoding.h" #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) { +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); + 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) { - const auto wallet = TW::HDWallet(128, ""); - const auto& mnemonic = wallet.mnemonic; +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); + 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); - - const auto wallet = HDWallet(mnemonic, ""); - const auto derivationPath = TW::derivationPath(coin); - const auto address = TW::deriveAddress(coin, wallet.getKey(coin, derivationPath)); - const auto extendedKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TW::xpubVersion(coin)); - key.accounts.emplace_back(address, coin, derivationPath, extendedKey); - +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); - 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, + TWDerivation derivation +) { 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); - - const auto derivationPath = TW::derivationPath(coin); - const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); - key.accounts.emplace_back(address, coin, derivationPath); + StoredKey key = createWithPrivateKey(name, password, privateKeyData, encryption); + const auto derivationPath = TW::derivationPath(coin, derivation); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = PrivateKey(privateKeyData, TWCoinTypeCurve(coin)).getPublicKey(pubKeyType); + const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData), derivation); + key.accounts.emplace_back(address, coin, derivation, derivationPath, hex(pubKey.bytes), ""); + return key; +} +StoredKey StoredKey::createWithEncodedPrivateKeyAddDefaultAddress( + const std::string& name, + const Data& password, + TWCoinType coin, + const std::string& encodedPrivateKey, + TWStoredKeyEncryption encryption, + TWDerivation derivation +) { + const auto curve = TW::curve(coin); + const auto privateKey = TW::decodePrivateKey(coin, encodedPrivateKey); + StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKey.bytes, TWStoredKeyEncryptionLevelDefault, encryption, encodedPrivateKey); + const auto derivationPath = TW::derivationPath(coin, derivation); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = privateKey.getPublicKey(pubKeyType); + const auto address = TW::deriveAddress(coin, privateKey, derivation); + key.accounts.emplace_back(address, coin, derivation, derivationPath, hex(pubKey.bytes), ""); return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data) - : type(type), id(), name(std::move(name)), payload(password, data), accounts() { - boost::uuids::random_generator gen; - id = boost::lexical_cast(gen()); +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption, const std::optional& encodedStr) + : type(type), id(), name(std::move(name)), accounts() { + const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption); + payload = EncryptedPayload(password, data, encryptionParams); + if (encodedStr) { + const auto bytes = reinterpret_cast(encodedStr->c_str()); + const auto encodedData = Data(bytes, bytes + encodedStr->size()); + encodedPayload = EncryptedPayload(password, encodedData, encryptionParams); + } + 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::optional StoredKey::account(TWCoinType coin) const { +std::vector StoredKey::getAccounts(TWCoinType coin) const { + std::vector result; for (auto& account : accounts) { if (account.coin == coin) { + result.push_back(account); + } + } + return result; +} + +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); + const auto defaultAccount = getAccount(coin, address); + if (defaultAccount.has_value()) { + return defaultAccount; + } + } + // no wallet or not found, rely on derivation=0 condition + const auto coinAccounts = getAccounts(coin); + for (auto& account : coinAccounts) { + if (account.derivation == TWDerivationDefault) { return account; } } return std::nullopt; } -std::optional StoredKey::account(TWCoinType coin, const HDWallet* wallet) { - if (wallet == nullptr) { - return account(coin); +std::optional StoredKey::getDefaultAccountOrAny(TWCoinType coin, const HDWallet<>* wallet) const { + const auto defaultAccount = getDefaultAccount(coin, wallet); + if (defaultAccount.has_value()) { + return defaultAccount; } - assert(wallet != nullptr); + // return any + const auto coinAccounts = getAccounts(coin); + if (coinAccounts.size() > 0) { + return coinAccounts[0]; + } + return std::nullopt; +} +std::optional StoredKey::getAccount(TWCoinType coin, const std::string& address) const { for (auto& account : accounts) { - if (account.coin == coin) { - if (account.address.empty()) { - account.address = wallet->deriveAddress(coin); - } + if (account.coin == coin && account.address == address) { return account; } } + return std::nullopt; +} + +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 { + if (account.address.empty() && wallet != nullptr) { + account.address = wallet->deriveAddress(account.coin, account.derivation); + } + if (account.publicKey.empty() && wallet != nullptr) { + const auto pubKeyType = TW::publicKeyType(account.coin); + const auto pubKey = wallet->getKey(account.coin, account.derivationPath).getPublicKey(pubKeyType); + account.publicKey = hex(pubKey.bytes); + } + return account; +} + +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(); + return fillAddressIfMissing(accountLval, wallet); + } + // not found, add + if (wallet == nullptr) { + return std::nullopt; + } + assert(wallet != nullptr); const auto derivationPath = TW::derivationPath(coin); const auto address = wallet->deriveAddress(coin); - const auto version = TW::xpubVersion(coin); const auto extendedPublicKey = wallet->getExtendedPublicKey(derivationPath.purpose(), coin, version); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = wallet->getKey(coin, derivationPath).getPublicKey(pubKeyType); - accounts.emplace_back(address, coin, derivationPath, extendedPublicKey); + addAccount(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), extendedPublicKey); return accounts.back(); } -void StoredKey::addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) { - accounts.emplace_back(address, coin, derivationPath, extetndedPublicKey); +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(); + return fillAddressIfMissing(accountLval, &wallet); + } + // not found, add + const auto derivationPath = TW::derivationPath(coin, derivation); + const auto address = wallet.deriveAddress(coin, derivation); + const auto version = TW::xpubVersionDerivation(coin, derivation); + const auto extendedPublicKey = wallet.getExtendedPublicKey(derivationPath.purpose(), coin, version); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = wallet.getKey(coin, derivationPath).getPublicKey(pubKeyType); + + addAccount(address, coin, derivation, derivationPath, hex(pubKey.bytes), extendedPublicKey); + return accounts.back(); +} + +std::optional StoredKey::account(TWCoinType coin) const { + return getDefaultAccountOrAny(coin, nullptr); +} + +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(); + return fillAddressIfMissing(accountLval, &wallet); + } + return std::nullopt; +} + +void StoredKey::addAccount( + const std::string& address, + TWCoinType coin, + TWDerivation derivation, + const DerivationPath& derivationPath, + const std::string& publicKey, + const std::string& extendedPublicKey) { + if (getAccount(coin, address).has_value()) { + // address already present + return; + } + accounts.emplace_back(address, coin, derivation, derivationPath, publicKey, extendedPublicKey); } 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()); +} + +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()); } const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { - switch (type) { - case StoredKeyType::mnemonicPhrase: { + return privateKey(coin, TWDerivationDefault, password); +} + +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), TWCoinTypeCurve(coin)); } 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() && TW::validateAddress(account.coin, account.address)) { - continue; - } - const auto& derivationPath = account.derivationPath; - const auto key = wallet.getKey(account.coin, derivationPath); - account.address = TW::deriveAddress(account.coin, key); - } + 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() && TW::validateAddress(account.coin, account.address)) { - continue; - } - account.address = TW::deriveAddress(account.coin, key); - } + 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; +} + +const std::string StoredKey::decryptPrivateKeyEncoded(const Data& password) const { + if (encodedPayload) { + auto data = encodedPayload->decrypt(password); + return std::string(reinterpret_cast(data.data()), data.size()); + } else { + auto data = payload.decrypt(password); + return TW::hex(data); + } +} // ----------------- // Encoding/Decoding @@ -188,93 +360,106 @@ 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 encodedCrypto = "encodedCrypto"; +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 = EncryptionParameters(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 = EncryptionParameters(json[UppercaseCodingKeys::crypto]); + payload = EncryptedPayload(json[UppercaseCodingKeys::crypto]); } else { 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::encodedCrypto) != 0) { + encodedPayload = EncryptedPayload(json[CodingKeys::SK::encodedCrypto]); + } else { + encodedPayload = std::nullopt; + } + + 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(); - accounts.emplace_back(address, coin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0)); + 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(); + if (encodedPayload) { + j[CodingKeys::SK::encodedCrypto] = encodedPayload->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; } @@ -296,3 +481,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 4a019c5b5b3..6d9ec35cc8d 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -1,21 +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. #pragma once #include "Account.h" #include "EncryptionParameters.h" -#include "../Data.h" +#include "Data.h" #include "../HDWallet.h" #include +#include +#include #include #include #include +#include namespace TW::Keystore { @@ -36,30 +37,51 @@ class StoredKey { std::string name; /// Encrypted payload. - EncryptionParameters payload; + EncryptedPayload payload; - /// Active accounts. + /// Optional encoded payload. Used when an encoded private key is imported. + std::optional encodedPayload; + + /// Active accounts. Address should be unique. std::vector accounts; /// 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); + 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); + 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 vald private key - static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData); + /// @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, 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 vald private key - static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData); + /// @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, + TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr, + TWDerivation derivation = TWDerivationDefault + ); + + /// Create a new StoredKey, with the given name and encoded private key, and also add the default address for the given coin.. + /// @throws std::invalid_argument if encodedPrivateKey is not a valid private key + static StoredKey createWithEncodedPrivateKeyAddDefaultAddress( + const std::string& name, + const Data& password, + TWCoinType coin, + const std::string& encodedPrivateKey, + TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr, + TWDerivation derivation = TWDerivationDefault + ); /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -67,37 +89,68 @@ 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); - /// Returns the account for a specific coin, creating it if necessary and - /// the provided wallet is not `nullptr`. - 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); /// 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; - /// Add an account - void addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey); - - /// Remove the account for a specific coin + /// Returns the account for a specific coin and derivation, if it exists. + 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). + void addAccount( + const std::string& address, + TWCoinType coin, + TWDerivation derivation, + const DerivationPath& derivationPath, + const std::string& publicKey, + const std::string& extendedPublicKey + ); + + /// Remove the account(s) for a specific coin void removeAccount(TWCoinType coin); - - /// Returns the private key for a specific coin, creating an account if necessary. + + /// Remove the account for a specific coin with the given derivation. + void removeAccount(TWCoinType coin, TWDerivation derivation); + + /// Remove the account for a specific coin with the given derivation path. + void removeAccount(TWCoinType coin, DerivationPath derivationPath); + + /// 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 + /// `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 descrypted 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. @@ -106,20 +159,60 @@ class StoredKey { /// Saves `this` as a JSON object. nlohmann::json json() const; - /// Fills in all empty and invalid addresses. + /// Fills in all empty or invalid addresses and public keys. /// /// Use to fix legacy wallets with invalid address data. This method needs /// 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); + + /// Decrypts the encoded private key. + /// + /// \returns the decoded private key. + /// \throws DecryptionError + const std::string decryptPrivateKeyEncoded(const Data& password) const; + private: /// Default constructor, private StoredKey() : type(StoredKeyType::mnemonicPhrase) {} /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. - /// This contstructor will encrypt the provided data with default encryption + /// This constructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data); + StoredKey( + StoredKeyType type, + std::string name, + const Data& password, + const Data& data, + TWStoredKeyEncryptionLevel encryptionLevel, + TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr, + const std::optional& encodedStr = std::nullopt + ); + + /// 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; + + /// 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; + + /// 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; + + /// Re-derive account address if missing + 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/Komodo/Entry.h b/src/Komodo/Entry.h new file mode 100644 index 00000000000..bca4e702fb0 --- /dev/null +++ b/src/Komodo/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Zcash/Entry.h" + +namespace TW::Komodo { + +/// Komodo entry dispatcher. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Zcash::Entry { +}; + +} // namespace TW::Komodo diff --git a/src/Kusama/Address.h b/src/Kusama/Address.h deleted file mode 100644 index 40163a1cb3f..00000000000 --- a/src/Kusama/Address.h +++ /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. - -#pragma once - -#include "../Data.h" -#include "../PublicKey.h" -#include "../SS58Address.h" -#include - -#include - -namespace TW::Kusama { - -class Address: public SS58Address { - public: - /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string) { return SS58Address::isValid(string, TWSS58AddressTypeKusama); } - - /// Initializes a Kusama address with a string representation. - Address(const std::string& string): SS58Address(string, TWSS58AddressTypeKusama) {} - - /// Initializes a Kusama address with a public key. - Address(const PublicKey& publicKey): SS58Address(publicKey, TWSS58AddressTypeKusama) {} -}; -} // namespace TW::Kusama - diff --git a/src/Kusama/Entry.cpp b/src/Kusama/Entry.cpp deleted file mode 100644 index fa1bb8671dc..00000000000 --- a/src/Kusama/Entry.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 "Entry.h" - -#include "Address.h" -#include "Polkadot/Signer.h" - -using namespace TW::Kusama; -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(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} diff --git a/src/Kusama/Entry.h b/src/Kusama/Entry.h index d766dd678a0..b1e819b039e 100644 --- a/src/Kusama/Entry.h +++ b/src/Kusama/Entry.h @@ -1,23 +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. #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" 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 { -public: - virtual const std::vector coinTypes() const { return {TWCoinTypeKusama}; } - 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; +class Entry : public Rust::RustCoinEntry { }; } // 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 e743d12c31a..a6e9a2227cd 100644 --- a/src/Mnemonic.cpp +++ b/src/Mnemonic.cpp @@ -1,18 +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. #include "Mnemonic.h" #include #include -#include +#include #include #include #include +#include namespace TW { @@ -26,13 +25,16 @@ 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 (strncmp(*w, wordC, word.length()) == 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) { @@ -52,7 +54,7 @@ std::string Mnemonic::suggest(const std::string& prefix) { if ((*word)[0] == prefixLo[0]) { if (strncmp(*word, prefixLoC, prefixLo.length()) == 0) { // we have a match - result.push_back(*word); + result.emplace_back(*word); if (result.size() >= SuggestMaxCount) { break; // enough results } @@ -71,4 +73,4 @@ std::string Mnemonic::suggest(const std::string& prefix) { return resultString; } -} +} // namespace TW diff --git a/src/Mnemonic.h b/src/Mnemonic.h index 31956237d98..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 @@ -10,14 +8,19 @@ namespace TW { -/// BIP39 Mnemonic Sentence handling. +/// BIP39 Mnemonic recovery phrase handling. class Mnemonic { public: - /// Determines whether a mnemonic phrase is valid. + static constexpr int MaxWords = 24; + static constexpr int MinWords = 12; + static constexpr int BitsPerWord = 11; // each word encodes this many bits (there are 2^11=2048 different words) + +public: + /// Determines whether a BIP39 English mnemonic phrase is valid. // E.g. for a valid mnemonic: "credit expect life fade cover suit response wash pear what skull force" static bool isValid(const std::string& mnemonic); - /// Determines whether word is a valid menemonic word. + /// Determines whether word is a valid BIP39 English menemonic word. static bool isValidWord(const std::string& word); /// Return BIP39 English words that match the given prefix. @@ -39,8 +42,3 @@ class Mnemonic { }; } // namespace TW - -/// Wrapper for C interface. -struct TWMnemonic { - TW::Mnemonic impl; -}; 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..35f2990330f --- /dev/null +++ b/src/MultiversX/Serialization.cpp @@ -0,0 +1,89 @@ +// 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}, + {"relayer", 15}}; + +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); + } + + if (!transaction.relayer.empty()) { + payload["relayer"] = json(transaction.relayer); + } + + 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..16abbd2c835 --- /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(), TWCurveED25519); + auto signableAsData = buildUnsignedTxBytes(input); + auto signature = privateKey.sign(signableAsData); + + 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..819cf49b11b --- /dev/null +++ b/src/MultiversX/Transaction.cpp @@ -0,0 +1,21 @@ +// 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(""), relayer(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(TransactionOptions::Default) { +} + +bool Transaction::hasGuardian() const { + return !guardian.empty(); +} + +bool Transaction::hasRelayer() const { + return !relayer.empty(); +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Transaction.h b/src/MultiversX/Transaction.h new file mode 100644 index 00000000000..24fce7d9865 --- /dev/null +++ b/src/MultiversX/Transaction.h @@ -0,0 +1,44 @@ +// 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 relayer; + std::string value; + std::string data; + uint64_t gasPrice; + uint64_t gasLimit; + std::string chainID; + uint32_t version; + TransactionOptions options; + + Transaction(); + + bool hasGuardian() const; + bool hasRelayer() const; +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactory.cpp b/src/MultiversX/TransactionFactory.cpp new file mode 100644 index 00000000000..b11f0b3de50 --- /dev/null +++ b/src/MultiversX/TransactionFactory.cpp @@ -0,0 +1,193 @@ +// 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.relayer = action.accounts().relayer(); + 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.relayer = transfer.accounts().relayer(); + 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.hasRelayer()); + 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.relayer = transfer.accounts().relayer(); + 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.hasRelayer()); + 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.relayer = transfer.accounts().relayer(); + 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.hasRelayer()); + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + + return transaction; +} + +uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian, bool hasRelayer) { + uint64_t dataMovementGasLimit = this->config.getMinGasLimit() + this->config.getGasPerDataByte() * dataLength; + uint64_t gasLimit = dataMovementGasLimit + executionGasLimit; + + if (hasGuardian) { + gasLimit += this->config.getExtraGasLimitForGuardedTransaction(); + } + + if (hasRelayer) { + gasLimit += this->config.getExtraGasLimitForRelayedTransaction(); + } + + 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..0f2df9edae1 --- /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, bool hasRelayer); + 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..8717f3e4b91 --- /dev/null +++ b/src/MultiversX/TransactionFactoryConfig.cpp @@ -0,0 +1,119 @@ +// 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->extraGasLimitForGuardedTransaction; +} + +void TransactionFactoryConfig::setExtraGasLimitForGuardedTransaction(uint32_t value) { + this->extraGasLimitForGuardedTransaction = value; +} + +uint32_t TransactionFactoryConfig::getExtraGasLimitForRelayedTransaction() const { + return this->extraGasLimitForRelayedTransaction; +} + +void TransactionFactoryConfig::setExtraGasLimitForRelayedTransaction(uint32_t value) { + this->extraGasLimitForRelayedTransaction = 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.setExtraGasLimitForRelayedTransaction(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..d7d7cb47d92 --- /dev/null +++ b/src/MultiversX/TransactionFactoryConfig.h @@ -0,0 +1,75 @@ +// 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; + uint32_t extraGasLimitForRelayedTransaction; + 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); + + uint32_t getExtraGasLimitForRelayedTransaction() const; + void setExtraGasLimitForRelayedTransaction(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 6fc0ccbd793..9be0149177b 100644 --- a/src/NEAR/Entry.cpp +++ b/src/NEAR/Entry.cpp @@ -1,31 +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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) 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([[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 46b568f4214..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 const std::vector coinTypes() const { return {TWCoinTypeNEAR}; } - 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 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 4b6e24826de..f1aac366a41 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)); } @@ -49,21 +52,119 @@ static void writeTransfer(Data& data, const Proto::Transfer& transfer) { writeU128(data, transfer.deposit()); } +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; - 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()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto public_key = key.getPublicKey(TWPublicKeyTypeED25519); auto public_key_proto = Proto::PublicKey(); public_key_proto.set_data(public_key.bytes.data(), public_key.bytes.size()); @@ -79,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 d31da57de03..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); - -} // namespace +Data transactionDataWithPublicKey(const Proto::SigningInput& input); +} // namespace TW::NEAR diff --git a/src/NEAR/Signer.cpp b/src/NEAR/Signer.cpp index 7434d646ea3..f5ae073d484 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,16 +8,40 @@ #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); - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto hash = Hash::sha256(transaction); - auto signature = key.sign(hash, TWCurveED25519); + auto signature = key.sign(hash); auto output = Proto::SigningOutput(); auto signedTransaction = signedTransactionData(transaction, signature); output.set_signed_transaction(signedTransaction.data(), signedTransaction.size()); + 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 1dc053b2da5..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 +} // 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 f51b0e07335..4fc03845630 100644 --- a/src/NEO/Entry.cpp +++ b/src/NEO/Entry.cpp @@ -1,29 +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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) 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([[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 ff632623175..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 const std::vector coinTypes() const { return {TWCoinTypeNEO}; } - 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 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 f7444e6af3c..97580a6c1c4 100644 --- a/src/NEO/ReadData.cpp +++ b/src/NEO/ReadData.cpp @@ -1,30 +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) { @@ -43,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 7e583aa75f5..8c661c29e90 100644 --- a/src/NEO/ReadData.h +++ b/src/NEO/ReadData.h @@ -1,32 +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 = INT_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 9ee61d5aa6e..c3f210e08d2 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); @@ -47,7 +46,7 @@ void Signer::sign(Transaction& tx) const { } Data Signer::sign(const Data& data) const { - auto signature = getPrivateKey().sign(TW::Hash::sha256(data), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(TW::Hash::sha256(data)); signature.pop_back(); return signature; } @@ -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; @@ -80,10 +83,10 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { int existGASTransfer = -1; for (int i = 0; i < input.outputs_size(); i++) { - auto outputPlan = plan.add_outputs(); + 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; @@ -114,7 +126,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { feeNeed = true; } if (feeNeed && existGASTransfer < 0) { - auto outputPlan = plan.add_outputs(); + auto* outputPlan = plan.add_outputs(); existGASTransfer = plan.outputs_size() - 1; if (available.find(input.gas_asset_id()) == available.end() || @@ -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()), TWCurveNIST256p1)); 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 6c352b36894..0c8d7431da8 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,20 @@ 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"); } resp->deserialize(data, initial_pos); return resp; @@ -49,27 +53,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 (int i = 0; i < witnesses.size(); i++) - append(resp, witnesses[i].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 +82,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 +92,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 283acb94797..fe29d702463 100644 --- a/src/NULS/Entry.h +++ b/src/NULS/Entry.h @@ -1,23 +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 const std::vector coinTypes() const { return {TWCoinTypeNULS}; } - 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 bca85e48535..51b44385159 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"); - } - - Proto::TransactionCoinFrom& 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); - - Proto::TransactionCoinTo& 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 priv = PrivateKey(privKey, TWCurveSECP256k1); 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, TWCurveSECP256k1); + 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 8d21d476098..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,12 +19,12 @@ bool Address::isValid(const std::string& address) { valid = nano_validate_address( kPrefixNano.c_str(), kPrefixNano.length(), address.c_str(), address.length(), - NULL); + nullptr); if (!valid) { valid = nano_validate_address( kPrefixXrb.c_str(), kPrefixXrb.length(), address.c_str(), address.length(), - NULL); + nullptr); } return valid; @@ -66,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 2c0493f1cd0..5fae524f9a3 100644 --- a/src/Nano/Entry.cpp +++ b/src/Nano/Entry.cpp @@ -1,31 +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 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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) 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([[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 a444938ad4a..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 const std::vector coinTypes() const { return {TWCoinTypeNano}; } - 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; + 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..3599953777f 100644 --- a/src/Nano/Signer.cpp +++ b/src/Nano/Signer.cpp @@ -1,22 +1,19 @@ // 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 +#include "../uint256.h" -#include +#include +#include #include using namespace TW; -using uint128_t = boost::multiprecision::uint128_t; using json = nlohmann::json; namespace TW::Nano { @@ -29,8 +26,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); @@ -120,7 +115,7 @@ std::array hashBlockData(const PublicKey& publicKey, const Proto::Sign } Signer::Signer(const Proto::SigningInput& input) - : privateKey(Data(input.private_key().begin(), input.private_key().end())), + : privateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519Blake2bNano), publicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b)), input(input), previous{previousFromInput(input)}, @@ -148,7 +143,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { std::array Signer::sign() const noexcept { auto digest = Data(blockHash.begin(), blockHash.end()); - auto sig = privateKey.sign(digest, TWCurveED25519Blake2bNano); + auto sig = privateKey.sign(digest); std::array signature = {0}; std::copy_n(sig.begin(), signature.size(), signature.begin()); @@ -181,4 +176,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 bbc81447f43..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}, - static_cast(Hash::sha3_256ripemd), false); - + 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 5de8d9fd91b..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeNebulas}; } - 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 4a42e4f8760..026683667e5 100644 --- a/src/Nebulas/Signer.cpp +++ b/src/Nebulas/Signer.cpp @@ -1,30 +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 "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 privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto tx = signer.buildTransaction(input); + + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); signer.sign(privateKey, tx); auto output = Proto::SigningOutput(); @@ -34,20 +25,34 @@ 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; - transaction.signature = privateKey.sign(transaction.hash, TWCurveSECP256k1); + transaction.signature = privateKey.sign(transaction.hash); 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); + 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); -} \ No newline at end of file + 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 0b174ad5f67..915c57f60a2 100644 --- a/src/Nebulas/Transaction.cpp +++ b/src/Nebulas/Transaction.cpp @@ -1,96 +1,101 @@ -// 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){ - auto data = new Proto::Data(); +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!"); } auto tx = Proto::RawTransaction(); - auto data = newPayloadData(payload); + auto* data = newPayloadData(payload); 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..1fd9b24347b --- /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(privateKey, TWCurveSECP256k1)); + } + 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..46ab1bb1f5e 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"; @@ -94,7 +88,7 @@ Address::Address(const std::vector& data) { Address::Address(const PublicKey& publicKey) { auto hash = std::array(); - blake2b(publicKey.bytes.data(), 32, hash.data(), hash.size()); + tc_blake2b(publicKey.bytes.data(), 32, hash.data(), hash.size()); std::copy(hash.begin(), hash.begin() + Address::size, bytes.begin()); } @@ -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 59c18f3e890..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeNimiq}; } - 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..fb4e6b55269 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.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 "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())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); std::array pubkeyBytes; std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); @@ -22,7 +19,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { /* destination */Address(input.destination()), /* amount */input.value(), /* fee */input.fee(), - /* vsh */input.validity_start_height() + /* vsh */input.validity_start_height(), + /* networkId */input.network_id() ); auto signer = Signer(); @@ -39,3 +37,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..9617cd2fcbb 100644 --- a/src/Nimiq/Transaction.cpp +++ b/src/Nimiq/Transaction.cpp @@ -1,38 +1,41 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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; std::vector Transaction::serialize() const { + // Source code: + // https://github.com/nimiq/core-rs-albatross/blame/b8ed402c9096ffb54afea52347b91ab7831e75de/primitives/transaction/src/lib.rs#L699 + std::vector data; data.push_back(0x00); // Basic TX type + if (isAlbatross()) { + data.push_back(0x00); // Signature Proof type and flags (Ed25519 type and no flags) + } data.insert(data.end(), sender_pub_key.begin(), sender_pub_key.end()); data.insert(data.end(), destination.bytes.begin(), destination.bytes.end()); encode64BE(amount, data); encode64BE(fee, data); encode32BE(vsh, data); - data.push_back(NETWORK_ID); + data.push_back(consensusNetworkId()); data.insert(data.end(), signature.begin(), signature.end()); return data; } std::vector Transaction::getPreImage() const { + // Source code: + // https://github.com/nimiq/core-rs-albatross/blame/b8ed402c9096ffb54afea52347b91ab7831e75de/primitives/transaction/src/lib.rs#L582 + std::vector data; // Build pre-image @@ -45,8 +48,32 @@ std::vector Transaction::getPreImage() const { encode64BE(amount, data); encode64BE(fee, data); encode32BE(vsh, data); - data.push_back(NETWORK_ID); + data.push_back(consensusNetworkId()); data.push_back(EMPTY_FLAGS); + if (isAlbatross()) { + data.push_back(0x00); // Sender Data size (+ 0 bytes of data) + } return data; } + +bool Transaction::isAlbatross() const { + if (networkId == Proto::NetworkId::MainnetAlbatross) { + return true; + } + return false; +} + +uint8_t Transaction::consensusNetworkId() const { + switch (networkId) { + case Proto::NetworkId::UseDefault: + case Proto::NetworkId::Mainnet: + return static_cast(Proto::NetworkId::Mainnet); + case Proto::NetworkId::MainnetAlbatross: + return static_cast(Proto::NetworkId::MainnetAlbatross); + default: + throw std::invalid_argument("Invalid network ID"); + } +} + +} // namespace TW::Nimiq diff --git a/src/Nimiq/Transaction.h b/src/Nimiq/Transaction.h index ad8c192756d..98ea799387d 100644 --- a/src/Nimiq/Transaction.h +++ b/src/Nimiq/Transaction.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 "Address.h" +#include "proto/Nimiq.pb.h" namespace TW::Nimiq { @@ -22,16 +21,22 @@ class Transaction { uint64_t fee; // Validity start (block) height uint32_t vsh; + // Network ID + Proto::NetworkId networkId; // Sender signature std::array signature; Transaction(const std::array& sender, const Address& dest, uint64_t amount, - uint64_t fee, uint32_t vsh) - : sender_pub_key(sender), destination(dest), amount(amount), fee(fee), vsh(vsh) {} + uint64_t fee, uint32_t vsh, Proto::NetworkId networkId) + : sender_pub_key(sender), destination(dest), amount(amount), fee(fee), vsh(vsh), networkId(networkId) {} public: std::vector serialize() const; std::vector getPreImage() const; + + private: + bool isAlbatross() const; + uint8_t consensusNetworkId() const; }; } // namespace TW::Nimiq 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 new file mode 100644 index 00000000000..0daa1f13242 --- /dev/null +++ b/src/NumericLiteral.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include // std::size_t + +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0330r8.html +inline constexpr std::size_t operator"" _uz(unsigned long long int n) { + return n; +} \ No newline at end of file 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 a82478f4050..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeOasis}; } - 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..ca687bccf05 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(), TWCurveED25519); + 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(), TWCurveED25519); + + // 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); + 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 ba3cf582d26..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeOntology}; } - 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..2ef9d41ca78 --- /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(), TWCurveNIST256p1)); + auto fromSigner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); + 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..6f6fcfc4383 100644 --- a/src/Ontology/OngTxBuilder.cpp +++ b/src/Ontology/OngTxBuilder.cpp @@ -1,30 +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 "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) { - auto payer = Signer(PrivateKey(input.payer_private_key())); - auto owner = Signer(PrivateKey(input.owner_private_key())); +Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { + auto payer = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto owner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto transaction = Ong().transfer(owner, toAddress, input.amount(), payer, input.gas_price(), input.gas_limit(), input.nonce()); @@ -32,9 +29,9 @@ Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput &input) { - auto payer = Signer(PrivateKey(input.payer_private_key())); - auto owner = Signer(PrivateKey(input.owner_private_key())); +Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput& input) { + auto payer = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto owner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto transaction = Ong().withdraw(owner, toAddress, input.amount(), payer, input.gas_price(), input.gas_limit(), input.nonce()); @@ -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..3da313eda46 100644 --- a/src/Ontology/OntTxBuilder.cpp +++ b/src/Ontology/OntTxBuilder.cpp @@ -1,30 +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 "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) { - auto payerSigner = Signer(PrivateKey(input.payer_private_key())); - auto fromSigner = Signer(PrivateKey(input.owner_private_key())); +Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { + auto payerSigner = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto fromSigner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto tranferTx = Ont().transfer(fromSigner, toAddress, input.amount(), payerSigner, input.gas_price(), input.gas_limit(), input.nonce()); @@ -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 ff5d7de3f92..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,33 +10,40 @@ #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); @@ -220,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())); @@ -230,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 d9e8166ae9d..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; @@ -41,8 +39,3 @@ class Signer { void addSign(Transaction& tx) const; }; } // namespace TW::Ontology - -/// Wrapper for C interface. -struct TWOntologySigner { - TW::Ontology::Signer impl; -}; 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/Pactus/Entry.h b/src/Pactus/Entry.h new file mode 100644 index 00000000000..2f41bcde419 --- /dev/null +++ b/src/Pactus/Entry.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Pactus { + +/// Entry point for Pactus 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::Pactus + diff --git a/src/Polkadot/Address.h b/src/Polkadot/Address.h deleted file mode 100644 index 183018ea3db..00000000000 --- a/src/Polkadot/Address.h +++ /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. - -#pragma once - -#include "../Data.h" -#include "../PublicKey.h" -#include "../SS58Address.h" -#include - -#include - -namespace TW::Polkadot { - -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); } - - /// Initializes a Polkadot address with a string representation. - Address(const std::string& string): SS58Address(string, TWSS58AddressTypePolkadot) {} - - /// Initializes a Polkadot address with a public key. - Address(const PublicKey& publicKey): SS58Address(publicKey, TWSS58AddressTypePolkadot) {} -}; -} // namespace TW::Polkadot - diff --git a/src/Polkadot/Entry.cpp b/src/Polkadot/Entry.cpp deleted file mode 100644 index 4db8a184a19..00000000000 --- a/src/Polkadot/Entry.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 "Entry.h" - -#include "Address.h" -#include "Signer.h" - -using namespace TW::Polkadot; -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(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} diff --git a/src/Polkadot/Entry.h b/src/Polkadot/Entry.h index e0932bba62a..d4e4a719a73 100644 --- a/src/Polkadot/Entry.h +++ b/src/Polkadot/Entry.h @@ -1,23 +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. #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" 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 { -public: - virtual const std::vector coinTypes() const { return {TWCoinTypePolkadot}; } - 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; +class Entry : public Rust::RustCoinEntry { }; } // namespace TW::Polkadot diff --git a/src/Polkadot/Extrinsic.cpp b/src/Polkadot/Extrinsic.cpp deleted file mode 100644 index 473526f49f1..00000000000 --- a/src/Polkadot/Extrinsic.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 "Extrinsic.h" -#include -#include - -using namespace TW; -using namespace TW::Polkadot; - -static constexpr uint8_t signedBit = 0x80; -static constexpr uint8_t sigTypeEd25519 = 0x00; -static constexpr uint8_t extrinsicFormat = 4; -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 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"; - -// Readable decoded call index can be found from https://polkascan.io -static std::map polkadotCallIndices = { - {balanceTransfer, Data{0x05, 0x00}}, - {utilityBatch, Data{0x1a, 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}}, -}; - -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}}, -}; - -static Data getCallIndex(TWSS58AddressType network, const std::string& key) { - switch (network) { - case TWSS58AddressTypePolkadot: - return polkadotCallIndices[key]; - case TWSS58AddressTypeKusama: - return kusamaCallIndices[key]; - } -} - -bool Extrinsic::encodeRawAccount(TWSS58AddressType network, uint32_t specVersion) { - if ((network == TWSS58AddressTypePolkadot && specVersion >= multiAddrSpecVersion) || - (network == TWSS58AddressTypeKusama && specVersion >= multiAddrSpecVersionKsm)) { - return false; - } - return true; -} - -Data Extrinsic::encodeEraNonceTip() const { - Data data; - // era - append(data, era); - // nonce - append(data, encodeCompact(nonce)); - // tip - append(data, encodeCompact(tip)); - return data; -} - -Data Extrinsic::encodeCall(const Proto::SigningInput& input) { - // 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()); - } else if (input.has_staking_call()) { - data = encodeStakingCall(input.staking_call(), network, input.spec_version()); - } - return data; -} - -Data Extrinsic::encodeBalanceCall(const Proto::Balance& balance, TWSS58AddressType network, uint32_t specVersion) { - 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)); - // destination - append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); - // value - append(data, encodeCompact(value)); - return data; -} - -Data Extrinsic::encodeBatchCall(const std::vector& calls, TWSS58AddressType network) { - Data data; - append(data, getCallIndex(network, utilityBatch)); - append(data, encodeVector(calls)); - return data; -} - -Data Extrinsic::encodeStakingCall(const Proto::Staking& staking, TWSS58AddressType network, uint32_t specVersion) { - 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.unbond().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.push_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; - - default: - break; - } - return data; -} - -Data Extrinsic::encodePayload() const { - Data data; - // call - append(data, call); - // era / nonce / tip - append(data, encodeEraNonceTip()); - // specVersion - encode32LE(specVersion, data); - // transactionVersion - encode32LE(version, data); - // genesis hash - append(data, genesisHash); - // block hash - append(data, blockHash); - return data; -} - -Data Extrinsic::encodeSignature(const PublicKey& signer, const Data& signature) const { - Data data; - // version header - append(data, Data{extrinsicFormat | signedBit}); - // signer public key - append(data, encodeAccountId(signer.bytes, encodeRawAccount(network, specVersion))); - // signature type - append(data, sigTypeEd25519); - // signature - append(data, signature); - // era / nonce / tip - append(data, encodeEraNonceTip()); - // call - append(data, call); - // append length - encodeLengthPrefix(data); - return data; -} diff --git a/src/Polkadot/Extrinsic.h b/src/Polkadot/Extrinsic.h deleted file mode 100644 index dc20eb75908..00000000000 --- a/src/Polkadot/Extrinsic.h +++ /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. - -#pragma once - -#include "Address.h" -#include "../Data.h" -#include "../proto/Polkadot.pb.h" -#include "../uint256.h" -#include "ScaleCodec.h" - -namespace TW::Polkadot { - -// ExtrinsicV4 -class Extrinsic { - public: - Data blockHash; - Data genesisHash; - uint64_t nonce; - // Runtime spec version - uint32_t specVersion; - // transaction version - uint32_t version; - // balances::TakeFees - uint256_t tip; - // encoded Era data - Data era; - // encoded Call data - Data call; - // network - TWSS58AddressType network; - - 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())) { - if (input.has_era()) { - era = encodeEra(input.era().block_number(), input.era().period()); - } else { - // immortal era - era = encodeCompact(0); - } - network = TWSS58AddressType(input.network()); - call = encodeCall(input); - } - - static Data encodeCall(const Proto::SigningInput& input); - // 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); - Data encodeEraNonceTip() const; -}; - -} // namespace TW::Polkadot diff --git a/src/Polkadot/ScaleCodec.h b/src/Polkadot/ScaleCodec.h deleted file mode 100644 index 6a244383440..00000000000 --- a/src/Polkadot/ScaleCodec.h +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "../BinaryCoding.h" -#include "../Data.h" -#include "../PublicKey.h" -#include "../SS58Address.h" -#include -#include -#include -#include - - -/// Reference https://github.com/soramitsu/kagome/blob/master/core/scale/scale_encoder_stream.cpp -using CompactInteger = boost::multiprecision::cpp_int; - -namespace TW::Polkadot { - -static constexpr size_t kMinUint16 = (1ul << 6u); -static constexpr size_t kMinUint32 = (1ul << 14u); -static constexpr size_t kMinBigInteger = (1ul << 30u); - -inline size_t countBytes(CompactInteger value) { - if (0 == value) { - return 1; - } - - size_t size = 0; - while (value > 0) { - ++size; - value >>= 8; - } - - return size; -} - -inline Data encodeCompact(CompactInteger value) { - auto data = Data{}; - - if (value < kMinUint16) { - auto v = value.convert_to() << 2u; - data.push_back(static_cast(v)); - return data; - } else if (value < kMinUint32) { - auto v = (value.convert_to() << 2u); - v += 0x01; // set 0b01 flag - auto minor_byte = static_cast(v & 0xffu); - data.push_back(minor_byte); - v >>= 8u; - auto major_byte = static_cast(v & 0xffu); - data.push_back(major_byte); - return data; - } else if (value < kMinBigInteger) { - uint32_t v = (value.convert_to() << 2u); - v += 0x02; // set 0b10 flag - encode32LE(v, data); - return data; - } - - auto length = countBytes(value); - if (length > 67) { - // too big - return data; - } - uint8_t header = (static_cast(length) - 4) * 4; - header += 0x03; // set 0b11 flag; - data.push_back(header); - - auto v = CompactInteger{value}; - for (size_t i = 0; i < length; ++i) { - data.push_back(static_cast(v & 0xff)); // push back least significant byte - v >>= 8; - } - return data; -} - -// append length prefix -inline void encodeLengthPrefix(Data& data) { - size_t len = data.size(); - auto prefix = encodeCompact(len); - data.insert(data.begin(), prefix.begin(), prefix.end()); -} - -inline Data encodeBool(bool value) { - return Data{uint8_t(value ? 0x01 : 0x00)}; -} - -inline Data encodeVector(const std::vector& vec) { - auto data = encodeCompact(vec.size()); - for (auto v : vec) { - append(data, v); - } - return data; -} - -inline Data encodeAccountId(const Data& bytes, bool raw) { - auto data = Data{}; - if (!raw) { - // MultiAddress::AccountId - // https://github.com/paritytech/substrate/blob/master/primitives/runtime/src/multiaddress.rs#L28 - append(data, 0x00); - } - append(data, bytes); - return data; -} - -inline Data encodeAccountIds(const std::vector& addresses, bool raw) { - std::vector vec; - for (auto addr : addresses) { - vec.push_back(encodeAccountId(addr.keyBytes(), raw)); - } - return encodeVector(vec); -} - -inline Data encodeEra(const uint64_t block, const uint64_t period) { - // MortalEra(phase, period) - // See decodeMortalObject at https://github.com/polkadot-js/api/blob/master/packages/types/src/extrinsic/ExtrinsicEra.ts#L87 - // See toU8a at https://github.com/polkadot-js/api/blob/master/packages/types/src/extrinsic/ExtrinsicEra.ts#L167 - uint64_t calPeriod = uint64_t(pow(2, ceil(log2(double(period))))); - calPeriod = std::min(std::max(calPeriod, uint64_t(4)), uint64_t(1) << 16); - uint64_t phase = block % calPeriod; - uint64_t quantizeFactor = std::max(calPeriod >> uint64_t(12), uint64_t(1)); - uint64_t quantizedPhase = phase / quantizeFactor * quantizeFactor; - - auto bitset = std::bitset<64>(calPeriod); - int trailingZeros = 0; - for (int i = 0; i < 64 - 1; i++) { - if (bitset[i] == 0) { - trailingZeros += 1; - } else { - break; - } - } - auto encoded = std::min(15, std::max(1, trailingZeros - 1)) + (((quantizedPhase / quantizeFactor) << 4)); - return Data{byte(encoded & 0xff), byte(encoded >> 8)}; -} - -} // namespace TW::Polkadot diff --git a/src/Polkadot/Signer.cpp b/src/Polkadot/Signer.cpp deleted file mode 100644 index c7f708adcc7..00000000000 --- a/src/Polkadot/Signer.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 "Signer.h" -#include "Extrinsic.h" -#include "../Hash.h" -#include "../PrivateKey.h" - -using namespace TW; -using namespace TW::Polkadot; - -static constexpr size_t hashTreshold = 256; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - auto extrinsic = Extrinsic(input); - auto payload = extrinsic.encodePayload(); - // check if need to hash - if (payload.size() > hashTreshold) { - payload = Hash::blake2b(payload, 32); - } - auto signature = privateKey.sign(payload, TWCurveED25519); - auto encoded = extrinsic.encodeSignature(publicKey, signature); - - auto protoOutput = Proto::SigningOutput(); - protoOutput.set_encoded(encoded.data(), encoded.size()); - return protoOutput; -} diff --git a/src/Polkadot/Signer.h b/src/Polkadot/Signer.h deleted file mode 100644 index 4b790bd8b42..00000000000 --- a/src/Polkadot/Signer.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 "../Data.h" -#include "../PrivateKey.h" -#include "../proto/Polkadot.pb.h" - -namespace TW::Polkadot { - -/// Helper class that performs Polkadot 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::Polkadot diff --git a/src/Polymesh/Entry.h b/src/Polymesh/Entry.h new file mode 100644 index 00000000000..e32f339be46 --- /dev/null +++ b/src/Polymesh/Entry.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Polymesh { + +/// Entry point for Polymesh 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::Polymesh + diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 4bbfd5e4a96..98e05e500bc 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,20 +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. Extended key needs 3*32 bytes. - if (data.size() != size && data.size() != extendedSize) { + // Check length + 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; } @@ -37,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; @@ -56,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: @@ -75,29 +105,60 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) return true; } -PrivateKey::PrivateKey(const Data& data) { - if (!isValid(data)) { +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"); + } + bytes = data; + } + +PrivateKey::PrivateKey(const Data& data, TWCurve curve) { + if (!isValid(data, curve)) { throw std::invalid_argument("Invalid private key data"); } - if (data.size() == extendedSize) { - // special extended case - *this = PrivateKey( - TW::data(data.data(), 32), - TW::data(data.data() + 32, 32), - TW::data(data.data() + 64, 32)); - } else { - // default case - bytes = data; + bytes = data; + _curve = curve; +} + +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) { + throw std::invalid_argument("Invalid private key or extended key data"); } + bytes = key1; + append(bytes, extension1); + append(bytes, chainCode1); + append(bytes, key2); + append(bytes, extension2); + append(bytes, chainCode2); } -PrivateKey::PrivateKey(const Data& data, const Data& ext, const Data& chainCode) { - if (!isValid(data) || !isValid(ext) || !isValid(chainCode)) { +PrivateKey::PrivateKey( + const Data& key1, const Data& extension1, const Data& chainCode1, + const Data& key2, const Data& extension2, const Data& chainCode2, + TWCurve curve) { + 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 = data; - extensionBytes = ext; - chainCodeBytes = chainCode; + bytes = key1; + append(bytes, extension1); + append(bytes, chainCode1); + append(bytes, key2); + append(bytes, extension2); + append(bytes, chainCode2); + _curve = curve; } PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { @@ -105,66 +166,63 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { switch (type) { case TWPublicKeyTypeSECP256k1: result.resize(PublicKey::secp256k1Size); - ecdsa_get_public_key33(&secp256k1, bytes.data(), result.data()); + ecdsa_get_public_key33(&secp256k1, key().data(), result.data()); break; case TWPublicKeyTypeSECP256k1Extended: result.resize(PublicKey::secp256k1ExtendedSize); - ecdsa_get_public_key65(&secp256k1, bytes.data(), result.data()); + ecdsa_get_public_key65(&secp256k1, key().data(), result.data()); break; case TWPublicKeyTypeNIST256p1: result.resize(PublicKey::secp256k1Size); - ecdsa_get_public_key33(&nist256p1, bytes.data(), result.data()); + ecdsa_get_public_key33(&nist256p1, key().data(), result.data()); break; case TWPublicKeyTypeNIST256p1Extended: result.resize(PublicKey::secp256k1ExtendedSize); - ecdsa_get_public_key65(&nist256p1, bytes.data(), result.data()); + ecdsa_get_public_key65(&nist256p1, key().data(), result.data()); break; case TWPublicKeyTypeED25519: result.resize(PublicKey::ed25519Size); - ed25519_publickey(bytes.data(), result.data()); + ed25519_publickey(key().data(), result.data()); break; case TWPublicKeyTypeED25519Blake2b: result.resize(PublicKey::ed25519Size); - ed25519_publickey_blake2b(bytes.data(), result.data()); + ed25519_publickey_blake2b(key().data(), result.data()); break; - case TWPublicKeyTypeED25519Extended: - // must be extended key - if (bytes.size() + extensionBytes.size() + chainCodeBytes.size() != extendedSize) { + case TWPublicKeyTypeED25519Cardano: { + // must be double extended key + if (bytes.size() != cardanoKeySize) { throw std::invalid_argument("Invalid extended key"); } - result.resize(PublicKey::ed25519ExtendedSize); - ed25519_publickey_ext(bytes.data(), extensionBytes.data(), result.data()); - // append chainCode to the end of the public key - std::copy(chainCodeBytes.begin(), chainCodeBytes.end(), result.begin() + 32); - break; - case TWPublicKeyTypeCURVE25519: + 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: { 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, bytes.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; } @@ -173,51 +231,50 @@ int ecdsa_sign_digest_checked(const ecdsa_curve *curve, const uint8_t *priv_key, } Data PrivateKey::sign(const Data& digest, TWCurve curve) const { + if (_curve.has_value() && _curve.value() != curve) { + throw std::invalid_argument("Specified curve is different from the curve of the private key"); + } Data result; bool success = false; switch (curve) { - case TWCurveSECP256k1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, bytes.data(), digest.data(), digest.size(), result.data(), - result.data() + 64, nullptr) == 0; - } break; - case TWCurveED25519HD: - case TWCurveED25519: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), bytes.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(), bytes.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(), bytes.data(), extensionBytes.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(), bytes.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, bytes.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; } @@ -227,24 +284,32 @@ 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, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { + if (!_curve.has_value()) { + throw std::invalid_argument("Curve is not set"); + } + return sign(digest, _curve.value(), canonicalChecker); +} + +Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { + if (_curve.has_value() && _curve.value() != curve) { + throw std::invalid_argument("Specified curve is different from the curve of the private key"); + } Data result; bool success = false; switch (curve) { case TWCurveSECP256k1: { result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, bytes.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, bytes.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: @@ -260,10 +325,20 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)( return result; } -Data PrivateKey::signAsDER(const Data& digest, TWCurve curve) const { +Data PrivateKey::sign(const Data& digest) const { + if (!_curve.has_value()) { + throw std::invalid_argument("Curve is not set"); + } + return sign(digest, _curve.value()); +} + +Data PrivateKey::signAsDER(const Data& digest) const { + if (_curve.has_value() && _curve.value() != TWCurveSECP256k1) { + throw std::invalid_argument("DER signature is only supported for SECP256k1"); + } Data sig(64); bool success = - ecdsa_sign_digest(&secp256k1, bytes.data(), digest.data(), sig.data(), nullptr, nullptr) == 0; + ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; if (!success) { return {}; } @@ -276,24 +351,12 @@ Data PrivateKey::signAsDER(const Data& digest, TWCurve curve) const { return result; } -Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { - bool success = false; - Data sig(64); - switch (curve) { - case TWCurveSECP256k1: { - success = zil_schnorr_sign(&secp256k1, bytes.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; +Data PrivateKey::signZilliqa(const Data& message) const { + if (_curve.has_value() && _curve.value() != TWCurveSECP256k1) { + throw std::invalid_argument("Zilliqa signature is only supported for SECP256k1"); } + Data sig(64); + bool success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; if (!success) { return {}; @@ -302,7 +365,5 @@ Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { } void PrivateKey::cleanup() { - std::fill(bytes.begin(), bytes.end(), 0); - std::fill(extensionBytes.begin(), extensionBytes.end(), 0); - std::fill(chainCodeBytes.begin(), chainCodeBytes.end(), 0); + memzero(bytes.data(), bytes.size()); } diff --git a/src/PrivateKey.h b/src/PrivateKey.h index 08d01d76d9d..ba0e5ebcff9 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -1,31 +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 "PublicKey.h" +#include #include +#include + namespace TW { class PrivateKey { public: /// The number of bytes in a private key. - static const size_t size = 32; - /// The number of bytes in an extended private key. - static const size_t extendedSize = 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. + /// The private key bytes: + /// - common case: 'size' bytes + /// - double extended case: 'cardanoKeySize' bytes, key+extension+chainCode+second+secondExtension+secondChainCode Data bytes; - /// Optional extended part of the key (additional 32 bytes) - Data extensionBytes; - /// Optional chain code (additional 32 bytes) - Data chainCodeBytes; + + /// Optional members for extended keys and second extended keys + Data key() const { return subData(bytes, 0, 32); } + Data extension() const { return subData(bytes, 32, 32); } + Data chainCode() const { return subData(bytes, 2*32, 32); } + Data secondKey() const { return subData(bytes, 3*32, 32); } + Data secondExtension() const { return subData(bytes, 4*32, 32); } + Data secondChainCode() const { return subData(bytes, 5*32, 32); } /// Determines if a collection of bytes makes a valid private key. static bool isValid(const Data& data); @@ -33,14 +40,38 @@ class PrivateKey { /// Determines if a collection of bytes and curve make a valid private key. static bool isValid(const Data& data, TWCurve curve); - /// Initializes a private key with an array of bytes. Size must be exact (normally 32, or 96 for extended) + // 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) + /// @deprecated Use PrivateKey(const Data& data, TWCurve curve) instead explicit PrivateKey(const Data& data); - /// Initializes a private key from a string of bytes (convenience method). + /// Initializes a private key with an array of bytes and a curve. + /// Size of the data must be exact (normally 32, or 192 for extended) + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey(const Data& data, TWCurve curve); + + /// Initializes a private key from a string of bytes. + /// @deprecated Use PrivateKey(const std::string& data, TWCurve curve) instead explicit PrivateKey(const std::string& data) : PrivateKey(TW::data(data)) {} - /// Initializes an extended private key with key, extended key, and chain code. - explicit PrivateKey(const Data& data, const Data& ext, const Data& chainCode); + /// Initializes a private key from a string of bytes and a curve. + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey(const std::string& data, TWCurve curve) : PrivateKey(TW::data(data), curve) {} + + /// Initializes a Cardano style key + /// @deprecated Use PrivateKey(const Data& bytes1, const Data& extension1, const Data& chainCode1, const Data& bytes2, const Data& extension2, const Data& chainCode2, TWCurve curve) instead + explicit PrivateKey( + const Data& bytes1, const Data& extension1, const Data& chainCode1, + const Data& bytes2, const Data& extension2, const Data& chainCode2); + + /// Initializes a Cardano style key with a specified curve. + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey( + const Data& bytes1, const Data& extension1, const Data& chainCode1, + const Data& bytes2, const Data& extension2, const Data& chainCode2, + TWCurve curve); PrivateKey(const PrivateKey& other) = default; PrivateKey& operator=(const PrivateKey& other) = default; @@ -53,35 +84,40 @@ 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. + /// If constructed with a curve, an exception will be thrown if the curve does not match the one specified. + /// @deprecated Use sign(const Data& digest) instead Data sign(const Data& digest, TWCurve curve) const; + /// Signs a digest using the given ECDSA curve at the time of construction. + /// IF constructed without a curve, an exception will be thrown. + Data sign(const Data& digest) const; + /// Signs a digest using the given ECDSA curve and prepends the recovery id (a la graphene) /// Only a sig that passes canonicalChecker is returned + /// If constructed with a curve, an exception will be thrown if the curve does not match the one specified. + /// @deprecated Use sign(const Data& digest, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) instead Data sign(const Data& digest, TWCurve curve, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; + /// Signs a digest using the given ECDSA curve and prepends the recovery id (a la graphene) + /// Only a sig that passes canonicalChecker is returned + Data sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; + /// Signs a digest using the given ECDSA curve. The result is encoded with /// DER. - Data signAsDER(const Data& digest, TWCurve curve) const; + /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. + 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 + /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. + Data signZilliqa(const Data& message) const; /// Cleanup contents (fill with 0s), called before destruction void cleanup(); +private: + std::optional _curve = std::nullopt; }; -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 a882ee14f8d..d9789e6bc06 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -1,18 +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 "PublicKey.h" +#include "PrivateKey.h" #include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" #include #include +#include #include #include #include -#include +#include +#include + +#include namespace TW { @@ -29,14 +33,16 @@ bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { case TWPublicKeyTypeCURVE25519: case TWPublicKeyTypeED25519Blake2b: return size == ed25519Size; - case TWPublicKeyTypeED25519Extended: - return size == ed25519ExtendedSize; + 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; } @@ -44,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: @@ -72,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(ed25519ExtendedSize); + case TWPublicKeyTypeED25519Cardano: + bytes.reserve(cardanoKeySize); std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); } } @@ -116,9 +124,21 @@ 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 { @@ -133,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()); @@ -148,12 +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::verifySchnorr(const Data& signature, const Data& message) const { +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; + } + + default: + return false; + } +} + +bool PublicKey::verifyZilliqa(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: @@ -162,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; @@ -171,7 +213,7 @@ bool PublicKey::verifySchnorr(const Data& signature, const Data& message) const Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) const { const auto offset = std::size_t(skipTypeByte ? 1 : 0); - const auto hash = hasher(bytes.data() + offset, bytes.size() - offset); + const auto hash = Hash::hash(hasher, bytes.data() + offset, bytes.size() - offset); auto result = Data(); result.reserve(prefix.size() + hash.size()); @@ -180,21 +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]; - if (v >= 27) { - v -= 27; + 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 27daa26a267..6a1e3d17855 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.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,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 ed25519ExtendedSize = 64; + /// 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. @@ -61,17 +69,31 @@ class PublicKey { /// Verifies a signature for the provided message. bool verify(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 signature in DER format. + bool verifyAsDER(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. /// /// The public key hash is computed by applying the hasher to the public key /// bytes and then prepending the prefix. - Data hash(const Data& prefix, Hash::Hasher hasher = Hash::sha256ripemd, bool skipTypeByte = false) const; + 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 adcd80e90fc..00000000000 --- a/src/Ripple/Entry.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 const std::vector coinTypes() const { return {TWCoinTypeXRP}; } - 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 0be0b41f62f..00000000000 --- a/src/Ripple/Signer.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 "Signer.h" -#include "../BinaryCoding.h" -#include "../Hash.h" - -using namespace TW; -using namespace TW::Ripple; - -static const int64_t fullyCanonical = 0x80000000; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - 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*/input.destination_tag() - ); - - auto signer = Signer(); - signer.sign(key, transaction); - - auto output = Proto::SigningOutput(); - 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 9d4a8dcbd58..00000000000 --- a/src/Ripple/Transaction.cpp +++ /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. - -#include "BinaryCoding.h" -#include "Transaction.h" -#include "../BinaryCoding.h" -#include "../HexCoding.h" - -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(&address.bytes[0] + 1, &address.bytes[21]); - 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/Ripple/XAddress.h b/src/Ripple/XAddress.h deleted file mode 100644 index dc60e67a57d..00000000000 --- a/src/Ripple/XAddress.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 "../Data.h" -#include "../PublicKey.h" - -#include - -namespace TW::Ripple { - -enum class TagFlag: byte { none = 0x00, classic = 0x01}; - -class XAddress { - public: - /// Number of bytes in a X-address. - static const size_t size = 31; - - /// Publick key hash length. - static const size_t keyHashSize = 20; - - /// Address data consisting of public key hash - std::array bytes; - - /// destination tag - uint32_t tag; - - /// destination tag flag, none/32/64bit - TagFlag flag = TagFlag::classic; - - /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); - - /// Initializes a Ripple X-address with a string representation. - explicit XAddress(const std::string& string); - - /// Initializes a Ripple X-address with a public key. - explicit XAddress(const PublicKey& publicKey, const uint32_t tag); - - /// Returns a string representation of the address. - std::string string() const; -}; - -inline bool operator==(const XAddress& lhs, const XAddress& rhs) { - return lhs.bytes == rhs.bytes; -} - -} // namespace TW::Ripple - -/// Wrapper for C interface. -struct TWRippleXAddress { - TW::Ripple::XAddress impl; -}; diff --git a/src/Ronin/Entry.h b/src/Ronin/Entry.h new file mode 100644 index 00000000000..eeffca63f40 --- /dev/null +++ b/src/Ronin/Entry.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Ethereum/Entry.h" + +namespace TW::Ronin { + +/// Entry point for Ronin (EVM side chain) +class Entry final : public Ethereum::Entry { +}; + +} // namespace TW::Ronin diff --git a/src/SS58Address.h b/src/SS58Address.h deleted file mode 100644 index b9ccfa627b6..00000000000 --- a/src/SS58Address.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "Base58.h" -#include "Data.h" -#include "PublicKey.h" - -#include -#include -#include - -const std::string SS58Prefix = "SS58PRE"; - -namespace TW { - -class SS58Address { - public: - /// Number of bytes in an address. - static const size_t size = 33; - - static const size_t checksumSize = 2; - - /// Address data consisting of a network byte followed by the public key. - std::array bytes; - - /// Determines whether a string makes a valid address - static bool isValid(const std::string& string, byte network) { - const auto decoded = Base58::bitcoin.decode(string); - if (decoded.size() != SS58Address::size + checksumSize) { - return false; - } - // check network - if (decoded[0] != 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(SS58Prefix.begin(), SS58Prefix.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; - } - - SS58Address() = default; - - /// Initializes an address with a string representation. - SS58Address(const std::string& string, byte network) { - if (!isValid(string, network)) { - throw std::invalid_argument("Invalid address string"); - } - const auto decoded = Base58::bitcoin.decode(string); - std::copy(decoded.begin(), decoded.end() - checksumSize, bytes.begin()); - } - - /// Initializes an address with a public key and network. - SS58Address(const PublicKey& publicKey, byte network) { - if (publicKey.type != TWPublicKeyTypeED25519) { - throw std::invalid_argument("SS58Address expects an ed25519 public key."); - } - bytes[0] = network; - std::copy(publicKey.bytes.begin(), publicKey.bytes.end(), bytes.begin() + 1); - } - - /// 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::bitcoin.encode(result); - } - - /// Returns public key bytes - Data keyBytes() const { - return Data(bytes.begin() + 1, bytes.end()); - } -}; - -inline bool operator==(const SS58Address& lhs, const SS58Address& rhs) { - return lhs.bytes == rhs.bytes; -} - -} // namespace TW 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 f773c8bdd08..c913362d060 100644 --- a/src/Solana/Entry.cpp +++ b/src/Solana/Entry.cpp @@ -1,27 +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 "Entry.h" +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "proto/Solana.pb.h" -#include "Address.h" -#include "Signer.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. +namespace TW::Solana { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(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.encoded(); } + ); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); +PrivateKey Entry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const { + auto data = Base58::decode(privateKey); + if (data.size() == 64) { + const auto privateKeyData = subData(data, 0, 32); + const auto publicKeyData = subData(data, 32, 32); + auto privKey = PrivateKey(privateKeyData, TW::curve(coin)); + auto publicKey = privKey.getPublicKey(TWPublicKeyType::TWPublicKeyTypeED25519); + if (publicKey.bytes != publicKeyData) { + throw std::invalid_argument("Invalid private key"); + } + return privKey; + } else if (data.size() == 32) { + return PrivateKey(data, TW::curve(coin)); + } else { + auto hexData = parse_hex(privateKey); + return PrivateKey(hexData, TW::curve(coin)); + } } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} +} // namespace TW::Solana diff --git a/src/Solana/Entry.h b/src/Solana/Entry.h index 0f5fab96f9f..226ab553ae0 100644 --- a/src/Solana/Entry.h +++ b/src/Solana/Entry.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 "../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 const std::vector coinTypes() const { return {TWCoinTypeSolana}; } - 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 supportsJSONSigning() const override { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; + PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey) const override; }; } // namespace TW::Solana diff --git a/src/Solana/Program.cpp b/src/Solana/Program.cpp deleted file mode 100644 index d63ce8aebff..00000000000 --- a/src/Solana/Program.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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); -} - -/* - * 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) { - Address 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 610c136a752..00000000000 --- a/src/Solana/Program.h +++ /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. - -#pragma once - -#include "Address.h" - -#include - -namespace TW::Solana { - -class StakeProgram { -public: - static Address addressFromValidatorSeed(const Address& fromAddress, - const Address& validatorAddress, - 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 e5427d5299e..00000000000 --- a/src/Solana/Signer.cpp +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 - -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; - } -} - -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( - /* from */ Address(key.getPublicKey(TWPublicKeyTypeED25519)), - /* to */ Address(protoMessage.recipient()), - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kStakeTransaction: - { - auto protoMessage = input.stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto validatorAddress = Address(protoMessage.validator_pubkey()); - auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); - auto stakeAddress = StakeProgram::addressFromValidatorSeed(userAddress, validatorAddress, stakeProgramId); - message = Message( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* 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 validatorAddress = Address(protoMessage.validator_pubkey()); - auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); - auto stakeAddress = StakeProgram::addressFromValidatorSeed(userAddress, validatorAddress, stakeProgramId); - message = Message( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* type */ Deactivate, - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kWithdrawTransaction: - { - auto protoMessage = input.withdraw_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto validatorAddress = Address(protoMessage.validator_pubkey()); - auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); - auto stakeAddress = StakeProgram::addressFromValidatorSeed(userAddress, validatorAddress, stakeProgramId); - message = Message( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* value */ protoMessage.value(), - /* type */ Withdraw, - /* recent_blockhash */ 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(userAddress, TokenInstruction::CreateTokenAccount, 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()); - message = Message(userAddress, TokenInstruction::TokenTransfer, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, decimals, blockhash); - 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()); - message = Message(userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, senderTokenAddress, amount, decimals, blockhash); - 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; -} diff --git a/src/Solana/Signer.h b/src/Solana/Signer.h deleted file mode 100644 index 04614e5e93b..00000000000 --- a/src/Solana/Signer.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 "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); - 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 15c99c17ef2..00000000000 --- a/src/Solana/Transaction.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 "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::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) { - accountKeys.push_back(a); - } - for(auto& a: unsignedAccounts) { - accountKeys.push_back(a); - } - for(auto& a: readOnlyAccounts) { - accountKeys.push_back(a); - } - - compileInstructions(); -} - -void Message::compileInstructions() { - compiledInstructions.clear(); - for (auto instruction: instructions) { - compiledInstructions.push_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) { - std::vector
::iterator 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 1b521b75888..00000000000 --- a/src/Solana/Transaction.h +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 -#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"; - -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 constructor creates a default System Transfer instruction - Instruction(const std::vector& accounts, uint64_t value) : - programId(Address(SYSTEM_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - SystemInstruction type = Transfer; - auto data = Data(); - encode32LE(static_cast(type), data); - encode64LE(static_cast(value), data); - this->data = data; - } - - // This constructor creates a System CreateAccountWithSeed instruction - Instruction(const std::vector& accounts, uint64_t value, uint64_t space, const Address& programId, - const Address& voteAddress, uint64_t seedLength, const Address& signer) : - programId(Address(SYSTEM_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - 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()); - this->data = data; - } - - // This constructor creates an Initialize Stake instruction - Instruction(StakeInstruction type, const std::vector& accounts, const Address& signer) : - programId(Address(STAKE_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - auto data = Data(); - encode32LE(static_cast(type), data); - append(data, signer.vector()); - append(data, signer.vector()); - auto lockup = Data(48); - append(data, lockup); - this->data = data; - } - - // This constructor creates a Withdraw Stake instruction - Instruction(StakeInstruction type, const std::vector& accounts, uint64_t value) : - programId(Address(STAKE_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - auto data = Data(); - encode32LE(static_cast(type), data); - encode64LE(static_cast(value), data); - this->data = data; - } - - // This constructor creates a Stake instruction - Instruction(StakeInstruction type, const std::vector& accounts) : - programId(Address(STAKE_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - auto data = Data(); - encode32LE(static_cast(type), data); - this->data = data; - } - - // This constructor creates a createAccount token instruction. - Instruction(TokenInstruction type, const std::vector& accounts) : - programId(Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - this->data = Data(); - } - - // This constructor creates a transfer token instruction. - Instruction(TokenInstruction type, const std::vector& accounts, uint64_t value, uint8_t decimals) : - programId(Address(TOKEN_PROGRAM_ID_ADDRESS)), - accounts(accounts) - { - auto data = Data(); - data.push_back(static_cast(type)); - encode64LE(value, data); - data.push_back(static_cast(decimals)); - this->data = 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()); - } -}; - -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(MessageHeader header, const std::vector
& accountKeys, Hash recentBlockhash, - const std::vector& instructions) - : header(header) - , accountKeys(accountKeys) - , recentBlockhash(recentBlockhash) - , instructions(instructions) { - compileInstructions(); - } - - // add an acount, to the corresponding bucket - void addAccount(const AccountMeta& account); - // compile the single accounts lists from the buckets - void compileAccounts(); - // compile the instructions; replace instruction accounts with indices - void compileInstructions(); - - // This constructor creates a default single-signer Transfer message - Message(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash) - : recentBlockhash(recentBlockhash) { - auto instruction = Instruction(std::vector{ - AccountMeta(from, true, false), - AccountMeta(to, false, false), - }, value); - this->instructions.push_back(instruction); - compileAccounts(); - } - - // This constructor creates a create_account_with_seed_and_delegate_stake message - // see delegate_stake() solana/programs/stake/src/stake_instruction.rs - Message(const Address& signer, const Address& stakeAddress, const Address& voteAddress, uint64_t value, - Hash recentBlockhash) - : recentBlockhash(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 - auto createAccountInstruction = Instruction(std::vector{ - AccountMeta(signer, true, true), - AccountMeta(stakeAddress, false, false), - AccountMeta(signer, true, true), - }, value, 200, stakeProgramId, voteAddress, 32, signer); - instructions.push_back(createAccountInstruction); - // initialize instruction - auto initializeInstruction = Instruction(Initialize, std::vector{ - AccountMeta(stakeAddress, false, false), - AccountMeta(sysvarRentId, false, true) - }, signer); - instructions.push_back(initializeInstruction); - // delegate_stake instruction - auto delegateInstruction = Instruction(DelegateStake, - std::vector{ - AccountMeta(stakeAddress, false, false), - AccountMeta(voteAddress, false, true), - AccountMeta(sysvarClockId, false, true), - AccountMeta(sysvarStakeHistoryId, false, true), - AccountMeta(stakeConfigId, false, true), - AccountMeta(signer, true, true), - }); - instructions.push_back(delegateInstruction); - this->instructions = instructions; - compileAccounts(); - } - - // This constructor creates a deactivate_stake message - Message(const Address& signer, const Address& stakeAddress, StakeInstruction type, Hash recentBlockhash) - : recentBlockhash(recentBlockhash) { - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto instruction = Instruction(Deactivate, std::vector{ - AccountMeta(stakeAddress, false, false), - AccountMeta(sysvarClockId, false, true), - AccountMeta(signer, true, false), - }); - this->instructions.push_back(instruction); - compileAccounts(); - } - - // This constructor creates a withdraw message, with the signer as the default recipient - Message(const Address& signer, const Address& stakeAddress, uint64_t value, StakeInstruction type, - Hash recentBlockhash) - : recentBlockhash(recentBlockhash) { - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); - auto instruction = Instruction(Withdraw, std::vector{ - AccountMeta(stakeAddress, false, false), - AccountMeta(signer, true, false), - AccountMeta(sysvarClockId, false, true), - AccountMeta(sysvarStakeHistoryId, false, true) - }, value); - this->instructions.push_back(instruction); - compileAccounts(); - } - - // This constructor creates a createAccount token message - // see create_associated_token_account() solana-program-library/associated-token-account/program/src/lib.rs - Message(const Address& signer, TokenInstruction type, const Address& otherMainAccount, const Address& tokenMintAddress, const Address& tokenAddress, Hash recentBlockhash) - : recentBlockhash(recentBlockhash) { - assert(type == TokenInstruction::CreateTokenAccount); - auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); - auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); - auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); - auto instruction = Instruction(type, 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), - }); - this->instructions.push_back(instruction); - compileAccounts(); - } - - // This constructor creates a transfer token message. - // see transfer_checked() solana-program-library/token/program/src/instruction.rs - Message(const Address& signer, TokenInstruction type, const Address& tokenMintAddress, - const Address& senderTokenAddress, const Address& recipientTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash) - : recentBlockhash(recentBlockhash) { - assert(type == TokenInstruction::TokenTransfer); - auto instruction = Instruction(type, std::vector{ - AccountMeta(senderTokenAddress, false, false), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(recipientTokenAddress, false, false), - AccountMeta(signer, true, false), - }, amount, decimals); - this->instructions.push_back(instruction); - compileAccounts(); - } - - // This constructor creates a createAndTransferToken message, combining createAccount and transfer. - Message(const Address& signer, const Address& recipientMainAddress, const Address& tokenMintAddress, - const Address& recipientTokenAddress, const Address& senderTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash) - : recentBlockhash(recentBlockhash) { - auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); - auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); - auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); - auto createInstruction = Instruction(TokenInstruction::CreateTokenAccount, 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), - }); - auto transferInstruction = Instruction(TokenInstruction::TokenTransfer, std::vector{ - AccountMeta(senderTokenAddress, false, false), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(recipientTokenAddress, false, false), - AccountMeta(signer, true, false), - }, amount, decimals); - this->instructions.push_back(createInstruction); - this->instructions.push_back(transferInstruction); - compileAccounts(); - } -}; - -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) - : message(Message(from, to, value, recentBlockhash)) { - 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 - -/// Wrapper for C interface. -struct TWSolanaTransaction { - TW::Solana::Transaction impl; -}; 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 2561ec3bf2d..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 - uint16_t checksum_expected = Crc::crc16(decoded.data(), 33); - uint16_t 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; + auto checksum_expected = Crc::crc16(decoded.data(), 33); + auto checksum_actual = static_cast((decoded[34] << 8) | decoded[33]); // unsigned short (little endian) + 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..50bfd9c4463 100644 --- a/src/Stellar/Entry.cpp +++ b/src/Stellar/Entry.cpp @@ -1,27 +1,105 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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" +#include "Base32.h" +#include "Coin.h" +#include "HexCoding.h" +#include "Crc.h" -using namespace TW::Stellar; -using namespace std; +namespace TW::Stellar { + +constexpr uint8_t ed25519SecretSeed = 18 << 3; // 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]); + }); +} + +// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/util/checksum.js#L1C1-L17C2 +bool verifyChecksum(const Data& expected, const Data& actual) { + if (expected.size() != actual.size()) { + return false; + } + + if (expected.empty()) { + return true; + } + + for (size_t i = 0; i < expected.size(); i++) { + if (expected[i] != actual[i]) { + return false; + } + } + + return true; +} + +// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/strkey.js#L290 +PrivateKey Entry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const { + Data decoded; + Base32::decode(privateKey, decoded); + + if (decoded.size() != 35) { + auto hexData = parse_hex(privateKey); + return PrivateKey(hexData, TW::curve(coin)); + } + + uint8_t versionByte = decoded[0]; + if (versionByte != ed25519SecretSeed) { + throw std::invalid_argument("Invalid version byte. Expected " + std::to_string(ed25519SecretSeed) + + ", got " + std::to_string(versionByte)); + } + + Data payload(decoded.begin(), decoded.end() - 2); + Data data(payload.begin() + 1, payload.end()); + Data checksum(decoded.end() - 2, decoded.end()); + + const auto expectedChecksum = Crc::crc16_xmodem(payload); + if (!verifyChecksum(expectedChecksum, checksum)) { + throw std::invalid_argument("invalid checksum"); + } + + return PrivateKey(data, TW::curve(coin)); +} + +} // namespace TW::Stellar diff --git a/src/Stellar/Entry.h b/src/Stellar/Entry.h index ed070efadf1..a8966c5795e 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,12 +10,15 @@ 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 const std::vector coinTypes() const { return {TWCoinTypeStellar, TWCoinTypeKin}; } - 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; + PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey) const override; }; } // namespace TW::Stellar diff --git a/src/Stellar/Signer.cpp b/src/Stellar/Signer.cpp index ba11047c747..1718ff8b571 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()), TWCurveED25519); + 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()); @@ -41,7 +39,7 @@ std::string Signer::sign() const noexcept { auto hash = Hash::sha256(encodedWithHeaders); auto data = Data(hash.begin(), hash.end()); - auto sign = key.sign(data, TWCurveED25519); + auto sign = key.sign(data); auto signature = Data(); signature.insert(signature.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,42 +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::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::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; } } @@ -144,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 { @@ -165,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.h b/src/THORChain/Entry.h new file mode 100644 index 00000000000..863117825d0 --- /dev/null +++ b/src/THORChain/Entry.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" +#include "../Cosmos/Entry.h" + +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 final : public Cosmos::Entry { +}; + +} // namespace TW::THORChain diff --git a/src/THORChain/Swap.cpp b/src/THORChain/Swap.cpp new file mode 100644 index 00000000000..260337675e3 --- /dev/null +++ b/src/THORChain/Swap.cpp @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Swap.h" + +#include "Coin.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/ABI/Function.h" +#include "Ethereum/Address.h" +#include "uint256.h" +#include "../proto/Ethereum.pb.h" +// BNB +#include "Binance/Address.h" +#include "../proto/Binance.pb.h" + +#include + +/* + * References: + * https://gitlab.com/thorchain/asgardex-common/asgardex-util + */ + +namespace TW::THORChainSwap { + +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; +} + +TWCoinType chainCoinType(Chain chain) { + switch (chain) { + 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 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"; + } +} + +bool validateAddress(Chain chain, const std::string& address) { + return TW::validateAddress(chainCoinType(chain), 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, mToAddress)) { + return {.status_code = static_cast(Proto::ErrorCode::Error_Invalid_to_address), .error = "Invalid to address"}; + } + + uint256_t fromAmountNum = uint256_t(mFromAmount); + const auto memo = this->buildMemo(shortened); + + switch (fromChain) { + 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); + } + + if (mAffFeeAddress.has_value() || mAffFeeRate.has_value() || mExtraMemo.has_value()) { + memo << ":"; + if (mAffFeeAddress.has_value()) { + memo << mAffFeeAddress.value(); + } + if (mAffFeeRate.has_value() || mExtraMemo.has_value()) { + memo << ":"; + if (mAffFeeRate.has_value()) { + memo << mAffFeeRate.value(); + } + if (mExtraMemo.has_value()) { + memo << ":" << mExtraMemo.value(); + } + } + } + + return memo.str(); +} + +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 ... + auto coinType = chainCoinType(fromChain); + input.set_hash_type(Bitcoin::hashTypeForCoin(coinType)); + input.set_byte_fee(1); + input.set_use_max_amount(false); + // private_key[] + // utxo[] + // scripts[] + // ... end + + 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 {.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; + + // 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; + } + { + 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)}; +} + +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(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 (!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); + + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(0)); + input.set_chain_id(chainId.data(), chainId.size()); + const auto nonce = store(uint256_t(0)); + input.set_nonce(nonce.data(), nonce.size()); + const auto gasPrice = store(uint256_t(0)); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + const auto gasLimit = store(uint256_t(0)); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_private_key(""); + // ... end + + 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 {.out = std::move(out)}; +} + +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* msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + + 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 {.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::THORChainSwap diff --git a/src/THORChain/Swap.h b/src/THORChain/Swap.h new file mode 100644 index 00000000000..94f35c5b8a3 --- /dev/null +++ b/src/THORChain/Swap.h @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "proto/THORChainSwap.pb.h" +#include "uint256.h" + +#include +#include +#include +#include + +namespace TW::THORChainSwap { + +/// Supported blockchains +enum Chain { + THOR = 0, + BTC = 1, + ETH = 2, + BNB = 3, + DOGE = 4, + BCH = 5, + LTC = 6, + ATOM = 7, + AVAX = 8, + BSC = 9, +}; + +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: + 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::THORChainSwap diff --git a/src/THORChain/TWSwap.cpp b/src/THORChain/TWSwap.cpp new file mode 100644 index 00000000000..eb5ef647a02 --- /dev/null +++ b/src/THORChain/TWSwap.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "Swap.h" +#include "proto/THORChainSwap.pb.h" +#include + +using namespace TW; + +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(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize input proto"); + auto outputData = TW::data(outputProto.SerializeAsString()); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); + } + + const auto fromChain = inputProto.from_asset().chain(); + const auto toChain = inputProto.to_asset().chain(); + 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(static_cast(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 (errorCode != 0) { + // error + outputProto.mutable_error()->set_code(static_cast(errorCode)); + outputProto.mutable_error()->set_message(error); + } else { + // no error + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::OK); + outputProto.mutable_error()->set_message(""); + + switch (fromChain) { + 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 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 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(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 = TW::data(outputProto.SerializeAsString()); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); +} diff --git a/src/TON/Address.cpp b/src/TON/Address.cpp deleted file mode 100644 index 9f36a9634a2..00000000000 --- a/src/TON/Address.cpp +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "Cell.h" -#include "Contract.h" - -#include "../Base64.h" -#include "../Crc.h" -#include "../HexCoding.h" - -#include -#include -#include -#include - -using namespace TW; -using namespace TW::TON; -using namespace std; - -bool Workchain::isValid(WorkchainId_t workchainId) { - return (workchainId == MasterChainId || workchainId == BasicChainId); -} - -Address::Address(const std::string& address) { - bool valid = parseAddress(address, *this); - - // Ensure address is valid - if (!valid) { - throw std::invalid_argument("Invalid address data"); - } -} - -Address::Address(const PublicKey& publicKey, WorkchainId_t workchain) { - // Steps: a StateInit account state cell is created (containing code and data), its hash is taken, - // and new address is derived from the hash - - if (publicKey.type != TWPublicKeyTypeED25519) { - throw std::invalid_argument("Invalid public key type"); - } - - Cell stateInit = Contract::createStateInit(publicKey); - - // compute hash of stateInit - auto hash = stateInit.hash(); - - // fill members - workchainId = workchain; - addrBytes = hash; - isBounceable = true; - isTestOnly = false; -} - -bool Address::isValid(const std::string& address, WorkchainId_t workchain) { - Address addr; - bool isValid = parseAddress(address, addr); - if (addr.workchainId != workchain) { - // not the right chain, refuse - return false; - } - return isValid; -} - -bool Address::parseAddress(const std::string& address, Address& addr_inout) { - // try several formats, start with the common one, stop if one matches - bool isValidUser = parseUserAddress(address, addr_inout); - if (isValidUser) { - return true; - } - bool isValidRaw = parseRawAddress(address, addr_inout); - return isValidRaw; -} - -bool Address::parseRawAddress(const std::string& addressStr_in, Address& addr_inout) { - // split by colon ':' - auto colidx = addressStr_in.find(':'); - - if (colidx == std::string::npos) { - // no colon, invalid - return false; - } - if (colidx < 1 || colidx >= addressStr_in.length() - 1) { - // colon in wrong position - return false; - } - std::string workchainStr = addressStr_in.substr(0, colidx); - std::string addressStr = addressStr_in.substr(colidx + 1, addressStr_in.length() - colidx - 1); - - WorkchainId_t workchainId; - try { - workchainId = std::stoi(workchainStr); - } catch (const std::exception& e) { - // workchain ID is invalid (not a decimal number) - return false; - } - if (!Workchain::isValid(workchainId)) { - // invalid workchain ID - return false; - } - addr_inout.workchainId = workchainId; - - if (addressStr.length() != AddressLength * 2) { - // wrong length of address part - return false; - } - - addr_inout.addrBytes = parse_hex(addressStr); - - addr_inout.isBounceable = true; - addr_inout.isTestOnly = false; - - return true; -} - -bool Address::parseUserAddress(const std::string& addressStr_in, Address& addr_inout) { - Data bytes; - try { - bytes = Base64::decodeBase64Url(addressStr_in); - } catch (const std::exception& ex) { - return false; - } - - // check length -- 1 byte flags, 1 byte chainId, 32 hash, 2 bytes crc - if (bytes.size() != 2 + AddressLength + 2) { - return false; - } - assert(bytes.size() >= 2 + AddressLength + 2); - - addr_inout.isBounceable = true; - addr_inout.isTestOnly = false; - byte tagByte = bytes[0]; - if (tagByte >= 0x80) { - // test-only - tagByte -= 0x80; - addr_inout.isTestOnly = true; - } - if (tagByte >= 0x40) { - // not bounceable - tagByte -= 0x40; - addr_inout.isBounceable = false; - } - if (tagByte != 0x11) { - // invalid tag - return false; - } - - byte chainId = bytes[1]; - switch (chainId) { - case 0x00: - addr_inout.workchainId = Workchain::BasicChainId; - break; - case 0xff: - addr_inout.workchainId = Workchain::MasterChainId; - break; - default: - // invalid chain - return false; - } - - // 32 bytes address - addr_inout.addrBytes = Data(AddressLength); - std::copy(bytes.begin() + 2, bytes.begin() + 2 + AddressLength, addr_inout.addrBytes.begin()); - - // check CRC - uint16_t crcGiven = static_cast(bytes[2 + AddressLength] << 8) + bytes[2 + AddressLength + 1]; - uint16_t crcComputed = Crc::crc16(bytes.data(), 2 + AddressLength); - if (crcGiven != crcComputed) { - // CRC mismatch - return false; - } - - return true; -} - -std::string Address::string() const { - Data bytes; - // tag - byte tag = 0x11 + 0x40 * (isBounceable ? 0 : 1) + 0x80 * (isTestOnly ? 1 : 0); - bytes.push_back(tag); - byte chainId = 0; - switch (workchainId) { - case Workchain::BasicChainId: - chainId = 0x00; - break; - case Workchain::MasterChainId: - chainId = 0xff; - break; - default: - chainId = 0x01; - break; // invalid - } - bytes.push_back(chainId); - append(bytes, addrBytes); - // add crc checksumS - uint16_t crc = Crc::crc16(bytes.data(), static_cast(bytes.size())); - bytes.push_back(crc >> 8); - bytes.push_back(crc & 0xff); - - // base64 encode, use Base64Url format (safer for wallets, explorer) - std::string addrEnc = Base64::encodeBase64Url(bytes); - return addrEnc; -} - -std::string Address::stringRaw() const { - std::stringstream ss; - ss << workchainId << ':' << hex(addrBytes); - return ss.str(); -} diff --git a/src/TON/Address.h b/src/TON/Address.h deleted file mode 100644 index a8d8c7ed9aa..00000000000 --- a/src/TON/Address.h +++ /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. - -#pragma once - -#include "../PublicKey.h" - -#include - -namespace TW::TON { - -using WorkchainId_t = int32_t; - -/// Workchain ID, currently supported: masterchain: -1, basic workchain: 0 -class Workchain { - public: - static constexpr WorkchainId_t MasterChainId = -1; - static constexpr WorkchainId_t BasicChainId = 0; - static constexpr WorkchainId_t InvalidChainId = 0x80000000; - - // The default workchain ID - static WorkchainId_t defaultChain() { return BasicChainId; } - static bool isValid(WorkchainId_t workchainId_in); -}; - -/// TON smart contract address, also account address -class Address { - public: - WorkchainId_t workchainId; - - static const uint8_t AddressLength = 32; - - // Address: 256 bits (for chains -1 and 0) - TW::Data addrBytes; - - bool isBounceable{true}; - bool isTestOnly{false}; - - /// Initializes a TON address with a string representation, either raw or user friendly - explicit Address(const std::string& address); - - /// Initializes a TON address with a public key. WorkchainId is optional, Basic chain by default. - explicit Address(const PublicKey& publicKey, WorkchainId_t workchain = Workchain::defaultChain()); - - /// Determines whether a string makes a valid address, in any format - static bool isValid(const std::string& address, WorkchainId_t workchain = Workchain::defaultChain()); - - /// Returns a string representation of the address (user friendly format) - std::string string() const; - - /// Returns a string representation of the address, raw format - std::string stringRaw() const; - - private: - /// Empty constructor - Address() = default; - - static bool parseAddress(const std::string& addressStr_in, Address& addr_inout); - static bool parseRawAddress(const std::string& addressStr_in, Address& addr_inout); - // Accepts user-friendly base64 format, also accepts Base64Url format - static bool parseUserAddress(const std::string& addressStr_in, Address& addr_inout); -}; - -inline bool operator==(const Address& lhs, const Address& rhs) { - return (lhs.workchainId == rhs.workchainId && lhs.addrBytes == rhs.addrBytes && - lhs.isBounceable == rhs.isBounceable && lhs.isTestOnly == rhs.isTestOnly); -} - -} // namespace TW::TON diff --git a/src/TON/Cell.cpp b/src/TON/Cell.cpp deleted file mode 100644 index 8a7f96b7771..00000000000 --- a/src/TON/Cell.cpp +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "Cell.h" -#include "../Hash.h" -#include "../HexCoding.h" - -#include // for boost::crc_32_type - -#include -#include -#include -#include - -namespace TW::TON { - -using namespace TW; -using namespace std; - -Slice::Slice() { - _sizeBits = 0; -} - -Slice::Slice(const Slice& from) : _data(from._data), _sizeBits(from._sizeBits) {} - -Slice Slice::createFromData(const Data& data) { - if (data.size() == 0) { - throw std::runtime_error("empty data"); - } - Slice s; - s.appendBytes(data); - return s; -} - -Slice Slice::createFromHex(std::string const& dataStr) { - Data data = parse_hex(dataStr); - return createFromData(data); -} - -Slice Slice::createFromBits(const Data& data, size_t sizeBits) { - if (sizeBits == 0) { - throw std::runtime_error("empty data"); - } - Slice s; - s.appendBits(data, sizeBits); - return s; -} - -Slice Slice::createFromBitsStr(std::string const& dataStr, size_t sizeBits) { - Data data = parse_hex(dataStr); - return createFromBits(data, sizeBits); -} - -void Slice::appendBytes(const Data& data_in) { - int diffBits = (int)(size() * 8 - _sizeBits); - assert(diffBits >= 0 && diffBits <= 7); - if (diffBits == 0) { - // at byte-boundary - append(_data, data_in); - _sizeBits = _data.size() * 8; - return; - } - // not at byte boundary, bit operations needed - return appendBits(data_in, data_in.size() * 8); -} - -void Slice::appendBits(const Data& data_in, size_t sizeBits) { - if (sizeBits == 0) { return; } - // compute number of bytes needed - size_t size1 = sizeBits / 8 + (((sizeBits & 7) == 0) ? 0 : 1); - if (data_in.size() != size1) { - // wrong number of bytes/bits - throw std::runtime_error("mismatch between bytes and bits size"); - } - assert(data_in.size() == size1); - if ((_sizeBits & 7) == 0) { - appendBitsAligned(data_in, sizeBits); - return; - } - // all new bits have to be shifted - appendBitsNotAligned(data_in, sizeBits); -} - -void Slice::appendBitsAligned(const Data& data_in, size_t sizeBits) { - // old is aligned - assert((_sizeBits & 7) == 0); - size_t size1 = sizeBits / 8 + (((sizeBits & 7) == 0) ? 0 : 1); - assert(data_in.size() == size1); - int diffBitsNew = (int)(size1 * 8 - sizeBits); - assert(diffBitsNew >= 0 && diffBitsNew <= 7); - if (diffBitsNew == 0) { - // both old and new are aligned, no bit operations needed - return appendBytes(data_in); - } - // old is aligned, but new is not - assert(diffBitsNew >= 1 && diffBitsNew <= 7); - // bytes, except last - if (size1 > 1) { - append(_data, TW::data(data_in.data(), size1 - 1)); - } - // last byte - byte last = data_in[size1 - 1]; - // zero unused bits - last &= ~((1 << (byte)diffBitsNew) - 1); - _data.push_back(last); - _sizeBits += sizeBits; - // set highest unused bit to 1 - int diffBits = (int)(size() * 8 - _sizeBits); - assert(diffBits >= 1 && diffBits <= 7); - _data[_data.size() - 1] |= (1 << (byte)(diffBits - 1)); -} - -void Slice::appendBitsNotAligned(const Data& data_in, size_t sizeBits) { - // old is not aligned - assert((_sizeBits & 7) != 0); - size_t size1 = sizeBits / 8 + (((sizeBits & 7) == 0) ? 0 : 1); - assert(data_in.size() == size1); - int diffBitsNew = (int)(size1 * 8 - sizeBits); - assert(diffBitsNew >= 0 && diffBitsNew <= 7); - // all new bits have to be shifted - size_t diffBitsOld = size() * 8 - _sizeBits; - assert(diffBitsOld >= 1 && diffBitsOld <= 7); - byte oldMask = (byte)((byte)0xff << (byte)diffBitsOld); - size_t newSize = data_in.size(); - for (size_t newIdx = 0; newIdx < newSize; ++newIdx) { - // first part -- affects current last byte in old - byte newByte = data_in[newIdx]; - if (newIdx == newSize - 1) { - // last byte in new - // zero unused bits - newByte &= ~((1 << (byte)diffBitsNew) - 1); - _sizeBits += (8 - diffBitsNew); - } else { - _sizeBits += 8; - } - byte first = newByte >> (byte)(8 - diffBitsOld); - _data[_data.size() - 1] = (_data[_data.size() - 1] & oldMask) | first; - // second part -- add as new byte - byte second = (byte)(newByte << (byte)diffBitsOld); - //cerr << (int)first << " " << (int)second << endl; - if (_sizeBits > size() * 8) { - _data.push_back(second); - } - // set highest unused bit to 1 - int diffBits = (int)(size() * 8 - _sizeBits); - assert(diffBits >= 0 && diffBits <= 7); - if (diffBits > 0) { - _data[_data.size() - 1] |= (1 << (byte)(diffBits - 1)); - } - } -} - -std::string Slice::asBytesStr() const { - return hex(_data); -} - -void Slice::serialize(TW::Data& data_inout) { - append(data_inout, _data); -} - -Data Slice::hash() const { - return Hash::sha256(_data); -} - - -Cell::Cell(const Cell& from) : _cells(from._cells), _slice(from._slice) {} - -void Cell::setSlice(Slice const& slice) { - _slice = slice; -} - -void Cell::setSliceBytes(const Data& data) { - Slice s = Slice::createFromData(data); - setSlice(s); -} - -void Cell::setSliceBytesStr(std::string const& sliceStr) { - Slice s = Slice::createFromHex(sliceStr); - setSlice(s); -} - -void Cell::setSliceBitsStr(std::string const& sliceStr, size_t sizeBits) { - Slice s = Slice::createFromBitsStr(sliceStr, sizeBits); - setSlice(s); -} - -void Cell::addCell(std::shared_ptr const& cell) { - if (cellCount() >= max_cells) { - throw std::runtime_error("too many cells"); - } - _cells.push_back(cell); -} - -std::string Cell::toString() const { - std::stringstream s; - s << "Cell: "; - if (_slice.size() == 0) { - s << " no slice"; - } else { - s << " slice: " << _slice.asBytesStr(); - } - if (cellCount() == 0) { - s << ", no children"; - } else { - s << ", " << cellCount() << " children"; - int cnt = 1; - for (auto i = _cells.begin(), n = _cells.end(); i != n; ++i, ++cnt) { - s << std::endl << " child " << cnt << ": " << i->get()->toString(); - } - } - return s.str(); -} - -Data Cell::hash() const { - // Need to copy data together into a contiguous area - Data hashData; - // number of children - hashData.push_back(static_cast(cellCount())); - // number of hex digits - size_t bits = _slice.sizeBits(); // may be 0 - hashData.push_back(d2(bits)); - // data - if (_slice.size() > 0) { - append(hashData, _slice.data()); - } - // children - if (cellCount() > 0) { - for (auto i = _cells.begin(), n = _cells.end(); i != n; ++i) { - hashData.push_back(0); - hashData.push_back(0); - } - for (auto i = _cells.begin(), n = _cells.end(); i != n; ++i) { - auto childHash = i->get()->hash(); - append(hashData, childHash); - } - } - // compute hash - return Hash::sha256(hashData); -} - -Cell::SerializationInfo Cell::getSerializationInfo(SerializationMode mode) const { - SerializationInfo info = SerializationInfo(); - size_t rawDataSize = serializedOwnSize(); - for (auto c: _cells) { - rawDataSize += c->serializedOwnSize(); - } - int intRefs = (int)cellCount(); - uint8_t refSize = 1; - while (cellCount() >= ((size_t)1 << (refSize * 8))) { ++refSize; } - assert(refSize >= 1 && refSize <= 8); - size_t hashes = 0; - size_t dataBytesAdj = rawDataSize + (size_t)intRefs * refSize + hashes; - size_t maxOffset = (mode & SerializationMode::WithCacheBits) ? dataBytesAdj * 2 : dataBytesAdj; - int offsetSize = 0; - while (maxOffset >= (1ULL << (offsetSize * 8))) { ++offsetSize; } - if (refSize > 4 || offsetSize > 8) { return info; } - - info.refByteSize = refSize; - info.offsetByteSize = 1; - info.rootCount = 1; - info.cellCount = info.rootCount + (int)cellCount(); // including self/roots - info.hasCrc32c = mode & SerializationMode::WithCRC32C; - int crcSize = info.hasCrc32c ? 4 : 0; - int rootCount = 0; - unsigned long rootsOffset = 4 + 1 + 1 + 3 * info.refByteSize + info.offsetByteSize; - unsigned long indexOffset = rootsOffset + rootCount * info.refByteSize; - unsigned long dataOffset = indexOffset; - //if (info.has_index) { - //info.data_offset += (long long)cell_count * info.offset_byte_size; - //} - // Magic num idx 68ff65f3 idxCrc32c acc3a728 generic b5ee9c72 - info.magic = parse_hex("b5ee9c72"); - info.dataSize = dataBytesAdj; - info.totalSize = dataOffset + dataBytesAdj + crcSize; - return info; -} - -size_t Cell::serializedOwnSize(bool withHashes) const { - if (withHashes) { throw std::invalid_argument("Cell::serializedOwnSize: WithHashes not supported"); } - return _slice.size() + 2; // bits/8 rounded up + 2 -} - -size_t Cell::serializedSize(SerializationMode mode) const { - auto info = getSerializationInfo(mode); - size_t ss = 0; - ss += 4; // magic - ss += 5; // byte1, offsetByteSize, cellCount, rootCount, - ss += info.offsetByteSize; // dataSize - ss += info.rootCount; // roots - - ss += serializedOwnSize(false); - ss += cellCount(); // cell refs - - // child cells - for(auto c: _cells) { - ss += c->serializedOwnSize(false); - } - - if (mode & SerializationMode::WithCRC32C) { - ss += 4; - } - return ss; -} - -void Cell::serializeOwn(TW::Data& data_inout, bool withHashes) { - if (withHashes) { throw std::invalid_argument("Cell::serializedOwnSize: WithHashes not supported"); } - //auto info = getSerializationInfo(mode); - // slice - data_inout.push_back((byte)cellCount()); - data_inout.push_back(d2(_slice.sizeBits())); - append(data_inout, _slice.data()); -} - -void Cell::serialize(TW::Data& data_inout, SerializationMode mode) { - if (mode != SerializationMode::None && mode != SerializationMode::WithCRC32C) { - throw std::invalid_argument("Cell::serialize: Mode " + std::to_string((int)mode) + " not supported"); - } - // save current start position - size_t startIdx = data_inout.size(); - auto info = getSerializationInfo(mode); - - // magic - append(data_inout, info.magic); - - byte byte1 = 0; - //if (info.hasIndex) { byte |= 1 << 7; } - if (info.hasCrc32c) { byte1 |= 1 << 6; } - //if (info.has_cache_bits) { byte |= 1 << 5; } - // 3, 4 - flags - if (info.refByteSize < 1 || info.refByteSize > 7) { - //cerr << info.refByteSize << endl; - return; - } - byte1 |= static_cast(info.refByteSize); - data_inout.push_back(byte1); - data_inout.push_back((byte)info.offsetByteSize); - data_inout.push_back((byte)info.cellCount); - data_inout.push_back((byte)info.rootCount); - data_inout.push_back(0); - data_inout.push_back((byte)info.dataSize); // offset - data_inout.push_back(0); // roots - - // own cell (slice) - serializeOwn(data_inout, false); - // cell refs? - uint8_t cidx = 0; - for(auto c: _cells) { - ++cidx; - data_inout.push_back(cidx); - } - - // child cells - for(auto c: _cells) { - c->serializeOwn(data_inout, false); - } - - if (mode & SerializationMode::WithCRC32C) { - // CRC32-C, of the serialized data so far - uint32_t crc = computeCrc(data_inout.data() + startIdx, data_inout.size() - startIdx); - data_inout.push_back(crc & 0x000000FF); - data_inout.push_back((crc & 0x0000FF00) >> 8); - data_inout.push_back((crc & 0x00FF0000) >> 16); - data_inout.push_back((crc & 0xFF000000) >> 24); - } -} - -uint32_t Cell::computeCrc(const byte* data, size_t len) { - // CRC32-C - using crc_32c_type = boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true>; - crc_32c_type result; - result.process_bytes((const void*)data, len); - return result.checksum(); -} - -byte Cell::d2(size_t bits) { - return (byte)((bits / 8) * 2 + (((bits & 7) == 0) ? 0 : 1)); -} - -} // namespace TW::TON diff --git a/src/TON/Cell.h b/src/TON/Cell.h deleted file mode 100644 index 56475080a19..00000000000 --- a/src/TON/Cell.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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::TON { - -/** - * Simplified implementation of TON Slice, Cell, etc. structures, for minimal represenatation and - * hash computation. - */ - -/// Represents a Slice. -class Slice { -public: - Slice(); - Slice(const Slice& from); - virtual ~Slice() {} - /// Constructor methods. May throw. - static Slice createFromData(const Data& data); - static Slice createFromHex(std::string const& dataStr); - static Slice createFromBits(const Data& data, size_t sizeBits); - static Slice createFromBitsStr(std::string const& dataStr, size_t sizeBits); - void appendBytes(const Data& data_in); - /// Append bytes, possible incomplete bytes at the end. SizeBits should be equal to data.size() * 8, or less by at most 7. - void appendBits(const TW::Data& data_in, size_t sizeBits); - Data data() const { return _data; } - inline size_t size() const { return _data.size(); } - size_t sizeBits() const { return _sizeBits; } - std::string asBytesStr() const; - void serialize(TW::Data& data_inout); - Data hash() const; - -protected: - void appendBitsAligned(const TW::Data& data_in, size_t sizeBits); - void appendBitsNotAligned(const TW::Data& data_in, size_t sizeBits); - -private: - Data _data; - size_t _sizeBits; -}; - -/// Represents a Cell, with references to other cells. -class Cell { -public: - enum SerializationMode: uint8_t { - None = 0, - WithIndex = 1, - WithCRC32C = 2, - WithTopHash = 4, - WithIntHashes = 8, - WithCacheBits = 16, - max = 31 - }; - class SerializationInfo { - public: - std::vector magic; - int rootCount; - int cellCount; - //int absent_count; - int refByteSize; - int offsetByteSize; - //bool valid; - //bool has_index; - //bool has_roots{false}; - bool hasCrc32c; - //bool has_cache_bits; - unsigned long dataSize; - unsigned long totalSize; - //unsigned long rootsOffset, dataOffset, indexOffset - }; - -public: - Cell() {} - Cell(const Cell& from); - void setSlice(Slice const& slice); - /// Convenience method for setting slice directly from bytes. May throw. - void setSliceBytes(const Data& data); - /// Convenience method for setting slice directly from hex string. May throw. - void setSliceBytesStr(std::string const& sliceStr); - /// Convenience method for setting slice directly from bits. May throw. - void setSliceBitsStr(std::string const& sliceStr, size_t sizeBits); - void addCell(std::shared_ptr const& cell); - size_t cellCount() const { return _cells.size(); } - Slice const& getSlice() const { return _slice; } - const std::vector>& getCells() const { return _cells; } - std::string toString() const; - Data hash() const; - /// Serialized size of this cell only, without children - size_t serializedOwnSize(bool withHashes = false) const; - /// Serialized size, including children - size_t serializedSize(SerializationMode mode = SerializationMode::None) const; - /// Serialize this cell only, without children - void serializeOwn(TW::Data& data_inout, bool withHashes = false); - /// Serialize this cell, including children - void serialize(TW::Data& data_inout, SerializationMode mode = SerializationMode::None); - static const size_t max_cells = 4; - /// second byte in length - static byte d2(size_t bits); -private: - /// Compute 4-byte CRC32-C checksum, used in serialization - static uint32_t computeCrc(const byte* data, size_t len); - // Prepare serialization properties - SerializationInfo getSerializationInfo(SerializationMode mode = SerializationMode::None) const; - -private: - std::vector> _cells; - Slice _slice; -}; - -} // namespace TW::TON diff --git a/src/TON/Contract.cpp b/src/TON/Contract.cpp deleted file mode 100644 index f31a60f3f6b..00000000000 --- a/src/TON/Contract.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 "Contract.h" -#include "../Data.h" -#include "../HexCoding.h" - -#include - -namespace TW::TON { - -using namespace TW; - -//static const char* walletContract1 = "FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54"; -//static const char* walletContract2 = "FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54"; -// Obtained from ton-lite-client new-wallet.fif, len=81 -static const char* walletContract3 = "FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54"; -// Obtained from released testnet desktop wallet, len=96 -//static const char* walletContract4 = "FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F2608308D71820D31FD31FD31FF82313BBF263ED44D0D31FD31FD3FFD15132BAF2A15144BAF2A204F901541055F910F2A3F8009320D74A96D307D402FB00E8D101A4C8CB1FCB1FCBFFC9ED54"; -Data Contract::walletContractDefault() { - return parse_hex(walletContract3); -} - -Cell Contract::createStateInit(const PublicKey& pubkey) { - // smart contract code -- constant - auto ccode = std::make_shared(); - ccode->setSliceBytes(walletContractDefault()); - - // data: 4 byte serial num (0), 32 byte public key - Data data; - append(data, Data(4)); - append(data, pubkey.bytes); - assert(data.size() == 4 + 32); - auto cdata = std::make_shared(); - cdata->setSliceBytes(data); - - Cell stateInit; - stateInit.setSliceBitsStr("30", 5); // b{00110} - stateInit.addCell(ccode); - stateInit.addCell(cdata); - - return stateInit; -} - -} // namespace TW::TON diff --git a/src/TON/Contract.h b/src/TON/Contract.h deleted file mode 100644 index 33f00ad8430..00000000000 --- a/src/TON/Contract.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 "Cell.h" -#include "../Data.h" -#include "../PublicKey.h" - -namespace TW::TON { - -using namespace TW; - -/// Smart contract support, limited -class Contract { -public: - /// Return the (compiled) smart contract of a wallet account - static Data walletContractDefault(); - - /// Create a StateInit structure for account initialization - static Cell createStateInit(const PublicKey& pubkey); -}; - -} // namespace TW::TON diff --git a/src/TON/Entry.cpp b/src/TON/Entry.cpp deleted file mode 100644 index cfe2a958a19..00000000000 --- a/src/TON/Entry.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 "Entry.h" - -#include "Address.h" -//#include "Signer.h" - -#include - -using namespace TW::TON; -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(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - // not implemented yet -} diff --git a/src/TON/Entry.h b/src/TON/Entry.h deleted file mode 100644 index f83b0c75157..00000000000 --- a/src/TON/Entry.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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::TON { - -/// Entry point for implementation of TON 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 const std::vector coinTypes() const { return {TWCoinTypeTON}; } - 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::TON diff --git a/src/TON/README.md b/src/TON/README.md deleted file mode 100644 index c93daf72cac..00000000000 --- a/src/TON/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# TON Blockchain Support, Examples - -## Smart Contract - -The TON network utilizes smart contract, defined in an own VM langugage. -Wallets and light clients do not need to understand smart contract language, but they have to deal with them to some extent. - -## Cell Representation - -In the TON network data is organized in Slice/Cell structures. They are just containers for different type of data. Specifically, non-byte bundary bit streams are a special feautre. Cells can be be organized recursively, can be hashed, serialized and deserialzed. - -## Cell Examples - -Sample slice usage: - - Slice s1 = Slice::createFromHex("123456")); // or createFromData(parse_hex("123456")); - std::cout << "slice, size: " << s1.size() << " data: " << s1.asBytesStr() << " hash: " << hex(s1.hash()) << std::endl; - -Outputs: - - slice, size: 3 data: 123456 hash: - bf7cbe09d71a1bcc373ab9a764917f730a6ed951ffa1a7399b7abd8f8fd73cb4 - -Sample Cell usage, bytes slice with no children: - - Cell cell; - cell.setSliceBytesStr("123456"); - ASSERT_EQ("09a6e1fb711077014e7cae82826707cace55a493501a16144cfd83fca0c8e6d6", hex(cell.hash())); - -Fift equivalent: - dup ."Data: " asBytesStr()); // the 5 bits padded with 100 - ASSERT_EQ("ea23ba20e2f88a07af38948bfaef741741f5a464df43e87067c901e537d1c44f", hex(cell.hash())); - -Fift equivalent: - dup ."Data: " (); - c1->setSliceBytesStr("123456"); - Cell c; - c.addCell(c1); - ASSERT_EQ("993e6d53d70fb05f052ce45ac751e24abd7d43e358b297694cfc19bbc796141c", hex(c.hash())); - -Fift equivalent: - - - dup ."Data: " (); - ccode->setSliceBytesStr("FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54"); - auto cdata = std::make_shared(); - cdata->setSliceBytesStr("00000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"); - c.addCell(ccode); - c.addCell(cdata); - ASSERT_EQ("60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0", hex(c.hash())); - -Fift equivalent: - - - - dup ."Data: " -#include -#include - -namespace TW::TON { - -using namespace TW; -using namespace std; - -/* -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); - - // ... - - return protoOutput; -} - -Data Signer::sign(const PrivateKey &privateKey, Transaction &transaction) noexcept { - // ... - - return Data(); -} -*/ - -Data Signer::sign(const PrivateKey& privateKey, const Data& message) noexcept { - auto signature = privateKey.sign(message, TWCurveED25519); - //cerr << "sign " << signature.size() << " " << hex(signature) << endl; - return signature; -} - -TW::Data Signer::buildInitMessage(const PrivateKey& privkey) { - // create pubkey - PublicKey pubkey = privkey.getPublicKey(TWPublicKeyTypeED25519); - // create address - Address address = Address(pubkey); - byte chainId = (byte)address.workchainId; - - // create msg - Data msgData = parse_hex("00000000"); //data(string("TRUST")); - - Cell msgc; - msgc.setSliceBytes(msgData); - auto msgHash = msgc.hash(); - - // sign - auto signature = sign(privkey, msgHash); - - auto extMsg = buildInitMessage(chainId, pubkey, signature, msgData); - return extMsg; -} - -Data Signer::buildInitMessage( - byte chainId, const PublicKey& pubkey, const Data& signature, const Data& msg -) { - Cell stateInit = Contract::createStateInit(pubkey); - assert(stateInit.cellCount() == 2); - - auto stateInitHash = stateInit.hash(); - - // build cell for message - Slice s; - s.appendBits(parse_hex("88"), 7); // 7 bits b{1000100} - // address (chainId + hash) - s.appendBytes(data(&chainId, 1)); - s.appendBytes(stateInitHash); - // 12 bits: b{000010} b{00110} b{0} - s.appendBits(parse_hex("08c0"), 12); - s.appendBytes(signature); - s.appendBytes(msg); - //cerr << s.size() << " " << s.asBytesStr() << endl; - - Cell c; - c.setSlice(s); - c.addCell(stateInit.getCells()[0]); - c.addCell(stateInit.getCells()[1]); - - // serialize it - Data ss2; - c.serialize(ss2, Cell::SerializationMode::WithCRC32C); - - return ss2; -} - -} // namespace TW::TON diff --git a/src/TON/Signer.h b/src/TON/Signer.h deleted file mode 100644 index 6e078e22104..00000000000 --- a/src/TON/Signer.h +++ /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. - -#pragma once - -#include "Address.h" -#include "../Data.h" -#include "../PublicKey.h" -#include "../PrivateKey.h" - -#include - -namespace TW::TON { - -/// Helper class that performs TON 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 the given transaction. - //static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; - - /// sign a message - static TW::Data sign(const PrivateKey& privateKey, const Data& message) noexcept; - /// Build the external message for account initialization - static TW::Data buildInitMessage(const PrivateKey& privkey); - -protected: - /// Build the external message for account initialization - static TW::Data buildInitMessage( - byte chainId, const PublicKey& pubkey, const TW::Data& signature, const TW::Data& msg - ); -}; - -} // namespace TW::TON - -/// Wrapper for C interface. -struct TWTONSigner { - TW::TON::Signer impl; -}; 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..6be3edd6442 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 + return PrivateKey(pk, TWCurveSECP256k1); +} + +} // 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 2a705849f32..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,14 +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 const std::vector coinTypes() const { return {TWCoinTypeTezos}; } - 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..18e0bfb79d0 --- /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}}, .anots{}}; + return PrimValue{.prim = "Pair", .args{{address}, {primTransferInfos}}, .anots{}}; +} + +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}}, .anots{}}; + MichelsonValue::MichelsonVariant to = StringValue{.string = txTransferInfos.to()}; + MichelsonValue::MichelsonVariant txs = MichelsonValue::MichelsonArray{PrimValue{.prim = "Pair", .args{{to}, {primTransferInfos}}, .anots{}}}; + auto primTxs = PrimValue{.prim = "Pair", .args{{from}, {txs}}, .anots{}}; + 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 52cdcd8b3ce..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 "BinaryCoding.h" -#include "Forging.h" -#include "HexCoding.h" #include "OperationList.h" +#include "Forging.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"); } @@ -41,7 +34,7 @@ Data Tezos::OperationList::forge(const PrivateKey& privateKey) const { for (auto operation : operation_list) { // If it's REVEAL operation, inject the public key if not specified if (operation.kind() == Operation::REVEAL && operation.has_reveal_operation_data()) { - auto revealOperationData = operation.mutable_reveal_operation_data(); + auto* revealOperationData = operation.mutable_reveal_operation_data(); if (revealOperationData->public_key().empty()) { auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); @@ -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..42ec445cdd0 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); + PrivateKey key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); + 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()); @@ -51,10 +53,28 @@ Data Signer::signData(const PrivateKey& privateKey, const Data& data) { append(watermarkedData, data); Data hash = Hash::blake2b(watermarkedData, 32); - Data signature = privateKey.sign(hash, TWCurve::TWCurveED25519); + Data signature = privateKey.sign(hash); Data signedData = Data(); append(signedData, 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 8e391ec086b..f8cff52113e 100644 --- a/src/Theta/Entry.cpp +++ b/src/Theta/Entry.cpp @@ -1,30 +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" -using namespace TW::Theta; -using namespace std; +namespace TW::Theta { -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 Ethereum::Address::isValid(address); } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { // normalized with EIP55 checksum return Ethereum::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 Ethereum::Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +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); } + +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 90d33acdf14..1d6beb82a5b 100644 --- a/src/Theta/Entry.h +++ b/src/Theta/Entry.h @@ -1,24 +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 "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 CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeTheta}; } - 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 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..0f25d9503c5 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -1,20 +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 "../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())); + auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto from = Ethereum::Address(pkFrom.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); auto transaction = Transaction( @@ -31,38 +29,81 @@ 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 { auto encoded = encode(transaction); auto hash = Hash::keccak256(encoded); - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); 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 8b16c4957f5..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,22 +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 - -/// Wrapper for C interface. -struct TWThetaSigner { - TW::Theta::Signer impl; -}; 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 new file mode 100644 index 00000000000..b9ee21bb760 --- /dev/null +++ b/src/TransactionCompiler.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TransactionCompiler.h" + +#include "Coin.h" + +using namespace TW; + +Data TransactionCompiler::preImageHashes(TWCoinType coinType, const Data& txInputData) { + return anyCoinPreImageHashes(coinType, txInputData); +} + +Data TransactionCompiler::compileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys) { + // input parameter conversion + const auto publicKeyType = ::publicKeyType(coinType); + std::vector pubs; + for (auto& p: publicKeys) { + if (!PublicKey::isValid(p, publicKeyType)) { + throw std::invalid_argument("Invalid public key"); + } + 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; + anyCoinCompileWithSignatures(coinType, txInputData, signatures, pubs, txOutput); + return txOutput; +} diff --git a/src/TransactionCompiler.h b/src/TransactionCompiler.h new file mode 100644 index 00000000000..0c12b4dd23c --- /dev/null +++ b/src/TransactionCompiler.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include "Data.h" +#include "CoinEntry.h" + +#include +#include + +namespace TW { + +/// Non-core transaction utility methods, like building a transaction using an external signature +class TransactionCompiler { +public: + /// 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. + /// For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. + static Data preImageHashes(TWCoinType coinType, const Data& txInputData); + + /// 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..64c15d6d75c 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 = signer.signaturePreimageHash(); + 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 0e13d37855e..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeTron}; } - 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..32af140daf0 --- /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.length() << 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..ed016f66b4b --- /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:\n"; + 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..17e30c3a689 100644 --- a/src/Tron/Protobuf/TronInternal.proto +++ b/src/Tron/Protobuf/TronInternal.proto @@ -4,6 +4,7 @@ import "google/protobuf/any.proto"; package protocol; +// https://github.com/tronprotocol/protocol/blob/2a678934da3992b1a67f975769bbb2d31989451f/core/Tron.proto#L336 message Transaction { message Contract { enum ContractType { @@ -17,6 +18,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; @@ -28,6 +34,8 @@ message Transaction { int64 ref_block_num = 3; bytes ref_block_hash = 4; int64 expiration = 8; + // transaction memo + bytes data = 10; //only support size = 1, repeated list here for extension repeated Contract contract = 11; int64 timestamp = 14; @@ -78,6 +86,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 +99,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 +156,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..989cac01ca2 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,21 @@ 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)) }); + if (!raw.data().empty()) { + raw_dataJSON["data"] = hex(raw.data()); + } + 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 3005227d6c7..3e126cdea3b 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -1,38 +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 "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 +#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 +41,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 +55,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 +69,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 +163,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,13 +178,13 @@ 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()); - auto vote = internal.add_votes(); + 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()); vote->set_vote_count(voteContract.votes(i).vote_count()); @@ -133,7 +195,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 +204,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 +218,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 +268,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 +280,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 +288,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 +346,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 +354,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 +362,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 +370,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 +378,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 +387,87 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { *contract->mutable_parameter() = any; } + if (!input.transaction().memo().empty()) { + tx.mutable_raw_data()->set_data(input.transaction().memo()); + } + + 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()), TWCurveSECP256k1); + auto output = Proto::SigningOutput(); + + Data hash; + if (!input.txid().empty()) { + hash = parse_hex(input.txid()); + } else if (!input.raw_json().empty()) { + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + if (parsed.contains("txID") && parsed["txID"].is_string()) { + hash = parse_hex(parsed["txID"].get()); + } else { + // If txID is not present, return an error + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("No txID found in raw JSON"); + return output; + } + } catch (const std::exception& e) { + // If parsing fails, return an error + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message(e.what()); + return output; + } + } + + const auto signature = key.sign(hash); + 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() || !input.raw_json().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 key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); + const auto signature = key.sign(hash); - 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 +475,76 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } + +Proto::SigningOutput Signer::compile(const Data& signature) const { + Proto::SigningOutput output; + if (!input.raw_json().empty()) { + // If raw JSON is provided, we use it directly + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + // Add signature to JSON and set to output + parsed["signature"] = nlohmann::json::array({hex(signature)}); + output.set_json(parsed.dump()); + output.set_signature(signature.data(), signature.size()); + // Extract txID and set to output + if (parsed.contains("txID") && parsed["txID"].is_string()) { + auto txID = parse_hex(parsed["txID"].get()); + output.set_id(txID.data(), txID.size()); + } + return output; + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message(e.what()); + return 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 { + if (!input.raw_json().empty()) { + // If raw JSON is provided, we use raw_data_hex directly + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + if (parsed.contains("raw_data_hex") && parsed["raw_data_hex"].is_string()) { + return parse_hex(parsed["raw_data_hex"].get()); + } + // If raw_data_hex is not present, return an empty Data + return {}; + } catch (...) { + // Ignore parsing errors, return an empty Data + return {}; + } + } + return serialize(buildTransaction(input)); +} + +Data Signer::signaturePreimageHash() const { + if (!input.raw_json().empty()) { + // If raw JSON is provided, we use txID directly + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + if (parsed.contains("txID") && parsed["txID"].is_string()) { + return parse_hex(parsed["txID"].get()); + } + // If txID is not present, return an empty Data + return {}; + } catch (...) { + // Ignore parsing errors, return an empty Data + return {}; + } + } + auto preImage = signaturePreimage(); + return Hash::sha256(preImage); +} + +} // namespace TW::Tron diff --git a/src/Tron/Signer.h b/src/Tron/Signer.h index 2336c3c8fc2..0765cbf6626 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,15 @@ 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; + Data signaturePreimageHash() 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 96a0ef25dab..312007e942a 100644 --- a/src/VeChain/Entry.cpp +++ b/src/VeChain/Entry.cpp @@ -1,30 +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 { -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 Ethereum::Address::isValid(address); } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { // normalized with EIP55 checksum return Ethereum::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 Ethereum::Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +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 b856f4ebe28..c15d4896b03 100644 --- a/src/VeChain/Entry.h +++ b/src/VeChain/Entry.h @@ -1,24 +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 "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 CoinEntry { +class Entry final : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeVeChain}; } - 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 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..de3e864318c 100644 --- a/src/VeChain/Signer.cpp +++ b/src/VeChain/Signer.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 "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())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto transaction = Transaction(); transaction.chainTag = static_cast(input.chain_tag()); transaction.blockRef = input.block_ref(); @@ -38,6 +37,41 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { auto encoded = transaction.encode(); auto hash = Hash::blake2b(encoded, 32); - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); 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 5b193a3385d..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,13 +24,12 @@ 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; }; } // namespace TW::VeChain - -/// Wrapper for C interface. -struct TWVeChainSigner { - TW::VeChain::Signer impl; -}; 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 new file mode 100644 index 00000000000..666c911d1e0 --- /dev/null +++ b/src/Wasm.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#ifndef __USE_WASM +#define __USE_WASM +#endif // __USE_WASM + +#ifndef __USE_MISC +#ifdef __USE_WASM + +typedef unsigned long int ulong; +typedef unsigned short int ushort; +typedef unsigned int uint; + +#endif // __USE_WASM +#endif // __USE_MISC diff --git a/src/Waves/Address.cpp b/src/Waves/Address.cpp index be51732e937..39eeb09a8c6 100644 --- a/src/Waves/Address.cpp +++ b/src/Waves/Address.cpp @@ -1,26 +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 "Address.h" #include "../Base58.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include #include +#include #include -using namespace TW; -using namespace TW::Waves; - -template -Data Address::secureHash(const T &data) { - return Hash::keccak256(Hash::blake2b(data, 32)); -} +namespace TW::Waves { bool Address::isValid(const Data& decoded) { if (decoded.size() != Address::size) { @@ -39,18 +32,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"); } @@ -81,5 +72,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..bba6347681e 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 @@ -24,7 +22,9 @@ class Address : public Base58Address<26> { static const signed char testnet = 'T'; template - static Data secureHash(const T &data); + static Data secureHash(const T &data) { + return Hash::keccak256(Hash::blake2b(data, 32)); + } /// Determines whether a string makes a valid address. static bool isValid(const std::string& string); 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 8418548c2b6..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeWaves}; } - 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..a8e11f6e7cf 100644 --- a/src/Waves/Signer.cpp +++ b/src/Waves/Signer.cpp @@ -1,35 +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. #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())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveCurve25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto transaction = Transaction(input, publicKey.bytes); 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); + auto signature = privateKey.sign(bytesToSign); return signature; } catch (...) { 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/Entry.h b/src/XRP/Entry.h new file mode 100644 index 00000000000..1395e482abd --- /dev/null +++ b/src/XRP/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Ripple { + +/// 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 final : public Rust::RustCoinEntryWithSignJSON { +}; + +} // namespace TW::Ripple diff --git a/src/XXHash64.h b/src/XXHash64.h deleted file mode 100644 index 6147ff42759..00000000000 --- a/src/XXHash64.h +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright © 2016 Stephan Brumme. All rights reserved, see http://create.stephan-brumme.com/disclaimer.html -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 // for uint32_t and uint64_t - -/// XXHash (64 bit), based on Yann Collet's descriptions, see http://cyan4973.github.io/xxHash/ -/** How to use: - uint64_t myseed = 0; - XXHash64 myhash(myseed); - myhash.add(pointerToSomeBytes, numberOfBytes); - myhash.add(pointerToSomeMoreBytes, numberOfMoreBytes); // call add() as often as you like to ... - // and compute hash: - uint64_t result = myhash.hash(); - - // or all of the above in one single line: - uint64_t result2 = XXHash64::hash(mypointer, numBytes, myseed); - - Note: my code is NOT endian-aware ! -**/ -class XXHash64 -{ -public: - /// create new XXHash (64 bit) - /** @param seed your seed value, even zero is a valid seed **/ - explicit XXHash64(uint64_t seed) - { - state[0] = seed + Prime1 + Prime2; - state[1] = seed + Prime2; - state[2] = seed; - state[3] = seed - Prime1; - buffer[0] = 0; - bufferSize = 0; - totalLength = 0; - } - - /// add a chunk of bytes - /** @param input pointer to a continuous block of data - @param length number of bytes - @return false if parameters are invalid / zero **/ - bool add(const void* input, uint64_t length) - { - // no data ? - if (!input || length == 0) - return false; - - totalLength += length; - // byte-wise access - const unsigned char* data = (const unsigned char*)input; - - // unprocessed old data plus new data still fit in temporary buffer ? - if (bufferSize + length < MaxBufferSize) - { - // just add new data - while (length-- > 0) - buffer[bufferSize++] = *data++; - return true; - } - - // point beyond last byte - const unsigned char* stop = data + length; - const unsigned char* stopBlock = stop - MaxBufferSize; - - // some data left from previous update ? - if (bufferSize > 0) - { - // make sure temporary buffer is full (16 bytes) - while (bufferSize < MaxBufferSize) - buffer[bufferSize++] = *data++; - - // process these 32 bytes (4x8) - process(buffer, state[0], state[1], state[2], state[3]); - } - - // copying state to local variables helps optimizer A LOT - uint64_t s0 = state[0], s1 = state[1], s2 = state[2], s3 = state[3]; - // 32 bytes at once - while (data <= stopBlock) - { - // local variables s0..s3 instead of state[0]..state[3] are much faster - process(data, s0, s1, s2, s3); - data += 32; - } - // copy back - state[0] = s0; state[1] = s1; state[2] = s2; state[3] = s3; - - // copy remainder to temporary buffer - bufferSize = uint32_t(stop - data); - for (unsigned int i = 0; i < bufferSize; i++) - buffer[i] = data[i]; - - // done - return true; - } - - /// get current hash - /** @return 64 bit XXHash **/ - uint64_t hash() const - { - // fold 256 bit state into one single 64 bit value - uint64_t result; - if (totalLength >= MaxBufferSize) - { - result = rotateLeft(state[0], 1) + - rotateLeft(state[1], 7) + - rotateLeft(state[2], 12) + - rotateLeft(state[3], 18); - result = (result ^ processSingle(0, state[0])) * Prime1 + Prime4; - result = (result ^ processSingle(0, state[1])) * Prime1 + Prime4; - result = (result ^ processSingle(0, state[2])) * Prime1 + Prime4; - result = (result ^ processSingle(0, state[3])) * Prime1 + Prime4; - } - else - { - // internal state wasn't set in add(), therefore original seed is still stored in state2 - result = state[2] + Prime5; - } - - result += totalLength; - - // process remaining bytes in temporary buffer - const unsigned char* data = buffer; - // point beyond last byte - const unsigned char* stop = data + bufferSize; - - // at least 8 bytes left ? => eat 8 bytes per step - for (; data + 8 <= stop; data += 8) - result = rotateLeft(result ^ processSingle(0, *(uint64_t*)data), 27) * Prime1 + Prime4; - - // 4 bytes left ? => eat those - if (data + 4 <= stop) - { - result = rotateLeft(result ^ (*(uint32_t*)data) * Prime1, 23) * Prime2 + Prime3; - data += 4; - } - - // take care of remaining 0..3 bytes, eat 1 byte per step - while (data != stop) - result = rotateLeft(result ^ (*data++) * Prime5, 11) * Prime1; - - // mix bits - result ^= result >> 33; - result *= Prime2; - result ^= result >> 29; - result *= Prime3; - result ^= result >> 32; - return result; - } - - - /// combine constructor, add() and hash() in one static function (C style) - /** @param input pointer to a continuous block of data - @param length number of bytes - @param seed your seed value, e.g. zero is a valid seed - @return 64 bit XXHash **/ - static uint64_t hash(const void* input, uint64_t length, uint64_t seed) - { - XXHash64 hasher(seed); - hasher.add(input, length); - return hasher.hash(); - } - -private: - /// magic constants :-) - static const uint64_t Prime1 = 11400714785074694791ULL; - static const uint64_t Prime2 = 14029467366897019727ULL; - static const uint64_t Prime3 = 1609587929392839161ULL; - static const uint64_t Prime4 = 9650029242287828579ULL; - static const uint64_t Prime5 = 2870177450012600261ULL; - - /// temporarily store up to 31 bytes between multiple add() calls - static const uint64_t MaxBufferSize = 31+1; - - uint64_t state[4]; - unsigned char buffer[MaxBufferSize]; - uint32_t bufferSize; - uint64_t totalLength; - - /// rotate bits, should compile to a single CPU instruction (ROL) - static inline uint64_t rotateLeft(uint64_t x, unsigned char bits) - { - return (x << bits) | (x >> (64 - bits)); - } - - /// process a single 64 bit value - static inline uint64_t processSingle(uint64_t previous, uint64_t input) - { - return rotateLeft(previous + input * Prime2, 31) * Prime1; - } - - /// process a block of 4x4 bytes, this is the main part of the XXHash32 algorithm - static inline void process(const void* data, uint64_t& state0, uint64_t& state1, uint64_t& state2, uint64_t& state3) - { - const uint64_t* block = (const uint64_t*) data; - state0 = processSingle(state0, block[0]); - state1 = processSingle(state1, block[1]); - state2 = processSingle(state2, block[2]); - state3 = processSingle(state3, block[3]); - } -}; diff --git a/src/Zcash/Entry.cpp b/src/Zcash/Entry.cpp index cbab0176d74..50a45f9f10d 100644 --- a/src/Zcash/Entry.cpp +++ b/src/Zcash/Entry.cpp @@ -1,29 +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 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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +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([[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 503b13544d5..a879f1dfb27 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 : public CoinEntry { public: - virtual const std::vector coinTypes() const { return {TWCoinTypeZcash, TWCoinTypeZelcash}; } - 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 final; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const final; + Data addressToData(TWCoinType coin, const std::string& address) const final; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const final; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const final; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const final; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const final; }; } // namespace TW::Zcash diff --git a/src/Zcash/Signer.cpp b/src/Zcash/Signer.cpp index 38695872fcf..f333f630e0b 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,20 +9,26 @@ #include "Transaction.h" #include "TransactionBuilder.h" -using namespace TW; -using namespace TW::Zcash; +namespace TW::Zcash { TransactionPlan Signer::plan(const SigningInput& input) noexcept { - auto signer = Bitcoin::TransactionSigner(std::move(input)); - return signer.plan.proto(); + if (input.has_signing_v2()) { + return Bitcoin::Signer::planAsV2(input); + } + 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 { + if (input.has_signing_v2()) { + return Bitcoin::Signer::signAsV2(input); + } + SigningOutput output; - auto signer = Bitcoin::TransactionSigner(std::move(input)); - auto result = signer.sign(); + 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(); @@ -39,3 +43,28 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { } return output; } + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + if (input.has_signing_v2()) { + return Bitcoin::Signer::preImageHashesAsV2(input); + } + + 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 6d6f5ac74a7..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 @@ -45,8 +43,3 @@ class TAddress : public TW::Base58Address<22> { }; } // namespace TW::Zcash - -/// Wrapper for C interface. -struct TWZcashTAddress { - TW::Zcash::TAddress impl; -}; diff --git a/src/Zcash/Transaction.cpp b/src/Zcash/Transaction.cpp index f872270d2dc..65f19a44e25 100644 --- a/src/Zcash/Transaction.cpp +++ b/src/Zcash/Transaction.cpp @@ -1,33 +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 "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}; -/// 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}; +/// See https://github.com/zcash/zips/blob/master/zips/zip-0205.rst#sapling-deployment BRANCH_ID section +const std::array SaplingBranchID = {0xbb, 0x09, 0xb8, 0x76}; +/// See https://github.com/zcash/zips/blob/master/zips/zip-0206.rst#blossom-deployment BRANCH_ID section +const std::array BlossomBranchID = {0x60, 0x0e, 0xb4, 0x2b}; +/// See https://github.com/zcash/zips/blob/main/zips/zip-0253.md#nu6-deployment CONSENSUS_BRANCH_ID section +const std::array Nu6BranchID = {0x55, 0x10, 0xe7, 0xc8}; Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const { @@ -36,7 +33,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 +96,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 +149,7 @@ Data Transaction::getShieldedOutputsHash() const { } void Transaction::encode(Data& data) const { - encode32LE(version, data); + encode32LE(_version, data); encode32LE(versionGroupId, data); // vin @@ -181,7 +178,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,11 +191,11 @@ 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) { - auto protoInput = protoTx.add_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); @@ -207,10 +204,12 @@ Bitcoin::Proto::Transaction Transaction::proto() const { } for (const auto& output : outputs) { - auto protoOutput = protoTx.add_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::Zcash diff --git a/src/Zcash/Transaction.h b/src/Zcash/Transaction.h index 1ec45215711..7d5fc1534b8 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 @@ -19,18 +17,19 @@ namespace TW::Zcash { extern const std::array SaplingBranchID; extern const std::array BlossomBranchID; +extern const std::array Nu6BranchID; /// 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; uint64_t valueBalance = 0; - std::vector inputs; - std::vector outputs; + Bitcoin::TransactionInputs inputs; + Bitcoin::TransactionOutputs outputs; std::array branchId; /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) @@ -40,7 +39,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 e903c85888e..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 @@ -19,24 +18,24 @@ namespace TW::Zcash { struct TransactionBuilder { /// Plans a transaction by selecting UTXOs and calculating fees. - static Bitcoin::TransactionPlan plan(const Bitcoin::Proto::SigningInput& input) { + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { return Bitcoin::TransactionBuilder::plan(input); } /// 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) { - coin = TWCoinTypeZcash; - Transaction tx = - Bitcoin::TransactionBuilder::build(plan, toAddress, changeAddress, coin); + 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 be7a7ef234f..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 @@ -26,7 +24,7 @@ class Address : public Bech32Address { Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2, publicKey) {} + Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherSha256, publicKey) {} std::string checksumed() const { return checksum(getKeyHash()); 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 7033880e3c2..246456d6bb5 100644 --- a/src/Zilliqa/Entry.cpp +++ b/src/Zilliqa/Entry.cpp @@ -1,27 +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 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(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!Address::decode(address, addr)) { + return {}; + } + // data in Zilliqa is a checksummed string without 0x + return data(checksum(addr.getKeyHash())); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { signTemplate(dataIn, dataOut); } + +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 58b2bdba16d..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,12 +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 const std::vector coinTypes() const { return {TWCoinTypeZilliqa}; } - 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; + 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 027082233a3..6db4930d251 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" @@ -15,10 +13,11 @@ #include +#include #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa { + using ByteArray = ZilliqaMessage::ByteArray; static inline Data prependZero(Data& data) { @@ -31,21 +30,21 @@ static inline Data prependZero(Data& data) { } static inline ByteArray* byteArray(Data& amount) { - auto array = new ByteArray(); + auto* array = new ByteArray(); amount = prependZero(amount); array->set_data(amount.data(), amount.size()); return array; } static inline ByteArray* byteArray(const void* data, size_t size) { - auto array = new ByteArray(); + auto* array = new ByteArray(); array->set_data(data, size); return array; } Data Signer::getPreImage(const Proto::SigningInput& input, Address& address) noexcept { auto internal = ZilliqaMessage::ProtoTransactionCoreInfo(); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); if (!Address::decode(input.to(), address)) { // invalid input address return Data(0); @@ -78,7 +77,8 @@ Data Signer::getPreImage(const Proto::SigningInput& input, Address& address) noe } break; } - default: break; + default: + break; } internal.set_allocated_amount(byteArray(amount)); @@ -91,9 +91,9 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); Address address; const auto preImage = Signer::getPreImage(input, address); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); 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 @@ -128,3 +128,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { 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()); + return hex(Signer::sign(input).json()); +} + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Signer.h b/src/Zilliqa/Signer.h index 625e51993a1..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" @@ -21,6 +19,9 @@ class Signer { /// Signs the given signing input 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); + /// compute preImage and decode address from signing input. static Data getPreImage(const Proto::SigningInput& input, Address& address) noexcept; }; diff --git a/src/algorithm/erase.h b/src/algorithm/erase.h new file mode 100644 index 00000000000..ed3a0bf9fd4 --- /dev/null +++ b/src/algorithm/erase.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +namespace TW { + +// C++17 Implementation of https://en.cppreference.com/w/cpp/container/vector/erase2 +template +constexpr typename std::vector::size_type erase(std::vector& c, + const U& value) { + auto it = std::remove(c.begin(), c.end(), value); + auto r = std::distance(it, c.end()); + c.erase(it, c.end()); + return r; +} + +template +constexpr typename std::vector::size_type erase_if(std::vector& c, + Functor&& pred) { + auto it = std::remove_if(c.begin(), c.end(), std::forward(pred)); + auto r = std::distance(it, c.end()); + c.erase(it, c.end()); + return r; +} + +} // namespace TW \ No newline at end of file diff --git a/src/algorithm/sort_copy.h b/src/algorithm/sort_copy.h new file mode 100644 index 00000000000..eb1e7b3bf31 --- /dev/null +++ b/src/algorithm/sort_copy.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +namespace TW { + +template +inline Container sortCopy(const Container& container) noexcept { + Container result = container; + std::sort(begin(result), end(result)); + return result; +} + +template +inline Container sortCopy(const Container& container, Func&& func) noexcept { + Container result = container; + std::sort(begin(result), end(result), std::forward(func)); + return result; +} + +} // namespace TW \ No newline at end of file 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 cbe49c3f321..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,30 +8,46 @@ using namespace TW; -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, 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) { auto& addressString = *reinterpret_cast(address); auto& derivationPathString = *reinterpret_cast(derivationPath); + auto& publicKeyString = *reinterpret_cast(publicKey); auto& extendedPublicKeyString = *reinterpret_cast(extendedPublicKey); const auto dp = DerivationPath(derivationPathString); - return new TWAccount{ Keystore::Account(addressString, coin, dp, extendedPublicKeyString) }; + return new TWAccount{ + Keystore::Account(addressString, coin, derivation, dp, publicKeyString, extendedPublicKeyString) + }; } -void TWAccountDelete(struct TWAccount *_Nonnull account) { +void TWAccountDelete(struct TWAccount* _Nonnull account) { delete account; } -TWString *_Nonnull TWAccountAddress(struct TWAccount *_Nonnull account) { +TWString* _Nonnull TWAccountAddress(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.address.c_str()); } -TWString *_Nonnull TWAccountDerivationPath(struct TWAccount *_Nonnull account) { +enum TWCoinType TWAccountCoin(struct TWAccount* _Nonnull account) { + return account->impl.coin; +} + +enum TWDerivation TWAccountDerivation(struct TWAccount* _Nonnull account) { + return account->impl.derivation; +} + +TWString* _Nonnull TWAccountDerivationPath(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.derivationPath.string().c_str()); } -TWString *_Nonnull TWAccountExtendedPublicKey(struct TWAccount *_Nonnull account) { - return TWStringCreateWithUTF8Bytes(account->impl.extendedPublicKey.c_str()); +TWString* _Nonnull TWAccountPublicKey(struct TWAccount* _Nonnull account) { + return TWStringCreateWithUTF8Bytes(account->impl.publicKey.c_str()); } -enum TWCoinType TWAccountCoin(struct TWAccount *_Nonnull account) { - return account->impl.coin; +TWString* _Nonnull TWAccountExtendedPublicKey(struct TWAccount* _Nonnull account) { + return TWStringCreateWithUTF8Bytes(account->impl.extendedPublicKey.c_str()); } diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index 5bb3ed29e8d..b91bb724f1d 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -1,40 +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 #include #include -#include - -#include "../Bitcoin/Address.h" -#include "../Bitcoin/CashAddress.h" -#include "../Bitcoin/SegwitAddress.h" -#include "../Cosmos/Address.h" -#include "../Decred/Address.h" -#include "../Kusama/Address.h" -#include "../Polkadot/Address.h" -#include "../Zcash/TAddress.h" -#include "../Zilliqa/Address.h" -#include "../Cardano/AddressV3.h" -#include "../NEO/Address.h" -#include "../Nano/Address.h" -#include "../Elrond/Address.h" -#include "../NEAR/Address.h" - -#include "../Coin.h" -#include "../HexCoding.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) { @@ -42,171 +20,98 @@ bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { return TW::validateAddress(coin, address); } -struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, - enum TWCoinType coin) { +bool TWAnyAddressIsValidSS58([[maybe_unused]] TWString* string, [[maybe_unused]] enum TWCoinType coin, [[maybe_unused]] uint32_t ss58Prefix) { 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}; -} - -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}; -} - -void TWAnyAddressDelete(struct TWAnyAddress* _Nonnull address) { - TWStringDelete(address->address); - delete address; -} - -TWString* _Nonnull TWAnyAddressDescription(struct TWAnyAddress* _Nonnull address) { - return TWStringCreateWithUTF8Bytes(TWStringUTF8Bytes(address->address)); + return TW::validateAddress(coin, address, ss58Prefix); } -enum TWCoinType TWAnyAddressCoin(struct TWAnyAddress* _Nonnull address) { - return address->coin; +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()); } -TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { - auto& string = *reinterpret_cast(address->address); - Data data; - switch (address->coin) { - case TWCoinTypeBinance: - case TWCoinTypeCosmos: - case TWCoinTypeKava: - case TWCoinTypeTerra: - case TWCoinTypeBandChain: - case TWCoinTypeIoTeX: { - Cosmos::Address addr; - if (!Cosmos::Address::decode(string, addr)) { - break; - } - data = addr.getKeyHash(); - break; - } - - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeGroestlcoin: - case TWCoinTypeLitecoin: - case TWCoinTypeViacoin: { - auto decoded = Bitcoin::SegwitAddress::decode(string); - if (!std::get<2>(decoded)) { - break; - } - data = std::get<0>(decoded).witnessProgram; - break; +struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, + enum TWCoinType coin) { + const auto& address = *reinterpret_cast(string); + auto *impl = TW::AnyAddress::createAddress(address, coin); + if (impl == nullptr) { + return nullptr; } + return new TWAnyAddress{impl}; +} - case TWCoinTypeBitcoinCash: { - auto addr = Bitcoin::CashAddress(string); - data.resize(Bitcoin::Address::size); - size_t outlen = 0; - cash_data_to_addr(data.data(), &outlen, addr.bytes.data(), 34); - data = Data(data.begin() + 1, data.end()); - break; +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}; +} - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeRavencoin: - case TWCoinTypeZcoin: { - auto addr = Bitcoin::Address(string); - data = Data(addr.bytes.begin() + 1, addr.bytes.end()); - break; - } - case TWCoinTypeDecred: { - auto addr = Decred::Address(string); - data = Data(addr.bytes.begin() + 2, addr.bytes.end()); - break; +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}; +} - case TWCoinTypeZcash: - case TWCoinTypeZelcash: { - auto addr = Zcash::TAddress(string); - data = Data(addr.bytes.begin() + 2, addr.bytes.end()); - break; - } +struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKey( + struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin) { + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin)}; +} - case TWCoinTypeCallisto: - case TWCoinTypeEthereum: - case TWCoinTypeEthereumClassic: - case TWCoinTypeGoChain: - case TWCoinTypePOANetwork: - case TWCoinTypeThunderToken: - case TWCoinTypeTomoChain: - case TWCoinTypeVeChain: - case TWCoinTypeTheta: - case TWCoinTypeWanchain: - case TWCoinTypeAion: - case TWCoinTypeSmartChainLegacy: - case TWCoinTypeSmartChain: - case TWCoinTypePolygon: - data = parse_hex(string); - break; - - case TWCoinTypeNano: { - auto addr = Nano::Address(string); - data = Data(addr.bytes.begin(), addr.bytes.end()); - break; - } +struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKeyDerivation( + struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, enum TWDerivation derivation) { + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin, derivation)}; +} - case TWCoinTypeZilliqa: { - Zilliqa::Address addr; - if (!Zilliqa::Address::decode(string, addr)) { - break; - } - // data in Zilliqa is a checksummed string without 0x - auto str = Zilliqa::checksum(addr.getKeyHash()); - data = Data(str.begin(), str.end()); - break; - } +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()))}; +} - case TWCoinTypeKusama: { - auto addr = Kusama::Address(string); - data = Data(addr.bytes.begin() + 1, addr.bytes.end()); - break; - } +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))}; +} - case TWCoinTypePolkadot: { - auto addr = Polkadot::Address(string); - data = Data(addr.bytes.begin() + 1, addr.bytes.end()); - break; +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)}; +} - case TWCoinTypeCardano: { - auto addr = Cardano::AddressV3(string); - data = addr.data(); - break; +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)}; +} - case TWCoinTypeNEO: { - auto addr = NEO::Address(string); - data = Data(addr.bytes.begin(), addr.bytes.end()); - break; - } +void TWAnyAddressDelete(struct TWAnyAddress* _Nonnull address) { + delete address->impl; + delete address; +} - case TWCoinTypeElrond: { - Elrond::Address addr; - if (Elrond::Address::decode(string, addr)) { - data = addr.getKeyHash(); - } - - break; - } +TWString* _Nonnull TWAnyAddressDescription(struct TWAnyAddress* _Nonnull address) { + return TWStringCreateWithUTF8Bytes(address->impl->address.c_str()); +} - case TWCoinTypeNEAR: { - auto addr = NEAR::Address(string); - data = Data(addr.bytes.begin(), addr.bytes.end()); - break; - } +enum TWCoinType TWAnyAddressCoin(struct TWAnyAddress* _Nonnull address) { + return address->impl->coin; +} - default: break; - } +TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { + 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/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/TWBech32.cpp b/src/interface/TWBech32.cpp new file mode 100644 index 00000000000..fe184ead846 --- /dev/null +++ b/src/interface/TWBech32.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "../Bech32.h" + +#include + +using namespace TW; + +static TWString *_Nonnull encodeGeneric(TWString* _Nonnull hrp, TWData *_Nonnull data, const Bech32::ChecksumVariant variant) { + const auto cppHrp = *static_cast(hrp); + const auto cppData = *static_cast(data); + Data enc; + if (!Bech32::convertBits<8, 5, true>(enc, cppData)) { + return TWStringCreateWithUTF8Bytes(""); + } + const auto result = Bech32::encode(cppHrp, enc, variant); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} + +static TWData *_Nullable decodeGeneric(TWString *_Nonnull string, const Bech32::ChecksumVariant variant) { + const auto cppString = *static_cast(string); + const auto decoded = Bech32::decode(cppString); + + const auto data = std::get<1>(decoded); + if (data.empty()) { // Failed to decode + return nullptr; + } + + if (std::get<2>(decoded) != variant) { // Wrong ChecksumVariant + return nullptr; + } + + // Bech bits conversion + Data conv; + if (!Bech32::convertBits<5, 8, false>(conv, data)) { + return nullptr; + } + + return TWDataCreateWithBytes(conv.data(), conv.size()); +} + +TWString *_Nonnull TWBech32Encode(TWString* _Nonnull hrp, TWData *_Nonnull data) { + return encodeGeneric(hrp, data, Bech32::ChecksumVariant::Bech32); +} + +TWString *_Nonnull TWBech32EncodeM(TWString* _Nonnull hrp, TWData *_Nonnull data) { + return encodeGeneric(hrp, data, Bech32::ChecksumVariant::Bech32M); +} + +TWData *_Nullable TWBech32Decode(TWString *_Nonnull string) { + return decodeGeneric(string, Bech32::ChecksumVariant::Bech32); +} + +TWData *_Nullable TWBech32DecodeM(TWString *_Nonnull string) { + return decodeGeneric(string, Bech32::ChecksumVariant::Bech32M); +} 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/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 6aff55689f2..3093e132896 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -1,36 +1,34 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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" -using namespace TW::Bitcoin; +#include struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreate() { - auto script = new TWBitcoinScript{}; + auto* script = new TWBitcoinScript{}; return script; } struct TWBitcoinScript *TWBitcoinScriptCreateWithData(TWData *data) { - auto script = new TWBitcoinScript{}; + auto* script = new TWBitcoinScript{}; script->impl.bytes.resize(TWDataSize(data)); TWDataCopyBytes(data, 0, TWDataSize(data), script->impl.bytes.data()); return script; } struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateWithBytes(uint8_t *_Nonnull bytes, size_t size) { - auto script = new TWBitcoinScript{}; + auto* script = new TWBitcoinScript{}; std::copy(bytes, bytes + size, std::back_inserter(script->impl.bytes)); return script; } struct TWBitcoinScript *TWBitcoinScriptCreateCopy(const struct TWBitcoinScript *script) { - auto newScript = new TWBitcoinScript{}; + auto* newScript = new TWBitcoinScript{}; newScript->impl.bytes = script->impl.bytes; return newScript; } @@ -118,33 +116,46 @@ TWData *TWBitcoinScriptEncode(const struct TWBitcoinScript *script) { return TWDataCreateWithBytes(result.data(), result.size()); } +struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKey(TWData *pubkey) { + auto* v = reinterpret_cast*>(pubkey); + 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* v = reinterpret_cast*>(hash); + 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* v = reinterpret_cast*>(scriptHash); + 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* v = reinterpret_cast*>(hash); + 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* v = reinterpret_cast*>(scriptHash); + 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* s = reinterpret_cast(address); + 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 new file mode 100644 index 00000000000..d4278c593c9 --- /dev/null +++ b/src/interface/TWCardano.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "Cardano/Transaction.h" +#include "Cardano/AddressV3.h" +#include "proto/Cardano.pb.h" + +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); + TW::Cardano::Proto::TokenBundle bundleProto; + if (bundleData && bundleProto.ParseFromArray(bundleData->data(), (int)bundleData->size())) { + 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 a99ecee7c75..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); } @@ -65,6 +74,18 @@ uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin) { return TW::staticPrefix(coin); } +TWString* _Nonnull TWCoinTypeChainId(enum TWCoinType coin) { + return TWStringCreateWithUTF8Bytes(TW::chainId(coin)); +} + uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin) { return TW::slip44Id(coin); } + +enum TWPublicKeyType TWCoinTypePublicKeyType(enum TWCoinType coin) { + return TW::publicKeyType(coin); +} + +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/TWData.cpp b/src/interface/TWData.cpp index cb3afcd52ca..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 @@ -14,20 +12,20 @@ using namespace TW; TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size) { - auto data = new std::vector(); + auto* data = new Data(); data->reserve(size); std::copy(bytes, bytes + size, std::back_inserter(*data)); return data; } TWData *_Nonnull TWDataCreateWithSize(size_t size) { - auto data = new std::vector(size, 0); + auto* data = new Data(size, 0); return data; } TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data) { - auto other = reinterpret_cast*>(data); - auto copy = new std::vector(*other); + auto* other = reinterpret_cast(data); + auto* copy = new Data(*other); return copy; } @@ -40,69 +38,69 @@ TWData* TWDataCreateWithHexString(const TWString* hex) { } size_t TWDataSize(TWData *_Nonnull data) { - auto v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); return v->size(); } uint8_t *_Nonnull TWDataBytes(TWData *_Nonnull data) { - auto v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); return v->data(); } uint8_t TWDataGet(TWData *_Nonnull data, size_t index) { - auto v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); return (*v)[index]; } void TWDataSet(TWData *_Nonnull data, size_t index, uint8_t byte) { - auto v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); (*v)[index] = byte; } void TWDataCopyBytes(TWData *_Nonnull data, size_t start, size_t size, uint8_t *_Nonnull output) { - auto v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); std::copy(std::begin(*v) + start, std::begin(*v) + start + size, output); } void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const uint8_t *_Nonnull bytes) { - auto v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); std::copy(bytes, bytes + size, std::begin(*v) + start); } 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) + auto* v = const_cast(reinterpret_cast(data)); + for (auto i = 0ul; i < size; i += 1) v->push_back(bytes[i]); } void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte) { - auto v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); v->push_back(byte); } void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append) { - auto v = const_cast*>(reinterpret_cast*>(data)); - auto av = reinterpret_cast*>(append); + auto* v = const_cast(reinterpret_cast(data)); + auto* av = reinterpret_cast(append); std::copy(av->begin(), av->end(), std::back_inserter(*v)); } void TWDataReverse(TWData *_Nonnull data) { - auto v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); std::reverse(std::begin(*v), std::end(*v)); } void TWDataReset(TWData *_Nonnull data) { - auto v = const_cast*>(reinterpret_cast*>(data)); + auto* v = const_cast(reinterpret_cast(data)); std::fill(std::begin(*v), std::end(*v), 0); } void TWDataDelete(TWData *_Nonnull data) { - auto v = reinterpret_cast*>(data); + auto* v = reinterpret_cast(data); delete v; } bool TWDataEqual(TWData *_Nonnull lhs, TWData *_Nonnull rhs) { - auto lv = reinterpret_cast*>(lhs); - auto rv = reinterpret_cast*>(rhs); + auto* lv = reinterpret_cast(lhs); + auto* rv = reinterpret_cast(rhs); return *lv == *rv; } diff --git a/src/interface/TWDataVector.cpp b/src/interface/TWDataVector.cpp new file mode 100644 index 00000000000..73985924849 --- /dev/null +++ b/src/interface/TWDataVector.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Data.h" + +#include +#include + +using namespace TW; + + +struct TWDataVector { + std::vector impl; +}; + + +struct TWDataVector *_Nonnull TWDataVectorCreate() { + auto* obj = new struct TWDataVector(); + assert(obj != nullptr); + return obj; +} + +struct TWDataVector *_Nonnull TWDataVectorCreateWithData(TWData *_Nonnull data) { + auto* obj = new struct TWDataVector(); + assert(obj != nullptr); + + TWDataVectorAdd(obj, data); + return obj; +} + +void TWDataVectorDelete(struct TWDataVector *_Nonnull dataVector) { + delete dataVector; +} + +void TWDataVectorAdd(struct TWDataVector *_Nonnull dataVector, TWData *_Nonnull data) { + dataVector->impl.push_back(TW::data(TWDataBytes(data), TWDataSize(data))); +} + +size_t TWDataVectorSize(const struct TWDataVector *_Nonnull dataVector) { + return dataVector->impl.size(); +} + +TWData *_Nullable TWDataVectorGet(const struct TWDataVector *_Nonnull dataVector, size_t index) { + if (index >= dataVector->impl.size()) { + return nullptr; + } + auto& elem = dataVector->impl[index]; + return TWDataCreateWithBytes(elem.data(), elem.size()); +} 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/TWEthereumAbi.cpp b/src/interface/TWEthereumAbi.cpp index a6208fe4193..59864b6bf41 100644 --- a/src/interface/TWEthereumAbi.cpp +++ b/src/interface/TWEthereumAbi.cpp @@ -1,57 +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; -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; +namespace EthAbi = TW::Ethereum::ABI; -/// Wrapper for C interface, empty as all methods are static -struct TWEthereumAbi { - // TW::Ethereum::ABI::Encoder impl; -}; +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,3 +78,27 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* return nullptr; } } + +TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson) { + Data data; + try { + data = Ethereum::MessageSigner::typedDataPreImageHash(TWStringUTF8Bytes(messageJson)); + } catch (...) {} // return empty + return TWDataCreateWithBytes(data.data(), data.size()); +} + +TWString* _Nullable TWEthereumAbiGetFunctionSignature(TWString* _Nonnull abi) { + try { + const Rust::TWStringWrapper abiStr = TWStringUTF8Bytes(abi); + + const Rust::TWStringWrapper outputDataPtr = Rust::tw_ethereum_abi_get_function_signature(TWCoinTypeEthereum, abiStr.get()); + if (!outputDataPtr) { + return nullptr; + } + + return TWStringCreateWithUTF8Bytes(outputDataPtr.c_str()); + } + catch(...) { + return nullptr; + } +} 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 194efd7af05..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,65 +8,63 @@ #include #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 c0007ed0759..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 @@ -12,21 +10,38 @@ using namespace TW; -bool TWHDWalletIsValid(TWString *_Nonnull mnemonic) { - return Mnemonic::isValid(TWStringUTF8Bytes(mnemonic)); + +struct TWHDWallet *_Nullable TWHDWalletCreate(int strength, TWString *_Nonnull passphrase) { + try { + return new TWHDWallet{ HDWallet(strength, TWStringUTF8Bytes(passphrase)) }; + } catch (...) { + return nullptr; + } } -struct TWHDWallet *_Nonnull TWHDWalletCreate(int strength, TWString *_Nonnull passphrase) { - return new TWHDWallet{ HDWallet(strength, TWStringUTF8Bytes(passphrase)) }; +struct TWHDWallet *_Nullable TWHDWalletCreateWithMnemonic(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase) { + try { + return new TWHDWallet{ HDWallet(TWStringUTF8Bytes(mnemonic), TWStringUTF8Bytes(passphrase)) }; + } catch (...) { + return nullptr; + } } -struct TWHDWallet *_Nonnull TWHDWalletCreateWithMnemonic(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase) { - return new TWHDWallet{ HDWallet(TWStringUTF8Bytes(mnemonic), TWStringUTF8Bytes(passphrase)) }; +struct TWHDWallet *_Nullable TWHDWalletCreateWithMnemonicCheck(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase, bool check) { + try { + return new TWHDWallet{ HDWallet(TWStringUTF8Bytes(mnemonic), TWStringUTF8Bytes(passphrase), check) }; + } catch (...) { + return nullptr; + } } -struct TWHDWallet *_Nonnull TWHDWalletCreateWithData(TWData *_Nonnull data, TWString *_Nonnull passphrase) { - auto *d = reinterpret_cast(data); - return new TWHDWallet{ HDWallet(*d, TWStringUTF8Bytes(passphrase)) }; +struct TWHDWallet *_Nullable TWHDWalletCreateWithEntropy(TWData *_Nonnull entropy, TWString *_Nonnull passphrase) { + try { + auto* d = reinterpret_cast(entropy); + return new TWHDWallet{ HDWallet(*d, TWStringUTF8Bytes(passphrase)) }; + } catch (...) { + return nullptr; + } } void TWHDWalletDelete(struct TWHDWallet *wallet) { @@ -34,11 +49,15 @@ void TWHDWalletDelete(struct TWHDWallet *wallet) { } TWData *_Nonnull TWHDWalletSeed(struct TWHDWallet *_Nonnull wallet) { - return TWDataCreateWithBytes(wallet->impl.seed.data(), HDWallet::seedSize); + return TWDataCreateWithBytes(wallet->impl.getSeed().data(), HDWallet<>::mSeedSize); } TWString *_Nonnull TWHDWalletMnemonic(struct TWHDWallet *_Nonnull wallet){ - return TWStringCreateWithUTF8Bytes(wallet->impl.mnemonic.c_str()); + return TWStringCreateWithUTF8Bytes(wallet->impl.getMnemonic().c_str()); +} + +TWData *_Nonnull TWHDWalletEntropy(struct TWHDWallet *_Nonnull wallet) { + return TWDataCreateWithBytes(wallet->impl.getEntropy().data(), wallet->impl.getEntropy().size()); } struct TWPrivateKey *_Nonnull TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull wallet, TWCurve curve) { @@ -46,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()); } @@ -63,11 +85,22 @@ struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull walle return new TWPrivateKey{ wallet->impl.getKey(coin, path) }; } -struct TWPrivateKey *_Nonnull TWHDWalletGetKeyBIP44(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address) { +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)); } @@ -76,9 +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, 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 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 5b335f58677..a6a5528627d 100644 --- a/src/interface/TWHash.cpp +++ b/src/interface/TWHash.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 #include "../Hash.h" -#include "../Data.h" +#include "Data.h" #include "BinaryCoding.h" -#include "XXHash64.h" #include #include #include @@ -79,21 +76,21 @@ TWData* _Nonnull TWHashGroestl512(TWData* _Nonnull data) { return TWDataCreateWithBytes(result.data(), result.size()); } -TWData* _Nonnull TWHashXXHash64(TWData* _Nonnull data, uint64_t seed) { - const auto result = Hash::xxhash64(reinterpret_cast(TWDataBytes(data)), TWDataSize(data), seed); - return TWDataCreateWithBytes(result.data(), result.size()); -} - -TWData* _Nonnull TWHashTwoXXHash64Concat(TWData* _Nonnull data) { - const auto result = Hash::xxhash64concat(reinterpret_cast(TWDataBytes(data)), TWDataSize(data)); - return TWDataCreateWithBytes(result.data(), result.size()); -} - TWData* _Nonnull TWHashSHA256SHA256(TWData* _Nonnull data) { const auto result = Hash::sha256d(reinterpret_cast(TWDataBytes(data)), TWDataSize(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); + tc_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 new file mode 100644 index 00000000000..3fae3225482 --- /dev/null +++ b/src/interface/TWPBKDF2.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Data.h" + +using namespace TW; + +TWData* _Nullable TWPBKDF2HmacSha256(TWData* _Nonnull password, TWData* _Nonnull salt, + uint32_t iterations, uint32_t dkLen) { + + Data key(dkLen); + int passLen = static_cast(TWDataSize(password)); + int saltLen = static_cast(TWDataSize(salt)); + pbkdf2_hmac_sha256( + TWDataBytes(password), + passLen, + TWDataBytes(salt), + saltLen, + iterations, + key.data(), + dkLen + ); + return TWDataCreateWithData(&key); +} + +TWData* _Nullable TWPBKDF2HmacSha512(TWData* _Nonnull password, TWData* _Nonnull salt, + uint32_t iterations, uint32_t dkLen) { + Data key(dkLen); + int passLen = static_cast(TWDataSize(password)); + int saltLen = static_cast(TWDataSize(salt)); + pbkdf2_hmac_sha512( + TWDataBytes(password), + passLen, + TWDataBytes(salt), + saltLen, + iterations, + key.data(), + dkLen + ); + return TWDataCreateWithData(&key); +} 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 10d68e60a7f..6459b947a05 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.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 @@ -48,16 +46,22 @@ struct TWPublicKey *_Nonnull TWPublicKeyUncompressed(struct TWPublicKey *_Nonnul return new TWPublicKey{ pk->impl.extended() }; } -bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *signature, TWData *message) { +bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *message) { const auto& s = *reinterpret_cast(signature); const auto& m = *reinterpret_cast(message); return pk->impl.verify(s, m); } -bool TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { +bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *message) { const auto& s = *reinterpret_cast(signature); const auto& m = *reinterpret_cast(message); - return pk->impl.verifySchnorr(s, m); + return pk->impl.verifyAsDER(s, m); +} + +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.verifyZilliqa(s, m); } enum TWPublicKeyType TWPublicKeyKeyType(struct TWPublicKey *_Nonnull publicKey) { diff --git a/src/interface/TWRippleXAddress.cpp b/src/interface/TWRippleXAddress.cpp deleted file mode 100644 index 86b97be6bcc..00000000000 --- a/src/interface/TWRippleXAddress.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 "../Ripple/XAddress.h" - -#include -#include -#include - -#include - -using namespace TW; -using namespace TW::Ripple; - -bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippleXAddress *_Nonnull rhs) { - return lhs->impl == rhs->impl; -} - -bool TWRippleXAddressIsValidString(TWString *_Nonnull string) { - auto s = reinterpret_cast(string); - return XAddress::isValid(*s); -} - -struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_Nonnull string) { - auto s = reinterpret_cast(string); - try { - const auto address = XAddress(*s); - return new TWRippleXAddress{ std::move(address) }; - } catch (...) { - return nullptr; - } -} - -struct TWRippleXAddress *_Nonnull TWRippleXAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, const uint32_t tag) { - return new TWRippleXAddress{ XAddress(publicKey->impl, tag) }; -} - -void TWRippleXAddressDelete(struct TWRippleXAddress *_Nonnull address) { - delete address; -} - -TWString *_Nonnull TWRippleXAddressDescription(struct TWRippleXAddress *_Nonnull address) { - const auto string = address->impl.string(); - return TWStringCreateWithUTF8Bytes(string.c_str()); -} - -uint32_t TWRippleXAddressTag(struct TWRippleXAddress *_Nonnull address) { - return address->impl.tag; -} \ No newline at end of file diff --git a/src/interface/TWSegwitAddress.cpp b/src/interface/TWSegwitAddress.cpp index a3c8531c020..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,20 +11,19 @@ #include using namespace TW; -using namespace TW::Bitcoin; bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; } bool TWSegwitAddressIsValidString(TWString *_Nonnull string) { - auto s = reinterpret_cast(string); - return SegwitAddress::isValid(*s); + auto* s = reinterpret_cast(string); + return Bitcoin::SegwitAddress::isValid(*s); } struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Nonnull string) { - auto s = reinterpret_cast(string); - auto dec = SegwitAddress::decode(*s); + auto* s = reinterpret_cast(string); + 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, 0, stringForHRP(hrp)); + const auto address = Bitcoin::SegwitAddress(publicKey->impl, stringForHRP(hrp)); return new TWSegwitAddress{ std::move(address) }; } @@ -52,6 +49,10 @@ enum TWHRP TWSegwitAddressHRP(struct TWSegwitAddress *_Nonnull address) { return hrpForString(address->impl.hrp.c_str()); } +int TWSegwitAddressWitnessVersion(struct TWSegwitAddress *_Nonnull address) { + return address->impl.witnessVersion; +} + TWData *_Nonnull TWSegwitAddressWitnessProgram(struct TWSegwitAddress *_Nonnull address) { return TWDataCreateWithBytes(address->impl.witnessProgram.data(), address->impl.witnessProgram.size()); } 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/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 cb4625a40c7..95cf199b060 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -1,53 +1,123 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing 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 "../HexCoding.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 TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { +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) }; + 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 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 TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation( + TWData* _Nonnull privateKey, + TWString* _Nonnull name, + TWData* _Nonnull password, + enum TWCoinType coin, + enum TWStoredKeyEncryption encryption, + enum TWDerivation derivation +) { + 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{ KeyStore::StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData, encryption, derivation) }; + } catch (...) { + return nullptr; + } +} + +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportPrivateKeyEncodedWithEncryption(privateKey, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { + try { + const auto& privateKeyString = *reinterpret_cast(privateKey); + const auto& nameString = *reinterpret_cast(name); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ KeyStore::StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyString, encryption) }; + } catch (...) { + return nullptr; + } +} + +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryptionAndDerivation( + TWString* _Nonnull privateKey, + TWString* _Nonnull name, + TWData* _Nonnull password, + enum TWCoinType coin, + enum TWStoredKeyEncryption encryption, + enum TWDerivation derivation +) { + try { + const auto& privateKeyString = *reinterpret_cast(privateKey); + const auto& nameString = *reinterpret_cast(name); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ KeyStore::StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyString, encryption, derivation) }; } 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; } @@ -56,8 +126,8 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo 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)) }; + const auto parsed = nlohmann::json::parse(std::string(d.begin(), d.end())); + return new TWStoredKey{ KeyStore::StoredKey::createWithJson(parsed) }; } catch (...) { return nullptr; } @@ -79,7 +149,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) { @@ -104,15 +174,41 @@ struct TWAccount* _Nullable TWStoredKeyAccountForCoin(struct TWStoredKey* _Nonnu } } +struct TWAccount* _Nullable TWStoredKeyAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWDerivation derivation, struct TWHDWallet* _Nullable wallet) { + try { + if (wallet == nullptr) { + return nullptr; + } + const auto account = key->impl.account(coin, derivation, wallet->impl); + return new TWAccount{ account }; + } catch (...) { + return nullptr; + } +} + void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin) { key->impl.removeAccount(coin); } -void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey) { +void TWStoredKeyRemoveAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, enum TWDerivation derivation) { + key->impl.removeAccount(coin, derivation); +} + +void TWStoredKeyRemoveAccountForCoinDerivationPath(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWString* _Nonnull derivationPath) { + const auto dp = TW::DerivationPath(*reinterpret_cast(derivationPath)); + key->impl.removeAccount(coin, dp); +} + +void TWStoredKeyAddAccountDerivation(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, enum TWDerivation derivation, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey) { const auto& addressString = *reinterpret_cast(address); - const auto& extetndedPublicKeyString = *reinterpret_cast(extetndedPublicKey); + const auto& publicKeyString = *reinterpret_cast(publicKey); + const auto& extendedPublicKeyString = *reinterpret_cast(extendedPublicKey); const auto dp = TW::DerivationPath(*reinterpret_cast(derivationPath)); - key->impl.addAccount(addressString, coin, dp, extetndedPublicKeyString); + key->impl.addAccount(addressString, coin, derivation, dp, publicKeyString, extendedPublicKeyString); +} + +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey) { + return TWStoredKeyAddAccountDerivation(key, address, coin, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); } bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) { @@ -135,6 +231,20 @@ TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, } } +TWString* _Nullable TWStoredKeyDecryptPrivateKeyEncoded(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { + try { + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + const auto encodedStr = key->impl.decryptPrivateKeyEncoded(passwordData); + return TWStringCreateWithUTF8Bytes(encodedStr.c_str()); + } catch (...) { + return nullptr; + } +} + +bool TWStoredKeyHasPrivateKeyEncoded(struct TWStoredKey* _Nonnull key) { + return key->impl.encodedPayload.has_value(); +} + TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); @@ -178,3 +288,19 @@ bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull return false; } } + +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; + } + const std::string params = key->impl.payload.json().dump(); + return TWStringCreateWithUTF8Bytes(params.c_str()); +} 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 3e6f3a514e5..3641660b8d8 100644 --- a/src/interface/TWString.cpp +++ b/src/interface/TWString.cpp @@ -1,45 +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 +#include #include TWString *_Nonnull TWStringCreateWithUTF8Bytes(const char *_Nonnull bytes) { - auto s = new std::string(bytes); + auto* s = new std::string(bytes); return s; } TWString *_Nonnull TWStringCreateWithRawBytes(const uint8_t *_Nonnull bytes, size_t size) { - auto s = new std::string(bytes, bytes + size); + auto* s = new std::string(bytes, bytes + size); return s; } size_t TWStringSize(TWString *_Nonnull string) { - auto s = reinterpret_cast(string); + auto* s = reinterpret_cast(string); return s->size(); } char TWStringGet(TWString *_Nonnull string, size_t index) { - auto s = reinterpret_cast(string); + auto* s = reinterpret_cast(string); return (*s)[index]; } const char *_Nonnull TWStringUTF8Bytes(TWString *_Nonnull string) { - auto s = reinterpret_cast(string); + auto* s = reinterpret_cast(string); return s->c_str(); } 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) { - auto lv = reinterpret_cast(lhs); - auto rv = reinterpret_cast(rhs); + auto* lv = reinterpret_cast(lhs); + auto* rv = reinterpret_cast(rhs); return *lv == *rv; } 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 new file mode 100644 index 00000000000..5db643063ee --- /dev/null +++ b/src/interface/TWTransactionCompiler.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "TransactionCompiler.h" +#include "DataVector.h" + +#include + +using namespace TW; + +TWData *_Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, TWData *_Nonnull txInputData) { + Data result; + try { + 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()); +} + +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::compileWithSignatures(coinType, inputData, signaturesVec, publicKeysVec); + } catch (...) {} // return empty + return TWDataCreateWithBytes(result.data(), result.size()); +} + +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); + 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::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/TWTransactionUtil.cpp b/src/interface/TWTransactionUtil.cpp new file mode 100644 index 00000000000..3f99dd81a83 --- /dev/null +++ b/src/interface/TWTransactionUtil.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/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 ad23a52ebc3..744a2562070 100644 --- a/src/proto/Algorand.proto +++ b/src/proto/Algorand.proto @@ -3,32 +3,79 @@ syntax = "proto3"; package TW.Algorand.Proto; option java_package = "wallet.core.jni.proto"; -message TransactionPay { +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; - uint64 fee = 2; - uint64 amount = 3; - uint64 first_round = 4; - uint64 last_round = 5; + + // 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; } // Input data necessary to create a signed transaction. message SigningInput { - // netowrk / chain id + // 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 amount + uint64 fee = 7; + // public key + bytes public_key = 8; + + // message payload oneof message_oneof { - TransactionPay transaction_pay = 10; + Transfer transfer = 10; + AssetTransfer asset_transfer = 11; + AssetOptIn asset_opt_in = 12; } } -// 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..9b0e1533867 --- /dev/null +++ b/src/proto/Aptos.proto @@ -0,0 +1,203 @@ +// 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; +} + +message FungibleAssetTransferMessage { + // Fungible Asset address (string) + string metadata_address = 1; + // Destination Account address (string) + string to = 2; + // Amount to be transferred (uint64) + uint64 amount = 3; +} + +// 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; + LiquidStaking liquid_staking_message = 14; + TokenTransferCoinsMessage token_transfer_coins = 15; + FungibleAssetTransferMessage fungible_asset_transfer = 16; + } + + string abi = 21; +} + +// 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/BabylonStaking.proto b/src/proto/BabylonStaking.proto new file mode 100644 index 00000000000..7a846719379 --- /dev/null +++ b/src/proto/BabylonStaking.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; + +package TW.BabylonStaking.Proto; +option java_package = "wallet.core.jni.proto"; + +// Public key and corresponding signature. +message PublicKeySignature { + // Public key bytes. Can be either compressed (33 bytes) or x-only (32 bytes). + bytes public_key = 1; + // Signature 64-length byte array. + bytes signature = 2; +} + +message StakingInfo { + // User's public key. + bytes staker_public_key = 1; + // Finality provider's public key chosen by the user. + bytes finality_provider_public_key = 2; + // Staking Output's lock time. + // Equal to `global_parameters.staking_time` when creating a Staking transaction. + // or `global_parameters.unbonding_time` when creating an Unbonding transaction. + uint32 staking_time = 3; + // Retrieved from global_parameters.covenant_pks. + // Babylon nodes that can approve Unbonding tx or Slash the staked position when acting bad. + repeated bytes covenant_committee_public_keys = 4; + // Retrieved from global_parameters.covenant_quorum. + // Specifies the quorum required by the covenant committee for unbonding transactions to be confirmed. + uint32 covenant_quorum = 5; +} + +message InputBuilder { + // Spend a Staking Output via timelock path (staking time expired). + // In other words, create a Withdraw transaction. + message StakingTimelockPath { + StakingInfo params = 1; + } + + // Spend a Staking Output via unbonding path. + // In other words, create an Unbonding transaction. + message StakingUnbondingPath { + StakingInfo params = 1; + // Signatures signed by covenant committees. + // There can be less signatures than covenant public keys, but not less than `covenant_quorum`. + repeated PublicKeySignature covenant_committee_signatures = 2; + } + + // Spend a Staking Output via slashing path. + // Slashing path is only used in [ExpressOfInterest](https://github.com/babylonlabs-io/babylon-proto-ts/blob/ef42d04959b326849fe8c9773ab23802573ad407/src/generated/babylon/btcstaking/v1/tx.ts#L61). + // In other words, generate an unsigned Slashing transaction, pre-sign the staker's signature only and share to Babylon PoS chain. + message StakingSlashingPath { + StakingInfo params = 1; + // Empty in most of the cases. Staker's signature can be calculated without the fp signature. + PublicKeySignature finality_provider_signature = 2; + } + + // Spend an Unbonding Output via timelock path (unbonding time expired). + // In other words, create a Withdraw transaction spending an Unbonding transaction. + message UnbondingTimelockPath { + StakingInfo params = 1; + } + + // Spend an Unbonding Output via slashing path. + // Slashing path is only used in [ExpressOfInterest](https://github.com/babylonlabs-io/babylon-proto-ts/blob/ef42d04959b326849fe8c9773ab23802573ad407/src/generated/babylon/btcstaking/v1/tx.ts#L61). + // In other words, generate an unsigned Slashing transaction, pre-sign the staker's signature only and share to Babylon PoS chain. + message UnbondingSlashingPath { + StakingInfo params = 1; + // Empty in most of the cases. Staker's signature can be calculated without the fp signature. + PublicKeySignature finality_provider_signature = 2; + } +} + +message OutputBuilder { + // Create a Staking Output. + message StakingOutput { + StakingInfo params = 1; + } + + // Create an Unbonding Output. + message UnbondingOutput { + StakingInfo params = 1; + } + + // Creates an OP_RETURN output used to identify the staking transaction among other transactions in the Bitcoin ledger. + message OpReturn { + // Retrieved from global_parameters.Tag. + bytes tag = 1; + // User's public key. + bytes staker_public_key = 2; + // Finality provider's public key chosen by the user. + bytes finality_provider_public_key = 3; + // global_parameters.min_staking_time <= staking_time <= global_parameters.max_staking_time. + uint32 staking_time = 4; + } +} 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 846307db7c6..7fe597ee92e 100644 --- a/src/proto/Binance.proto +++ b/src/proto/Binance.proto @@ -3,140 +3,260 @@ syntax = "proto3"; package TW.Binance.Proto; 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; } @@ -162,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; @@ -213,11 +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; + + // OK (=0) or other codes in case of error + Common.Proto.SigningError error = 2; + + // 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 6e973f686b9..e88c95179a1 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,32 +110,73 @@ 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. + // If all inputs have final (`0xffffffff`) sequence numbers then `lockTime` is irrelevant. + // Otherwise, the transaction may not be added to a block until after `lockTime`. + // value < 500000000 : Block number at which this transaction is unlocked + // value >= 500000000 : UNIX timestamp at which this transaction is unlocked + uint32 lock_time = 12; + + // 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; + + // Whether to calculate the fee according to ZIP-0317 for the given transaction + // https://zips.z.cash/zip-0317#fee-calculation + bool zip_0317 = 18; + + // 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. @@ -107,16 +184,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 @@ -124,19 +201,69 @@ message TransactionPlan { // Optional error Common.Proto.SigningError error = 7; + + // 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; + + /// public key hash used for signing + bytes public_key_hash = 2; +} + +/// Transaction pre-signing output +message PreSigningOutput { + /// hash, public key list + repeated HashPublicKey hash_public_keys = 1; + + /// error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + /// error description + string error_message = 3; + + // 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..a0da684554f --- /dev/null +++ b/src/proto/BitcoinV2.proto @@ -0,0 +1,383 @@ +syntax = "proto3"; + +package TW.BitcoinV2.Proto; +option java_package = "wallet.core.jni.proto"; + +import "BabylonStaking.proto"; +import "Common.proto"; +import "DecredV2.proto"; +import "Utxo.proto"; +import "Zcash.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; + } +} + +// Public key and corresponding signature. +message PublicKeySignature { + // Public key bytes. Type of the public key depends on the context. + bytes public_key = 1; + // Signature 64-length byte array. + bytes signature = 2; +} + +message Input { + // Reference to the previous transaction's output. + Utxo.Proto.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; + // Spend a Staking Output via timelock path (staking time expired). + // In other words, create a Withdraw transaction. + BabylonStaking.Proto.InputBuilder.StakingTimelockPath babylon_staking_timelock_path = 15; + // Spend a Staking Output via unbonding path. + // In other words, create an Unbonding transaction. + BabylonStaking.Proto.InputBuilder.StakingUnbondingPath babylon_staking_unbonding_path = 16; + // Spend a Staking Output via slashing path. + // In other words, generate an unsigned slashing transaction, pre-sign the staker's signature only and share to Babylon PoS chain. + BabylonStaking.Proto.InputBuilder.StakingSlashingPath babylon_staking_slashing_path = 17; + // Spend an Unbonding Output via timelock path (unbonding time expired). + // In other words, create a Withdraw transaction spending an Unbonding transaction. + BabylonStaking.Proto.InputBuilder.UnbondingTimelockPath babylon_unbonding_timelock_path = 18; + // Spend an Unbonding Output via slashing path. + // In other words, generate an unsigned Slashing transaction, pre-sign the staker's signature only and share to Babylon PoS chain. + BabylonStaking.Proto.InputBuilder.UnbondingSlashingPath babylon_unbonding_slashing_path = 19; + } + } + + 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; + // Create a Babylon `Staking` output. + BabylonStaking.Proto.OutputBuilder.StakingOutput babylon_staking = 15; + // Create a Babylon `Unbonding` output. + BabylonStaking.Proto.OutputBuilder.UnbondingOutput babylon_unbonding = 16; + // Create a Babylon `Staking` OP_RETURN output. + BabylonStaking.Proto.OutputBuilder.OpReturn babylon_staking_op_return = 17; + } + } + + // 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; +} + +// Transaction builder used in `SigningInput`. +message TransactionBuilder { + // Transaction version. + TransactionVersion version = 1; + // (optional) Block height or timestamp indicating at what point transactions can be included in a block. + // Zero by default. + uint32 lock_time = 2; + // The inputs to spend. + repeated Input inputs = 3; + // The output of the transaction. Note that the change output is specified + // in the `change_output` field. + repeated Output outputs = 4; + // How the inputs should be selected. + InputSelector input_selector = 5; + // 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 = 6; + // (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 = 7; + // 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 = 8; + // 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 = 10; + } + oneof chain_specific { + // ZCash specific transaction data. + Zcash.Proto.TransactionBuilderExtraData zcash_extra_data = 20; + } +} + +// Partially Signed Bitcoin Transaction. +message Psbt { + // Partially Signed Bitcoin Transaction binary encoded. + bytes psbt = 1; +} + +message SigningInput { + // User private keys. + // Only required if the `sign` method is called. + repeated bytes private_keys = 1; + // User public keys. + // Only required if the `plan`, `preImageHash` methods are called. + repeated bytes public_keys = 2; + // 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 = 3; + // Whether disable auxiliary random data when signing. + // Use for testing **ONLY**. + bool dangerous_use_fixed_schnorr_rng = 4; + + // The transaction signing type. + oneof transaction { + // Build a transaction to be signed. + TransactionBuilder builder = 10; + // Finalize a Partially Signed Bitcoin Transaction by signing the rest of UTXOs. + Psbt psbt = 11; + } +} + +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; + // 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; + // Optional. Signed transaction serialized as PSBT. + // Set if `SigningInput.psbt` is used. + Psbt psbt = 9; + // Resulting transaction. + oneof transaction { + // Standard Bitcoin transaction. + Utxo.Proto.Transaction bitcoin = 15; + // ZCash transaction. + Zcash.Proto.Transaction zcash = 16; + // Decred transaction. + DecredV2.Proto.Transaction decred = 17; + } +} diff --git a/src/proto/Cardano.proto b/src/proto/Cardano.proto new file mode 100644 index 00000000000..b19b50b5cf6 --- /dev/null +++ b/src/proto/Cardano.proto @@ -0,0 +1,235 @@ +syntax = "proto3"; + +package TW.Cardano.Proto; +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 { + // 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; + + // 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 in the UTXO + uint64 amount = 3; + + // 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 + uint64 amount = 2; + + // optional token amounts + repeated TokenAmount token_amount = 3; +} + +// Collection of tokens with amount +message TokenBundle { + repeated TokenAmount token = 1; +} + +// Message for simple Transfer tx +message Transfer { + // Destination address as string + string to_address = 1; + + // Change address + string change_address = 2; + + // Requested ADA amount to be transferred. Output can be different only in use_max_amount case. + // Note that Cardano has a minimum amount per UTXO, see TWCardanoMinAdaAmount. + uint64 amount = 3; + + // Optional token(s) to be transferred + // Currently only one token type to be transferred per transaction is supported + TokenBundle token_amount = 4; + + // Request max amount option. If set, tx will send all possible amounts from all inputs, including all tokens; there will be no change; requested amount and token_amount is disregarded in this case. + bool use_max_amount = 5; + + // Optional fee overriding. If left to 0, optimal fee will be calculated. If set, exactly this value will be used as fee. + // Use it with care, it may result in underfunded or wasteful fee. + 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; +} + +// Vote delegation to a specific DRep or always abstain or no confidence +message VoteDelegation { + enum DRepType { + DREP_ID = 0; + DREP_ALWAYS_ABSTAIN = 2; + DREP_NO_CONFIDENCE = 3; + } + + // Staking address (as string) + string staking_address = 1; + + // DRep type + DRepType drep_type = 2; + + // DRep ID (only used when drep_type is DREP_ID) + string drep_id = 3; +} + +// 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; + + // coins in the output UTXO + uint64 amount = 2; + + // coin amount deducted as fee + uint64 fee = 3; + + // 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; + + // tokens in the output (optional) + repeated TokenAmount output_tokens = 6; + + // 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. +message SigningInput { + // Available input UTXOs + repeated TxInput utxos = 1; + + // 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; + + // Optional, used for vote delegation + VoteDelegation vote_delegation = 11; + + // Time-to-live time of the TX + uint64 ttl = 4; + + // Optional plan, if missing it will be computed + TransactionPlan plan = 5; + + // extra output UTXOs + repeated TxOutput extra_outputs = 10; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Encoded transaction bytes + bytes encoded = 1; + + // TxID, derived from transaction data, also needed for submission + bytes tx_id = 2; + + // 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 5391a97abc0..af13469d47f 100644 --- a/src/proto/Common.proto +++ b/src/proto/Common.proto @@ -3,25 +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; - // chain-generic, fee + // 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 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 + // 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 fc4afa9bc56..be631f5a452 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -3,22 +3,56 @@ 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; - int64 amount = 2; + + // amount, number as string + string amount = 2; } +// Fee incl. gas message Fee { + // Fee amount(s) repeated Amount amounts = 1; + + // Gas price uint64 gas = 2; } +// Block height, a revision and block height tuple. +// A height can be compared against another Height for the purposes of updating and freezing clients +message Height { + // the revision that the client is currently on + uint64 revision_number = 1; + // the height within the given revision + 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 { @@ -28,6 +62,22 @@ message Message { string type_prefix = 4; } + // cosmos-sdk/MsgTransfer, IBC transfer + message Transfer { + // IBC port, e.g. "transfer" + string source_port = 1; + // IBC connection channel, e.g. "channel-141", see apis /ibc/applications/transfer/v1beta1/denom_traces (connections) or /node_info (own channel) + string source_channel = 2; + Amount token = 3; + string sender = 4; + string receiver = 5; + // Timeout block height. Either timeout height or timestamp should be set. + // Recommendation is to set height, to rev. 1 and block current + 1000 (see api /blocks/latest) + Height timeout_height = 6; + // Timeout timestamp (in nanoseconds) relative to the current block timestamp. Either timeout height or timestamp should be set. + uint64 timeout_timestamp = 7; + } + // cosmos-sdk/MsgDelegate to stake message Delegate { string delegator_address = 1; @@ -53,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; @@ -60,40 +117,348 @@ message Message { string type_prefix = 3; } + // transfer within wasm/MsgExecuteContract, used by Terra Classic + message WasmTerraExecuteContractTransfer { + // 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, used by Terra Classic + message WasmTerraExecuteContractSend { + // 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; + } + + // thorchain/MsgSend + message THORChainSend { + bytes from_address = 1; + bytes to_address = 2; + repeated Amount amounts = 3; + } + + // 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; + + // 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; + } + + // 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; + } + + // MsgInstantiateContract defines a message for instantiating a new CosmWasm contract. + message WasmInstantiateContract { + string sender = 1; + string admin = 2; + uint64 code_id = 3; + string label = 4; + bytes msg = 5; + repeated Amount init_funds = 6; + } + 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 + // Required + bytes body_bytes = 1; + // The prepared serialized AuthInfo + // Optional. If not provided, will be generated from `SigningInput` parameters. + 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; - Delegate stake_message = 2; - Undelegate unstake_message = 3; - BeginRedelegate restake_message = 4; - WithdrawDelegationReward withdraw_stake_reward_message = 5; - RawJSON raw_json_message = 6; + Transfer transfer_tokens_message = 2; + Delegate stake_message = 3; + Undelegate unstake_message = 4; + BeginRedelegate restake_message = 5; + WithdrawDelegationReward withdraw_stake_reward_message = 6; + RawJSON raw_json_message = 7; + 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 = 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; + WasmInstantiateContract wasm_instantiate_contract_message = 24; } } -// Input data necessary to create a signed order. +// Options for transaction encoding: JSON (Amino, older) or Protobuf. +enum SigningMode { + JSON = 0; // JSON format, Pre-Stargate + Protobuf = 1; // Protobuf-serialized (binary), Stargate +} + +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 { - uint64 account_number = 1; - string chain_id = 2; - Fee fee = 3; - string memo = 4; - uint64 sequence = 5; + // 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; - bytes private_key = 6; + // Transaction fee + Fee fee = 4; - repeated Message messages = 7; + // Optional memo + string memo = 5; - BroadcastMode mode = 8; + // 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; - // Signed transaction in JSON. + + // Signed transaction in JSON (pre-Stargate case) string json = 2; + + // Signed transaction containing protobuf encoded, Base64-encoded form (Stargate case), + // wrapped in a ready-to-broadcast json. + string serialized = 3; + + // 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..7babfa24601 100644 --- a/src/proto/Decred.proto +++ b/src/proto/Decred.proto @@ -4,13 +4,15 @@ package TW.Decred.Proto; option java_package = "wallet.core.jni.proto"; import "Bitcoin.proto"; +import "BitcoinV2.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 +21,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,9 +36,14 @@ message TransactionInput { // Transaction version as defined by the sender. uint32 sequence = 2; - int64 valueIn = 3; - uint32 blockHeight = 4; - uint32 blockIndex = 5; + // 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. bytes script = 6; @@ -47,14 +54,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 +74,10 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 4; + + 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 = 6; } diff --git a/src/proto/DecredV2.proto b/src/proto/DecredV2.proto new file mode 100644 index 00000000000..3f37232a241 --- /dev/null +++ b/src/proto/DecredV2.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package TW.DecredV2.Proto; +option java_package = "wallet.core.jni.proto"; + +// Decred 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; + // The tree in utxo, only works for DCR. + uint32 tree = 3; +} + +// A transfer transaction +message Transaction { + // Serialization format + uint32 serialize_type = 1; + // Transaction version. + // Currently, version 1 is supported only. + uint32 version = 2; + // 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 + repeated TransactionOutput outputs = 4; + // Block height or timestamp indicating at what point transactions can be included in a block. + // Zero by default. + uint32 lock_time = 5; + // The block height at which the transaction expires and is no longer valid. + uint32 expiry = 6; +} + +// Decred transaction input. +message TransactionInput { + // Reference to the previous transaction's output. + OutPoint out_point = 1; + // Transaction version as defined by the sender. + uint32 sequence = 2; + // The amount of the input. + int64 value = 3; + // Creation block height. + uint32 block_height = 4; + // Index within the block. + uint32 block_index = 5; + // Computational script for confirming transaction authorization. + bytes script_sig = 6; +} + +// Decred transaction output. +message TransactionOutput { + // Transaction amount. + int64 value = 1; + // 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; +} 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 36cb5aaf2c2..00000000000 --- a/src/proto/Elrond.proto +++ /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. - -syntax = "proto3"; - -package TW.Elrond.Proto; -option java_package = "wallet.core.jni.proto"; - -// A transaction, typical balance transfer -message TransactionMessage { - uint64 nonce = 1; - string value = 2; - string receiver = 3; - string sender = 4; - uint64 gas_price = 5; - uint64 gas_limit = 6; - string data = 7; - string chain_id = 8; - uint32 version = 9; -} - -// Input data necessary to create a signed transaction. -message SigningInput { - bytes private_key = 1; - - oneof message_oneof { - TransactionMessage transaction = 2; - } -} - -// Transaction signing output. -message SigningOutput { - string encoded = 1; - string signature = 2; -} diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 68a53670128..e25d8fc92f9 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -3,11 +3,13 @@ syntax = "proto3"; package TW.Ethereum.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Transaction (transfer, smart contract call, ...) 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 @@ -16,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; @@ -57,13 +65,42 @@ 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; } + // Batch transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + message SCWalletBatch { + 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; + } + + // Batched calls to be executed on the smart contract wallet. + repeated BatchedCall calls = 1; + // Smart contract wallet type. + SCWalletType wallet_type = 2; + } + + // Execute transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + message SCWalletExecute { + // Transaction to be executed on the smart contract wallet. + // TODO currently, smart contract wallet address is specified in `SigningInput.toAddress`, but it will be refactored soon. + Transaction transaction = 1; + // Smart contract wallet type. + SCWalletType wallet_type = 2; + } + + // Payload transfer oneof transaction_oneof { Transfer transfer = 1; ERC20Transfer erc20_transfer = 2; @@ -71,41 +108,255 @@ message Transaction { ERC721Transfer erc721_transfer = 4; ERC1155Transfer erc1155_transfer = 5; ContractGeneric contract_generic = 6; + // Batch transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + SCWalletBatch scw_batch = 7; + // Execute transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + SCWalletExecute scw_execute = 8; } } +// Transaction type +enum TransactionMode { + // 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; + + // EIP-7702 transaction (with type 0x4); allows to set the code of a contract for an EOA. + // Note that `SetCode` transaction extends `Enveloped` transaction. + // https://eips.ethereum.org/EIPS/eip-7702 + SetCode = 4; +} + +// 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; +} + +// EIP-7702 compatible ERC-4337 structure that describes a transaction to be sent on behalf of a user +message UserOperationV0_7 { + // Entry point contract address + string entry_point = 1; + + // Account factory contract address + string factory = 2; + + // Account factory data + bytes factory_data = 3; + + // Account logic contract address + string sender = 4; + + // The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata + bytes pre_verification_gas = 5; + + // The amount of gas to allocate for the verification step + bytes verification_gas_limit = 6; + + // Address of paymaster + string paymaster = 7; + + // The amount of gas to allocate for the paymaster verification step + bytes paymaster_verification_gas_limit = 8; + + // The amount of gas to allocate for paymaster post ops + bytes paymaster_post_op_gas_limit = 9; + + // Paymaster data + bytes paymaster_data = 10; +} + +// 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; +} + +// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization. +message Authorization { + // Address to be authorized, a smart contract address. + string address = 2; + // If custom_signature isn't provided, the authorization will be signed with the provided private key, nonce and chainId + AuthorizationCustomSignature custom_signature = 3; +} + +// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization. +message AuthorizationCustomSignature { + // Chain id (uint256, serialized big endian). + bytes chain_id = 1; + // Nonce, the nonce of authority. + bytes nonce = 2; + // The signature, Hex-encoded. + string signature = 3; +} + +// Smart Contract Wallet type. +enum SCWalletType { + // ERC-4337 compatible smart contract wallet. + // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol + SimpleAccount = 0; + // Biz smart contract (Trust Wallet specific) through ERC-4337 EntryPoint. + Biz4337 = 1; + // Biz smart contract (Trust Wallet specific) directly through ERC-7702. + Biz = 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; - // Gas price (256-bit number) - bytes gas_price = 3; + // Transaction version selector: Legacy or enveloped, has impact on fee structure. + // Default is Legacy (value 0) + TransactionMode tx_mode = 3; + + // Gas price (uint256, serialized big endian) + // Relevant for legacy transactions only (disregarded for enveloped/EIP1559) + bytes gas_price = 4; + + // Gas limit (uint256, serialized big endian) + bytes gas_limit = 5; + + // 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; - // Gas limit (256-bit number) - bytes gas_limit = 4; + // 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 = 5; + // TODO currently, will be moved to each `Transaction` oneof soon. + string to_address = 8; - // Private key. - bytes private_key = 6; + // The secret private key used for signing (32 bytes). + bytes private_key = 9; - Transaction transaction = 7; + // The payload transaction + Transaction transaction = 10; + + // UserOperation for ERC-4337 wallets + oneof user_operation_oneof { + UserOperation user_operation = 11; + + // EIP-7702 compatible + UserOperationV0_7 user_operation_v0_7 = 13; + } + + // Optional list of addresses and storage keys that the transaction plans to access. + // Used in `TransactionMode::Enveloped` only. + repeated Access access_list = 12; + + // EIP7702 authorization. + // Used in `TransactionMode::SetOp` or `TransactionMode::UserOp`. + // Currently, we support delegation to only one authority at a time. + Authorization eip7702_authorization = 15; } -// 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; // 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 + Common.Proto.SigningError error = 6; + + // 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; + // Sign a EIP-7702 authorization tuple. + MessageType_eip7702_authorization = 5; +} + +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..dadbeef1bc9 --- /dev/null +++ b/src/proto/MultiversX.proto @@ -0,0 +1,133 @@ +// 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; + + // Relayer address + string relayer = 7; +} + +// 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 7bd8b2e2781..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 { - bytes method_name = 1; + // 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,22 +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; + + // 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..13abbd9c734 100644 --- a/src/proto/Nimiq.proto +++ b/src/proto/Nimiq.proto @@ -3,20 +3,37 @@ syntax = "proto3"; package TW.Nimiq.Proto; option java_package = "wallet.core.jni.proto"; +enum NetworkId { + UseDefault = 0; + // Default PoW Mainnet. + Mainnet = 42; + // PoS Mainnet starting at the PoW block height 3’456’000. + MainnetAlbatross = 24; +} + // 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; + + // Network ID. + NetworkId network_id = 6; } -// 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/Pactus.proto b/src/proto/Pactus.proto new file mode 100644 index 00000000000..ce3f9d3a02e --- /dev/null +++ b/src/proto/Pactus.proto @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Pactus.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +message TransactionMessage { + // The lock time for the transaction. + uint32 lock_time = 1; + // The transaction fee in NanoPAC. + int64 fee = 2; + // A memo string for the transaction (optional). + string memo = 3; + + oneof payload { + TransferPayload transfer = 10; + BondPayload bond = 11; + } +} + +// Transfer payload for creating a Transfer transaction between two accounts. +message TransferPayload { + // The sender's account address. + string sender = 1; + // The receiver's account address. + string receiver = 2; + // The amount to be transferred, specified in NanoPAC. + int64 amount = 3; +} + +// Bond payload for creating a Bond transaction from an account to a validator. +message BondPayload { + // The sender's account address. + string sender = 1; + // The receiver's validator address. + string receiver = 2; + // The stake amount in NanoPAC. + int64 stake = 3; + // The public key of the validator (only set when creating a new validator). + string public_key = 4; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + TransactionMessage transaction = 2; +} + +// Transaction signing output. +message SigningOutput { + // Transaction ID (Hash). + bytes transaction_id = 1; + // Signed and encoded transaction bytes. + bytes signed_transaction_data = 2; + // Signature the signed transaction. + bytes signature = 3; + // A possible error, `OK` if none. + Common.Proto.SigningError error = 4; + // Detailed error message, if any. + string error_message = 5; +} diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index a0f07d1b0c6..c95f4b22e97 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,48 +21,208 @@ 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; + + // Staking.Bond call indices + CallIndices bond_call_indices = 6; + + // Staking.Nominate call indices + CallIndices nominate_call_indices = 7; + } + + // Bond extra, with nominators + message BondExtraAndNominate { + // amount (uint256, serialized big endian) + bytes value = 1; + + // list of nominators + repeated string nominators = 2; + + // Batch call indices + CallIndices call_indices = 3; + + // Staking.BondExtra call indices + CallIndices bond_extra_call_indices = 4; + + // Staking.Nominate call indices + CallIndices nominate_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; + + // Staking.Chill call indices + CallIndices chill_call_indices = 3; + + // Staking.Unbond call indices + CallIndices unbond_call_indices = 4; } - message Chill {} + // Chill + message Chill { + // call indices + CallIndices call_indices = 1; + } + // Payload messsage oneof message_oneof { Bond bond = 1; BondAndNominate bond_and_nominate = 2; @@ -72,6 +231,9 @@ message Staking { WithdrawUnbonded withdraw_unbonded = 5; Nominate nominate = 6; Chill chill = 7; + ChillAndUnbond chill_and_unbond = 8; + Rebond rebond = 9; + BondExtraAndNominate bond_extra_and_nominate = 10; } } @@ -80,29 +242,48 @@ 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; } } -// 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; +} \ No newline at end of file diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto new file mode 100644 index 00000000000..05d69d11dfe --- /dev/null +++ b/src/proto/Polymesh.proto @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Polymesh.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; +import "Polkadot.proto"; + +// Balance transfer transaction +message Balance { + // transfer + message Transfer { + // destination address + string to_address = 1; + + // amount (uint256, serialized big endian) + bytes value = 2; + + // max 32 chars + string memo = 3; + + // call indices + Polkadot.Proto.CallIndices call_indices = 4; + } + + oneof message_oneof { + Transfer transfer = 1; + } +} + +// 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 + Polkadot.Proto.RewardDestination reward_destination = 3; + + // call indices + Polkadot.Proto.CallIndices call_indices = 4; + } + + // Bond extra amount + message BondExtra { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Unbond + message Unbond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Rebond + message Rebond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Withdraw unbonded amounts + message WithdrawUnbonded { + int32 slashing_spans = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Nominate + message Nominate { + // list of nominators + repeated string nominators = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Chill + message Chill { + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + } + + // Payload messsage + oneof message_oneof { + Bond bond = 1; + BondExtra bond_extra = 2; + Unbond unbond = 3; + WithdrawUnbonded withdraw_unbonded = 4; + Nominate nominate = 5; + Chill chill = 6; + Rebond rebond = 7; + } +} + +message IdentityId { + // 32 byte identity id. + bytes id = 1; +} + +message AssetId { + // 16 byte asset id. + bytes id = 1; +} + +message PortfolioId { + // IdentityId of the portfolio owner. + IdentityId identity = 1; + // If `default` is true ignore the `user` field. + bool default = 2; + // The users portfolio number. (ignored if `default = true`) + uint64 user = 3; +} + +message SecondaryKeyPermissions { + enum RestrictionKind { + Whole = 0; + These = 1; + Except = 2; + } + + message AssetPermissions { + RestrictionKind kind = 1; + repeated AssetId assets = 2; + } + + message PortfolioPermissions { + RestrictionKind kind = 1; + repeated PortfolioId portfolios = 2; + } + + message PalletPermissions { + string pallet_name = 1; + RestrictionKind kind = 2; + repeated string extrinsic_names = 3; + } + + message ExtrinsicPermissions { + RestrictionKind kind = 1; + repeated PalletPermissions pallets = 2; + } + + // The assets permissions of the secondary key. + AssetPermissions asset = 1; + + // The pallet/extrinsic permissions of the secondary key. + ExtrinsicPermissions extrinsic = 2; + + // The portfolio permissions of the secondary key. + PortfolioPermissions portfolio = 3; +} + +// Identity module +message Identity { + // Identity::join_identity_as_key call + message JoinIdentityAsKey { + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + + // auth id + uint64 auth_id = 2; + } + + // Identity::leave_identity_as_key call + message LeaveIdentityAsKey { + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + } + + // Identity::add_authorization call + message AddAuthorization { + message Authorization { + // Authorization data. + oneof auth_oneof { + // AttestPrimaryKeyRotation(IdentityId) = 1 + // RotatePrimaryKey = 2 + // TransferTicker(Ticker) = 3 + // AddMultiSigSigner(AccountId) = 4 + // TransferAssetOwnership(AssetId) = 5 + SecondaryKeyPermissions join_identity = 6; + // PortfolioCustody(PortfolioId) = 7 + // BecomeAgent(AssetId, AgentGroup) = 8 + // AddRelayerPayingKey(AccountId, AccountId, Balance) = 9 + // RotatePrimaryKeyToSecondary(Permissions) = 10 + } + } + + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + + // address that will be added to the Identity + string target = 2; + + // Authorization. + Authorization authorization = 3; + + // expire time, unix seconds + uint64 expiry = 4; + } + + oneof message_oneof { + JoinIdentityAsKey join_identity_as_key = 1; + AddAuthorization add_authorization = 2; + LeaveIdentityAsKey leave_identity_as_key = 3; + } +} + +// Utility pallet transaction +message Utility { + enum BatchKind { + // Batch multiple calls, stoping on the first error. + // + // Each call in the batch is executed in its own transaction. + // When one call fails only that transaction will be rolled back + // and any following calls in the batch will be skipped. + StopOnError = 0; + // Batch multiple calls and execute them in a single atomic transaction. + // The whole transaction will rollback if any of the calls fail. + Atomic = 1; + // Batch multiple calls. Unlike `Batch` this will continue even + // if one of the calls failed. + // + // Each call in the batch is executed in its own transaction. + // When a call fails its transaction will be rolled back and the error + // will be emitted in an event. + // + // Execution will continue until all calls in the batch have been executed. + Optimistic = 2; + } + + message Batch { + // The type of batch. + BatchKind kind = 1; + + // batched calls. + repeated RuntimeCall calls = 2; + + // call indices + Polkadot.Proto.CallIndices call_indices = 3; + } + + oneof message_oneof { + Batch batch = 1; + } +} + +// Polymesh runtime call. +message RuntimeCall { + // Top-level pallets. + oneof pallet_oneof { + Balance balance_call = 1; + Staking staking_call = 2; + Identity identity_call = 3; + Utility utility_call = 4; + } +} + +// Input data necessary to create a signed transaction. +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; + + // Optional tip to pay, big integer + bytes tip = 6; + + // Optional time validity limit, recommended, for replay-protection. Empty means Immortal. + Polkadot.Proto.Era era = 7; + + // The secret private key used for signing (32 bytes). + bytes private_key = 8; + + // Network type + uint32 network = 9; + + // Payload call + RuntimeCall runtime_call = 10; +} + +// 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; +} \ No newline at end of file diff --git a/src/proto/Ripple.proto b/src/proto/Ripple.proto index 8d0be3c317e..5d8e0f89bab 100644 --- a/src/proto/Ripple.proto +++ b/src/proto/Ripple.proto @@ -3,28 +3,187 @@ syntax = "proto3"; package TW.Ripple.Proto; 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 + // It must fit uint32 + uint64 destination_tag = 4; +} + +// https://xrpl.org/escrowcreate.html +message OperationEscrowCreate { + // Escrow amount + int64 amount = 1; + + // Beneficiary account + string destination = 2; + + // Destination Tag + // It must fit uint32 + uint64 destination_tag = 3; + + // Escrow expire time + // It must fit uint32 + uint64 cancel_after = 4; + + // Escrow release time + // It must fit uint32 + uint64 finish_after = 5; + + // Hex-encoded 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 + uint32 offer_sequence = 2; +} + +// https://xrpl.org/escrowfinish.html +message OperationEscrowFinish { + // Funding account + string owner = 1; + + // Escrow transaction sequence + uint32 offer_sequence = 2; + + // Hex-encoded crypto condition + string condition = 3; + + // Hex-encoded fulfillment matching condition + string fulfillment = 4; +} + +// https://xrpl.org/nftokenburn.html +message OperationNFTokenBurn { + // Hex-encoded H256 NFTokenId + string nftoken_id = 1; +} + +// https://xrpl.org/nftokencreateoffer.html +message OperationNFTokenCreateOffer { + // Hex-encoded Hash256 NFTokenId + string nftoken_id = 1; + + // Destination account + string destination = 2; +} + +// https://xrpl.org/nftokenacceptoffer.html +message OperationNFTokenAcceptOffer { + // Hex-encoded Hash256 NFTokenOffer + string sell_offer = 1; +} + +// https://xrpl.org/nftokencanceloffer.html +message OperationNFTokenCancelOffer { + // Hex-encoded Vector256 NFTokenOffers + repeated string token_offers = 1; +} + // Input data necessary to create a signed transaction. message SigningInput { - int64 amount = 1; + // Transfer fee + int64 fee = 1; + + // Account sequence number + uint32 sequence = 2; + + // Ledger sequence number + uint32 last_ledger_sequence = 3; + + // Source account + string account = 4; + + // Transaction flags, optional + // It must fit uint32 + uint64 flags = 5; + + // The secret private key used for signing (32 bytes). + bytes private_key = 6; - int64 fee = 2; + oneof operation_oneof { + OperationTrustSet op_trust_set = 7; - int32 sequence = 3; + OperationPayment op_payment = 8; - int32 last_ledger_sequence = 4; + OperationNFTokenBurn op_nftoken_burn = 9; - string account = 5; + OperationNFTokenCreateOffer op_nftoken_create_offer = 10; - string destination = 6; + OperationNFTokenAcceptOffer op_nftoken_accept_offer = 11; - int64 destination_tag = 7; + OperationNFTokenCancelOffer op_nftoken_cancel_offer = 12; - int64 flags = 8; + OperationEscrowCreate op_escrow_create = 16; - bytes private_key = 9; + OperationEscrowCancel op_escrow_cancel = 17; + + OperationEscrowFinish op_escrow_finish = 18; + } + + // Only used by tss chain-integration. + bytes public_key = 15; + + // Generate a transaction from its JSON representation. + // The following parameters can be replaced from the `SigningInput` Protobuf: + // * Account + // * SigningPubKey + // * Fee + // * Sequence + // * LastLedgerSequence + string raw_json = 20; + + // Arbitrary integer used to identify the reason for this payment, or a sender on whose behalf this transaction is made. + // Conventionally, a refund should specify the initial payment's SourceTag as the refund payment's DestinationTag. + // It must fit uint32. + uint64 source_tag = 25; } -// 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 99a934db353..c759769d2b1 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -3,70 +3,350 @@ 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; + + // optional memo + string memo = 3; + + // optional referenced public keys + repeated string references = 4; } -message Stake { +// 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 { - string validator_pubkey = 1; + // 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 { - string validator_pubkey = 1; + // staking account + string stake_account = 1; + + // withdrawal amount uint64 value = 2; } +// Technical structure to group a staking account and an amount +message StakeAccountValue { + // staking account + string stake_account = 1; + + // amount + uint64 value = 2; +} + +// Withdraw amounts from stake accounts +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 + + // 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 + + // 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; - Stake stake_transaction = 4; - DeactivateStake deactivate_stake_transaction = 5; - WithdrawStake withdraw_transaction = 6; - CreateTokenAccount create_token_account_transaction = 7; - TokenTransfer token_transfer_transaction = 8; - CreateAndTransferToken create_and_transfer_token_transaction = 9; + 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; +} + +message MessageSigningInput { + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // A UTF-8 regular message to sign. + string message = 2; +} + +message MessageSigningOutput { + // The signature, Base58-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, Base58-encoded. + string signature = 3; } diff --git a/src/proto/Stellar.proto b/src/proto/Stellar.proto index 1698cf084bd..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,47 +36,95 @@ 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; +} + +// 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; + + // 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; OperationChangeTrust op_change_trust = 8; + OperationCreateClaimableBalance op_create_claimable_balance = 14; + OperationClaimClaimableBalance op_claim_claimable_balance = 15; } + // Memo oneof memo_type_oneof { MemoVoid memo_void = 9; MemoText memo_text = 10; @@ -78,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..55f23922926 --- /dev/null +++ b/src/proto/Sui.proto @@ -0,0 +1,192 @@ +// 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; + string raw_json = 10; + } + + // 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; +} + +// Message signing input. +message MessageSigningInput { + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // A UTF-8 regular message to sign. + string message = 2; +} + +// Message signing output. +message MessageSigningOutput { + // The signature, a 97-byte array encoded in base64. + // The first byte indicates the signature scheme (currently set to 0x00, as we only support ED25519). + // The following 64 bytes represent the raw ED25519 signature, while the next 32 bytes contain the public key. + 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 verifying input. +message MessageVerifyingInput { + // The message signed. + string message = 1; + + // Public key that will verify the message. + // It must be equal to the public key encoded in the signature. + bytes public_key = 2; + + // The signature, a 97-byte array encoded in base64. + // Same as the signature field in MessageSigningOutput. + string signature = 3; +} diff --git a/src/proto/THORChainSwap.proto b/src/proto/THORChainSwap.proto new file mode 100644 index 00000000000..69b6d257e16 --- /dev/null +++ b/src/proto/THORChainSwap.proto @@ -0,0 +1,132 @@ +syntax = "proto3"; + +package TW.THORChainSwap.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Bitcoin.proto"; +import "Ethereum.proto"; +import "Binance.proto"; +import "Cosmos.proto"; + +// Supported blockchains +enum Chain { + THOR = 0; + BTC = 1; + ETH = 2; + BNB = 3; + DOGE = 4; + BCH = 5; + LTC = 6; + ATOM = 7; + AVAX = 8; + BSC = 9; +} + +// Predefined error codes +enum ErrorCode { + // OK + OK = 0; + Error_general = 1; + Error_Input_proto_deserialization = 2; + Error_Unsupported_from_chain = 13; + Error_Unsupported_to_chain = 14; + Error_Invalid_from_address = 15; + Error_Invalid_to_address = 16; + Error_Invalid_vault_address = 21; + Error_Invalid_router_address = 22; +} + +// 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 + Asset from_asset = 1; + + // Source address, on source chain + string from_address = 2; + + // Destination chain+asset, on destination chain + Asset to_asset = 3; + + // Destination address, on destination chain + string to_address = 4; + + // ThorChainSwap vault, on the source chain. Should be queried afresh, as it may change + string vault_address = 5; + + // ThorChain router, only in case of Ethereum source network + string router_address = 6; + + // The source amount, integer as string, in the smallest native unit of the chain + string from_amount = 7; + + // 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/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. + oneof signing_input_oneof { + 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..afcd7154286 --- /dev/null +++ b/src/proto/TheOpenNetwork.proto @@ -0,0 +1,118 @@ +// 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 + // uint128 / big endian byte order + bytes 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; + + // Optional raw one-cell BoC encoded in Base64. + // Can be used to deploy a smart contract. + string state_init = 6; + + // One of the Transfer message payloads (optional). + oneof payload { + // Jetton transfer payload. + JettonTransfer jetton_transfer = 7; + // TON transfer with custom payload (contract call). Raw one-cell BoC encoded in Base64. + string custom_payload = 8; + } +} + +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 + // uint128 / big endian byte order + bytes 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 + // uint128 / big endian byte order + bytes forward_amount = 5; + + // Optional raw one-cell BoC encoded in Base64. + // Can be used in the case of mintless jetton transfers. + string custom_payload = 6; +} + +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/TransactionCompiler.proto b/src/proto/TransactionCompiler.proto new file mode 100644 index 00000000000..5afcd7f51ba --- /dev/null +++ b/src/proto/TransactionCompiler.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package TW.TxCompiler.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +/// Transaction pre-signing output +message PreSigningOutput { + /// Pre-image data hash that will be used for signing + bytes data_hash = 1; + + /// Pre-image data + 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/Tron.proto b/src/proto/Tron.proto index 3a6b635b169..3130965dc82 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; @@ -121,9 +228,12 @@ message Transaction { // Transaction block header. BlockHeader block_header = 3; - // Transaction fee limit + // Transaction fee limit. int64 fee_limit = 4; + // Transaction memo. + string memo = 5; + // Contract. oneof contract_oneof { TransferContract transfer = 10; @@ -136,18 +246,31 @@ 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. + // TODO: This field can be removed in the future, as we can use raw_json.txID instead. + string txId = 3; + + // Raw JSON data from the DApp, which contains fields 'txID', 'raw_data' and 'raw_data_hex' normally. + string raw_json = 4; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Transaction identifier. bytes id = 1; @@ -158,5 +281,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/Utxo.proto b/src/proto/Utxo.proto new file mode 100644 index 00000000000..863ec5ad99a --- /dev/null +++ b/src/proto/Utxo.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package TW.Utxo.Proto; +option java_package = "wallet.core.jni.proto"; + +// 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 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 Transaction { + // The protocol version, is currently expected to be 1 or 2 (BIP68). + uint32 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; +} 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/Zcash.proto b/src/proto/Zcash.proto new file mode 100644 index 00000000000..af873a04e76 --- /dev/null +++ b/src/proto/Zcash.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package TW.Zcash.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Utxo.proto"; + +message TransactionBuilderExtraData { + // Currently, `branch_id` is the only configurable Zcash specific parameter. + // There can also be `version_group_id` configured in the future. + bytes branch_id = 1; + + // Zero in most cases. + uint32 expiry_height = 2; + + // Whether to calculate the fee according to ZIP-0317 for the given transaction + // https://zips.z.cash/zip-0317#fee-calculation + bool zip_0317 = 3; +} + +message Transaction { + /// Transaction version. + /// Currently, version 4 (0x80000004) is supported only. + uint32 version = 1; + // If transaction version is 4 (0x80000004), version group ID is 0x892F2085. + uint32 version_group_id = 2; + // The transaction inputs. + repeated Utxo.Proto.TransactionInput inputs = 3; + // The transaction outputs. + repeated Utxo.Proto.TransactionOutput outputs = 4; + // Block height or timestamp indicating at what point transactions can be included in a block. + // Zero by default. + uint32 lock_time = 5; + // Expiry height. + uint32 expiry_height = 6; + // Sapling value balance for the transaction. + // Always 0 for a transparent transaction. + int64 sapling_value_balance = 7; + // Consensus branch ID for the epoch of the block containing the transaction. + bytes branch_id = 8; +} 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..5daa97f68f5 --- /dev/null +++ b/src/rust/RustCoinEntry.cpp @@ -0,0 +1,118 @@ +// 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 if (const auto* ss58Prefix = std::get_if(&addressPrefix); ss58Prefix) { + return Rust::tw_any_address_is_valid_ss58(addressStr.get(), static_cast(coin), static_cast(*ss58Prefix)); + } else { + throw std::invalid_argument("`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 if (const auto* ss58Prefix = std::get_if(&addressPrefix); ss58Prefix) { + anyAddressRaw = Rust::tw_any_address_create_ss58_with_public_key(twPublicKey.get(), + static_cast(coin), + static_cast(*ss58Prefix)); + } else { + throw std::invalid_argument("`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..14cb997ea6c --- /dev/null +++ b/src/rust/Wrapper.h @@ -0,0 +1,252 @@ +// 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); +} + +inline std::shared_ptr wrapTWPrivateKey(TWPrivateKey* privateKey) { + return std::shared_ptr(privateKey, tw_private_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; + + ~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; + + ~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 2c61d2edbcc..43c6999a21a 100644 --- a/src/uint256.h +++ b/src/uint256.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,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) { @@ -55,12 +40,18 @@ inline uint256_t load(const std::string& data) { return result; } -/// Stores a `uint256_t` as a collection of bytes. -inline Data store(const uint256_t& v) { +/// Stores a `uint256_t` as a collection of bytes, with optional padding (typically to 32 bytes). +/// If minLen is given (non-zero), and result is shorter, it is padded (with zeroes, on the left, big endian) +inline Data store(const uint256_t& v, byte minLen = 0) { using boost::multiprecision::cpp_int; Data bytes; bytes.reserve(32); export_bits(v, std::back_inserter(bytes), 8); + if (minLen && bytes.size() < minLen) { + Data padded(minLen - bytes.size()); + append(padded, bytes); + return padded; + } return bytes; } diff --git a/swift/.gitignore b/swift/.gitignore index 6d26577aac6..f392a48ec06 100644 --- a/swift/.gitignore +++ b/swift/.gitignore @@ -10,3 +10,8 @@ Pods/ cpp.xcconfig *.xcodeproj *.xcworkspace + +# fastlane +fastlane/README.md +fastlane/report.xml +WalletCoreRs.xcframework diff --git a/.swiftlint.yml b/swift/.swiftlint.yml similarity index 100% rename from .swiftlint.yml rename to swift/.swiftlint.yml diff --git a/swift/Example/Example.xcodeproj/project.pbxproj b/swift/Example/Example.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..3b60297c680 --- /dev/null +++ b/swift/Example/Example.xcodeproj/project.pbxproj @@ -0,0 +1,737 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 24ED63DA1EB5B7F749EC81BE /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F758B5F96DA1A1E6EC7757D4 /* Pods_Example.framework */; }; + 459F00FA99B5877F70019273 /* Pods_Example_ExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14CB7F4DE9A40E2AE6208BBA /* Pods_Example_ExampleTests.framework */; }; + BBE2F11E2631644B00DB6574 /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE2F11D2631644A00DB6574 /* ExampleApp.swift */; }; + BBE2F1202631644B00DB6574 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE2F11F2631644B00DB6574 /* ContentView.swift */; }; + BBE2F1222631644C00DB6574 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBE2F1212631644C00DB6574 /* Assets.xcassets */; }; + BBE2F1252631644C00DB6574 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBE2F1242631644C00DB6574 /* Preview Assets.xcassets */; }; + BBE2F25126319CDF00DB6574 /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE2F25026319CDF00DB6574 /* ExampleTests.swift */; }; + BBE2F2D92633000800DB6574 /* ExampleMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE2F2D82633000800DB6574 /* ExampleMacApp.swift */; }; + BBE2F2DB2633000800DB6574 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE2F2DA2633000800DB6574 /* ContentView.swift */; }; + BBE2F2DD2633000A00DB6574 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBE2F2DC2633000A00DB6574 /* Assets.xcassets */; }; + BBE2F2E02633000A00DB6574 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBE2F2DF2633000A00DB6574 /* Preview Assets.xcassets */; }; + F5FD8DECEB01164B694EB7A5 /* Pods_ExampleMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C467D24C04E908AF99EEE35 /* Pods_ExampleMac.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + BBE2F25326319CDF00DB6574 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BBE2F1122631644A00DB6574 /* Project object */; + proxyType = 1; + remoteGlobalIDString = BBE2F1192631644A00DB6574; + remoteInfo = Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 14CB7F4DE9A40E2AE6208BBA /* Pods_Example_ExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example_ExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2C467D24C04E908AF99EEE35 /* Pods_ExampleMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3FDE48D44DDD8C10D3C2B964 /* Pods-ExampleMac.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleMac.release.xcconfig"; path = "Target Support Files/Pods-ExampleMac/Pods-ExampleMac.release.xcconfig"; sourceTree = ""; }; + 44B4C17F3E986107DE2B092A /* Pods-Example-ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-ExampleTests.release.xcconfig"; path = "Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests.release.xcconfig"; sourceTree = ""; }; + 8FBF29FDFEFBAC133B8185CD /* Pods-ExampleMac.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleMac.debug.xcconfig"; path = "Target Support Files/Pods-ExampleMac/Pods-ExampleMac.debug.xcconfig"; sourceTree = ""; }; + 931148B487383741A0690854 /* Pods-Example-ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-ExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests.debug.xcconfig"; sourceTree = ""; }; + A803A357B78589FF3DC50204 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; + BBE2F11A2631644A00DB6574 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BBE2F11D2631644A00DB6574 /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; }; + BBE2F11F2631644B00DB6574 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + BBE2F1212631644C00DB6574 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BBE2F1242631644C00DB6574 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + BBE2F1262631644C00DB6574 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BBE2F24E26319CDF00DB6574 /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + BBE2F25026319CDF00DB6574 /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; }; + BBE2F25226319CDF00DB6574 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BBE2F26B2631A4D900DB6574 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; + BBE2F2D62633000800DB6574 /* ExampleMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BBE2F2D82633000800DB6574 /* ExampleMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleMacApp.swift; sourceTree = ""; }; + BBE2F2DA2633000800DB6574 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + BBE2F2DC2633000A00DB6574 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BBE2F2DF2633000A00DB6574 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + BBE2F2E12633000A00DB6574 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BBE2F2E22633000A00DB6574 /* ExampleMac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ExampleMac.entitlements; sourceTree = ""; }; + F758B5F96DA1A1E6EC7757D4 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FB1E40CD59FCDAF770FB6249 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BBE2F1172631644A00DB6574 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 24ED63DA1EB5B7F749EC81BE /* Pods_Example.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BBE2F24B26319CDF00DB6574 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 459F00FA99B5877F70019273 /* Pods_Example_ExampleTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BBE2F2D32633000800DB6574 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F5FD8DECEB01164B694EB7A5 /* Pods_ExampleMac.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9845C6D5A8CC09964B3EBEC5 /* Pods */ = { + isa = PBXGroup; + children = ( + FB1E40CD59FCDAF770FB6249 /* Pods-Example.debug.xcconfig */, + A803A357B78589FF3DC50204 /* Pods-Example.release.xcconfig */, + 931148B487383741A0690854 /* Pods-Example-ExampleTests.debug.xcconfig */, + 44B4C17F3E986107DE2B092A /* Pods-Example-ExampleTests.release.xcconfig */, + 8FBF29FDFEFBAC133B8185CD /* Pods-ExampleMac.debug.xcconfig */, + 3FDE48D44DDD8C10D3C2B964 /* Pods-ExampleMac.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + B87B0CAB0FBD04860823F4D6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F758B5F96DA1A1E6EC7757D4 /* Pods_Example.framework */, + 14CB7F4DE9A40E2AE6208BBA /* Pods_Example_ExampleTests.framework */, + 2C467D24C04E908AF99EEE35 /* Pods_ExampleMac.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BBE2F1112631644A00DB6574 = { + isa = PBXGroup; + children = ( + BBE2F11C2631644A00DB6574 /* Example */, + BBE2F24F26319CDF00DB6574 /* ExampleTests */, + BBE2F2D72633000800DB6574 /* ExampleMac */, + BBE2F11B2631644A00DB6574 /* Products */, + 9845C6D5A8CC09964B3EBEC5 /* Pods */, + B87B0CAB0FBD04860823F4D6 /* Frameworks */, + ); + sourceTree = ""; + }; + BBE2F11B2631644A00DB6574 /* Products */ = { + isa = PBXGroup; + children = ( + BBE2F11A2631644A00DB6574 /* Example.app */, + BBE2F24E26319CDF00DB6574 /* ExampleTests.xctest */, + BBE2F2D62633000800DB6574 /* ExampleMac.app */, + ); + name = Products; + sourceTree = ""; + }; + BBE2F11C2631644A00DB6574 /* Example */ = { + isa = PBXGroup; + children = ( + BBE2F26B2631A4D900DB6574 /* Example.entitlements */, + BBE2F11D2631644A00DB6574 /* ExampleApp.swift */, + BBE2F11F2631644B00DB6574 /* ContentView.swift */, + BBE2F1212631644C00DB6574 /* Assets.xcassets */, + BBE2F1262631644C00DB6574 /* Info.plist */, + BBE2F1232631644C00DB6574 /* Preview Content */, + ); + path = Example; + sourceTree = ""; + }; + BBE2F1232631644C00DB6574 /* Preview Content */ = { + isa = PBXGroup; + children = ( + BBE2F1242631644C00DB6574 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + BBE2F24F26319CDF00DB6574 /* ExampleTests */ = { + isa = PBXGroup; + children = ( + BBE2F25026319CDF00DB6574 /* ExampleTests.swift */, + BBE2F25226319CDF00DB6574 /* Info.plist */, + ); + path = ExampleTests; + sourceTree = ""; + }; + BBE2F2D72633000800DB6574 /* ExampleMac */ = { + isa = PBXGroup; + children = ( + BBE2F2D82633000800DB6574 /* ExampleMacApp.swift */, + BBE2F2DA2633000800DB6574 /* ContentView.swift */, + BBE2F2DC2633000A00DB6574 /* Assets.xcassets */, + BBE2F2E12633000A00DB6574 /* Info.plist */, + BBE2F2E22633000A00DB6574 /* ExampleMac.entitlements */, + BBE2F2DE2633000A00DB6574 /* Preview Content */, + ); + path = ExampleMac; + sourceTree = ""; + }; + BBE2F2DE2633000A00DB6574 /* Preview Content */ = { + isa = PBXGroup; + children = ( + BBE2F2DF2633000A00DB6574 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + BBE2F1192631644A00DB6574 /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = BBE2F1292631644C00DB6574 /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + A5D6D4FF969FD2D0E6D9FA3D /* [CP] Check Pods Manifest.lock */, + BBE2F1162631644A00DB6574 /* Sources */, + BBE2F1172631644A00DB6574 /* Frameworks */, + BBE2F1182631644A00DB6574 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Example; + productName = Example; + productReference = BBE2F11A2631644A00DB6574 /* Example.app */; + productType = "com.apple.product-type.application"; + }; + BBE2F24D26319CDF00DB6574 /* ExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = BBE2F25726319CE000DB6574 /* Build configuration list for PBXNativeTarget "ExampleTests" */; + buildPhases = ( + CEBA21302A09E05DEFB9CDF5 /* [CP] Check Pods Manifest.lock */, + BBE2F24A26319CDF00DB6574 /* Sources */, + BBE2F24B26319CDF00DB6574 /* Frameworks */, + BBE2F24C26319CDF00DB6574 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BBE2F25426319CDF00DB6574 /* PBXTargetDependency */, + ); + name = ExampleTests; + productName = ExampleTests; + productReference = BBE2F24E26319CDF00DB6574 /* ExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + BBE2F2D52633000800DB6574 /* ExampleMac */ = { + isa = PBXNativeTarget; + buildConfigurationList = BBE2F2E52633000B00DB6574 /* Build configuration list for PBXNativeTarget "ExampleMac" */; + buildPhases = ( + 954E0D125EE4DA6BABD7255F /* [CP] Check Pods Manifest.lock */, + BBE2F2D22633000800DB6574 /* Sources */, + BBE2F2D32633000800DB6574 /* Frameworks */, + BBE2F2D42633000800DB6574 /* Resources */, + DEE7530F96A5EFFB47DF9940 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ExampleMac; + productName = ExampleMac; + productReference = BBE2F2D62633000800DB6574 /* ExampleMac.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BBE2F1122631644A00DB6574 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1240; + LastUpgradeCheck = 1240; + TargetAttributes = { + BBE2F1192631644A00DB6574 = { + CreatedOnToolsVersion = 12.4; + }; + BBE2F24D26319CDF00DB6574 = { + CreatedOnToolsVersion = 12.4; + TestTargetID = BBE2F1192631644A00DB6574; + }; + BBE2F2D52633000800DB6574 = { + CreatedOnToolsVersion = 12.4; + }; + }; + }; + buildConfigurationList = BBE2F1152631644A00DB6574 /* Build configuration list for PBXProject "Example" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BBE2F1112631644A00DB6574; + productRefGroup = BBE2F11B2631644A00DB6574 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BBE2F1192631644A00DB6574 /* Example */, + BBE2F24D26319CDF00DB6574 /* ExampleTests */, + BBE2F2D52633000800DB6574 /* ExampleMac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BBE2F1182631644A00DB6574 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBE2F1252631644C00DB6574 /* Preview Assets.xcassets in Resources */, + BBE2F1222631644C00DB6574 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BBE2F24C26319CDF00DB6574 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BBE2F2D42633000800DB6574 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBE2F2E02633000A00DB6574 /* Preview Assets.xcassets in Resources */, + BBE2F2DD2633000A00DB6574 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 954E0D125EE4DA6BABD7255F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ExampleMac-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + A5D6D4FF969FD2D0E6D9FA3D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CEBA21302A09E05DEFB9CDF5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-ExampleTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DEE7530F96A5EFFB47DF9940 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ExampleMac/Pods-ExampleMac-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ExampleMac/Pods-ExampleMac-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExampleMac/Pods-ExampleMac-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BBE2F1162631644A00DB6574 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBE2F1202631644B00DB6574 /* ContentView.swift in Sources */, + BBE2F11E2631644B00DB6574 /* ExampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BBE2F24A26319CDF00DB6574 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBE2F25126319CDF00DB6574 /* ExampleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BBE2F2D22633000800DB6574 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBE2F2DB2633000800DB6574 /* ContentView.swift in Sources */, + BBE2F2D92633000800DB6574 /* ExampleMacApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + BBE2F25426319CDF00DB6574 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BBE2F1192631644A00DB6574 /* Example */; + targetProxy = BBE2F25326319CDF00DB6574 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + BBE2F1272631644C00DB6574 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + BBE2F1282631644C00DB6574 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + BBE2F12A2631644C00DB6574 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FB1E40CD59FCDAF770FB6249 /* Pods-Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Example/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.trustwallet.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + BBE2F12B2631644C00DB6574 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A803A357B78589FF3DC50204 /* Pods-Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Example/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.trustwallet.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + BBE2F25526319CE000DB6574 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 931148B487383741A0690854 /* Pods-Example-ExampleTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.trustwallet.ExampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; + }; + name = Debug; + }; + BBE2F25626319CE000DB6574 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 44B4C17F3E986107DE2B092A /* Pods-Example-ExampleTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.trustwallet.ExampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; + }; + name = Release; + }; + BBE2F2E32633000B00DB6574 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8FBF29FDFEFBAC133B8185CD /* Pods-ExampleMac.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = ExampleMac/ExampleMac.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"ExampleMac/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = ExampleMac/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.1; + PRODUCT_BUNDLE_IDENTIFIER = com.trustwallet.ExampleMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + BBE2F2E42633000B00DB6574 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3FDE48D44DDD8C10D3C2B964 /* Pods-ExampleMac.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = ExampleMac/ExampleMac.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"ExampleMac/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = ExampleMac/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.1; + PRODUCT_BUNDLE_IDENTIFIER = com.trustwallet.ExampleMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BBE2F1152631644A00DB6574 /* Build configuration list for PBXProject "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBE2F1272631644C00DB6574 /* Debug */, + BBE2F1282631644C00DB6574 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BBE2F1292631644C00DB6574 /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBE2F12A2631644C00DB6574 /* Debug */, + BBE2F12B2631644C00DB6574 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BBE2F25726319CE000DB6574 /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBE2F25526319CE000DB6574 /* Debug */, + BBE2F25626319CE000DB6574 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BBE2F2E52633000B00DB6574 /* Build configuration list for PBXNativeTarget "ExampleMac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBE2F2E32633000B00DB6574 /* Debug */, + BBE2F2E42633000B00DB6574 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BBE2F1122631644A00DB6574 /* Project object */; +} diff --git a/swift/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..919434a6254 --- /dev/null +++ b/swift/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/swift/Example/Example.xcworkspace/contents.xcworkspacedata b/swift/Example/Example.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..a37cf193d1f --- /dev/null +++ b/swift/Example/Example.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/swift/Example/Example/ContentView.swift b/swift/Example/Example/ContentView.swift index 71bde3109c0..b5da0ca723d 100644 --- a/swift/Example/Example/ContentView.swift +++ b/swift/Example/Example/ContentView.swift @@ -1,15 +1,13 @@ -// 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 struct ContentView: View { - let wallet = HDWallet(strength: 32, passphrase: "") + let wallet = HDWallet(strength: 256, passphrase: "")! var body: some View { Text("Ethereum address: \(wallet.getAddressForCoin(coin: .ethereum))") .padding() diff --git a/swift/Example/Example/Example.entitlements b/swift/Example/Example/Example.entitlements new file mode 100644 index 00000000000..ee95ab7e582 --- /dev/null +++ b/swift/Example/Example/Example.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + 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/Assets.xcassets/AccentColor.colorset/Contents.json b/swift/Example/ExampleMac/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/swift/Example/ExampleMac/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/swift/Example/ExampleMac/Assets.xcassets/AppIcon.appiconset/Contents.json b/swift/Example/ExampleMac/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..3f00db43ec3 --- /dev/null +++ b/swift/Example/ExampleMac/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/swift/Example/ExampleMac/Assets.xcassets/Contents.json b/swift/Example/ExampleMac/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/swift/Example/ExampleMac/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/swift/Example/ExampleMac/ContentView.swift b/swift/Example/ExampleMac/ContentView.swift new file mode 100644 index 00000000000..80981c59ac9 --- /dev/null +++ b/swift/Example/ExampleMac/ContentView.swift @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import SwiftUI +import WalletCore + +struct ContentView: View { + let wallet = HDWallet(strength: 256, passphrase: "")! + var body: some View { + Text("Ethereum address: \(wallet.getAddressForCoin(coin: .ethereum))") + .padding() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/swift/Example/ExampleMac/ExampleMac.entitlements b/swift/Example/ExampleMac/ExampleMac.entitlements new file mode 100644 index 00000000000..f2ef3ae0265 --- /dev/null +++ b/swift/Example/ExampleMac/ExampleMac.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/swift/Example/ExampleMac/ExampleMacApp.swift b/swift/Example/ExampleMac/ExampleMacApp.swift new file mode 100644 index 00000000000..040a390bd88 --- /dev/null +++ b/swift/Example/ExampleMac/ExampleMacApp.swift @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import SwiftUI + +@main +struct ExampleMacApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/swift/Example/ExampleMac/Info.plist b/swift/Example/ExampleMac/Info.plist new file mode 100644 index 00000000000..69c84ae74d7 --- /dev/null +++ b/swift/Example/ExampleMac/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + + diff --git a/swift/Example/ExampleMac/Preview Content/Preview Assets.xcassets/Contents.json b/swift/Example/ExampleMac/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/swift/Example/ExampleMac/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/swift/Example/ExampleTests/ExampleTests.swift b/swift/Example/ExampleTests/ExampleTests.swift index 66b07652307..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 @@ -11,7 +9,7 @@ import WalletCore class ExampleTests: XCTestCase { func testEthereum() { - let wallet = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "TREZOR") + let wallet = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "TREZOR")! let address = wallet.getAddressForCoin(coin: .ethereum) XCTAssertEqual(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979") diff --git a/swift/Example/Podfile b/swift/Example/Podfile index 35a9f9761c4..07a4770626f 100644 --- a/swift/Example/Podfile +++ b/swift/Example/Podfile @@ -1,14 +1,20 @@ -platform :ios, '13.0' install! 'cocoapods', :deterministic_uuids => false, :share_schemes_for_development_pods => true def dev_pod - pod 'WalletCoreDev', :path => '../../' + pod 'WalletCore', :path => '../..' end target 'Example' do + platform :ios, '13.0' use_frameworks! :linkage => :static dev_pod target 'ExampleTests' do inherit! :complete end end + +target 'ExampleMac' do + platform :osx, '10.12' + use_frameworks! :linkage => :dynamic + dev_pod +end diff --git a/swift/Example/Podfile.lock b/swift/Example/Podfile.lock index 284bc4f41a5..c212c3d5960 100644 --- a/swift/Example/Podfile.lock +++ b/swift/Example/Podfile.lock @@ -1,27 +1,27 @@ PODS: - - SwiftProtobuf (1.15.0) - - WalletCoreDev (2.5.6): - - WalletCoreDev/Core (= 2.5.6) - - WalletCoreDev/Core (2.5.6): - - WalletCoreDev/Types - - WalletCoreDev/Types (2.5.6): + - SwiftProtobuf (1.16.0) + - WalletCore (2.6.5): + - WalletCore/Core (= 2.6.5) + - WalletCore/Core (2.6.5): + - WalletCore/Types + - WalletCore/Types (2.6.5): - SwiftProtobuf DEPENDENCIES: - - WalletCoreDev (from `../../`) + - WalletCore (from `../..`) SPEC REPOS: trunk: - SwiftProtobuf EXTERNAL SOURCES: - WalletCoreDev: - :path: "../../" + WalletCore: + :path: "../.." SPEC CHECKSUMS: - SwiftProtobuf: 3320217e9d8fb75f36b40282e78c482640fd75dd - WalletCoreDev: 7a2245118494fc8eb77ed274a8254f47da443100 + SwiftProtobuf: 4e16842b83c6fda06b10fac50d73b3f1fce8ab7b + WalletCore: 87c9c14940c54f0e3ba872a32fc7db343428331e -PODFILE CHECKSUM: 326472bd28152c03cd531998ec90dd42228ac356 +PODFILE CHECKSUM: 78b5311dee3e5c57cb6682134b4bbf2bac46290f COCOAPODS: 1.10.1 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..ef8d1a6c62b 100644 --- a/swift/Playground.playground/Contents.swift +++ b/swift/Playground.playground/Contents.swift @@ -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. import UIKit import TrustWalletCore -import SwiftProtobuf +import WalletCoreSwiftProtobuf enum PlaygroundError: Error { case invalidHexString diff --git a/swift/Podfile b/swift/Podfile index 6268fce4ec4..e1f840eb626 100644 --- a/swift/Podfile +++ b/swift/Podfile @@ -4,11 +4,12 @@ deployment_target = '12.0' platform :ios, deployment_target inhibit_all_warnings! +project 'TrustWalletCore.xcodeproj' + target 'WalletCore' do use_frameworks! - pod 'SwiftProtobuf' - pod 'SwiftLint' + pod 'WalletCoreSwiftProtobuf' target 'WalletCoreTests' end diff --git a/swift/Podfile.lock b/swift/Podfile.lock index 0e85ef64336..4e481723841 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -1,20 +1,16 @@ PODS: - - SwiftLint (0.41.0) - - SwiftProtobuf (1.13.0) + - WalletCoreSwiftProtobuf (1.29.0) DEPENDENCIES: - - SwiftLint - - SwiftProtobuf + - WalletCoreSwiftProtobuf SPEC REPOS: trunk: - - SwiftLint - - SwiftProtobuf + - WalletCoreSwiftProtobuf SPEC CHECKSUMS: - SwiftLint: c585ebd615d9520d7fbdbe151f527977b0534f1e - SwiftProtobuf: fd4693388a96c8c2df35d3b063272b0e7c499d00 + WalletCoreSwiftProtobuf: a4798576a2d309511fc45f81843d348732ec571d -PODFILE CHECKSUM: 159625a957d599dfe52b30d250815212818070a7 +PODFILE CHECKSUM: bbbccdbb7b3665e060820f476dbf21d5b1f8e605 -COCOAPODS: 1.10.1 +COCOAPODS: 1.16.2 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..d308aa06b34 100644 --- a/swift/Sources/AnySigner.swift +++ b/swift/Sources/AnySigner.swift @@ -5,21 +5,35 @@ // file LICENSE at the root of the source code distribution tree. import Foundation -import SwiftProtobuf +import WalletCoreSwiftProtobuf 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) - return try SigningOutput(serializedData: outputData) + return try SigningOutput(serializedBytes: outputData) } catch let error { fatalError(error.localizedDescription) } } + /// 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,15 +63,27 @@ 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) - return try TransactionPlan(serializedData: outputData) + return try TransactionPlan(serializedBytes: outputData) } catch let error { fatalError(error.localizedDescription) } } + /// 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 c9f694bb6b4..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 determinisic 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 3cb855c4329..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 foce inclusion of the C++ STL when compiling. +// 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 new file mode 100644 index 00000000000..75449402873 --- /dev/null +++ b/swift/Sources/Extensions/Account+Codable.swift @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import Foundation + +extension Account: Equatable { + public static func == (lhs: Account, rhs: Account) -> Bool { + return lhs.coin == rhs.coin && + lhs.address == rhs.address && + lhs.derivation == rhs.derivation && + lhs.derivationPath == rhs.derivationPath && + lhs.publicKey == rhs.publicKey && + lhs.extendedPublicKey == rhs.extendedPublicKey + } +} + +extension Account: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(coin) + hasher.combine(address) + hasher.combine(derivation) + hasher.combine(derivationPath) + hasher.combine(publicKey) + hasher.combine(extendedPublicKey) + } +} + +extension Account: Codable { + private enum CodingKeys: String, CodingKey { + case coin + case address + case derivation + case derivationPath + case publicKey + case extendedPublicKey + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(coin.rawValue, forKey: .coin) + try container.encode(address, forKey: .address) + try container.encode(derivation.rawValue, forKey: .derivation) + try container.encode(derivationPath, forKey: .derivationPath) + try container.encode(publicKey, forKey: .publicKey) + try container.encode(extendedPublicKey, forKey: .extendedPublicKey) + } + + public convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let rawCoin = try container.decode(UInt32.self, forKey: .coin) + let address = try container.decode(String.self, forKey: .address) + let rawDerivation = try container.decode(UInt32.self, forKey: .derivation) + let derivationPath = try container.decode(String.self, forKey: .derivationPath) + let publicKey = try container.decode(String.self, forKey: .publicKey) + let extendedPublicKey = try container.decode(String.self, forKey: .extendedPublicKey) + + self.init( + address: address, + coin: CoinType(rawValue: rawCoin)!, + derivation: Derivation(rawValue: rawDerivation)!, + derivationPath: derivationPath, + publicKey: publicKey, + extendedPublicKey: extendedPublicKey + ) + } +} 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 3c4642eaa76..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 @@ -21,6 +19,11 @@ extension Data { return nil } + // Check odd characters + if string.contains(where: { !$0.isHexDigit }) { + return nil + } + // Convert the string to bytes for better performance guard let stringData = string.data(using: .ascii, allowLossyConversion: true) else { return nil @@ -46,6 +49,12 @@ extension Data { return UInt8(letter, radix: 16) } + /// Reverses and parses hex string as `Data` + public static func reverse(hexString: String) -> Data { + guard let data = Data(hexString: hexString) else { return Data() } + return Data(data.reversed()) + } + /// Returns the hex string representation of the data. public var hexString: String { return map({ String(format: "%02x", $0) }).joined() 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.Error.swift b/swift/Sources/KeyStore.Error.swift old mode 100755 new mode 100644 index 8f432309be9..722bb1db78b --- a/swift/Sources/KeyStore.Error.swift +++ b/swift/Sources/KeyStore.Error.swift @@ -10,6 +10,7 @@ extension KeyStore { public enum Error: Swift.Error, LocalizedError { case accountNotFound case invalidMnemonic + case invalidJSON case invalidKey case invalidPassword @@ -19,6 +20,8 @@ extension KeyStore { return NSLocalizedString("Account not found", comment: "Error message when trying to access an account that does not exist") case .invalidMnemonic: return NSLocalizedString("Invalid mnemonic phrase", comment: "Error message when trying to import an invalid mnemonic phrase") + case .invalidJSON: + return NSLocalizedString("Invalid Keystore/JSON format", comment: "Error message when trying to import an invalid json phrase") case .invalidKey: return NSLocalizedString("Invalid private key", comment: "Error message when trying to import an invalid private key") case .invalidPassword: diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift old mode 100755 new mode 100644 index 703f0e4155c..dc4f55afd3c --- 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) } @@ -128,7 +128,7 @@ public final class KeyStore { /// - Returns: new account public func `import`(json: Data, name: String, password: String, newPassword: String, coins: [CoinType]) throws -> Wallet { guard let key = StoredKey.importJSON(json: json) else { - throw Error.invalidKey + throw Error.invalidJSON } guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword @@ -141,7 +141,14 @@ public final class KeyStore { guard let privateKey = PrivateKey(data: data) else { throw Error.invalidKey } - return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + if key.hasPrivateKeyEncoded { + guard let encodedPrivateKey = key.decryptPrivateKeyEncoded(password: Data(password.utf8)) else { + throw Error.invalidPassword + } + return try self.import(encodedPrivateKey: encodedPrivateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + } else { + return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + } } private func checkMnemonic(_ data: Data) -> String? { @@ -150,6 +157,13 @@ public final class KeyStore { } return mnemonic } + + private func checkEncoded(wallet: Wallet, password: String) -> String? { + guard wallet.key.hasPrivateKeyEncoded else { + return nil + } + return wallet.key.decryptPrivateKeyEncoded(password: Data(password.utf8)) + } /// Imports a private key. /// @@ -158,8 +172,29 @@ 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() + let wallet = Wallet(keyURL: url, key: newKey) + _ = try wallet.getAccount(password: password, coin: coin) + wallets.append(wallet) + + try save(wallet: wallet) + + return wallet + } + + /// Imports an encoded private key. + /// + /// - Parameters: + /// - privateKey: private key to import + /// - password: password to use for the imported private key + /// - coin: coin to use for this wallet + /// - Returns: new wallet + public func `import`(encodedPrivateKey: String, name: String, password: String, coin: CoinType, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let newKey = StoredKey.importPrivateKeyEncodedWithEncryption(privateKey: encodedPrivateKey, name: name, password: Data(password.utf8), coin: coin, encryption: encryption) else { throw Error.invalidKey } let url = makeAccountURL() @@ -179,8 +214,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 +236,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 +246,17 @@ 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 privateKey = checkEncoded(wallet: wallet, password: password), let newKey = StoredKey.importPrivateKeyEncodedWithEncryption(privateKey: privateKey, 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 } @@ -238,6 +278,19 @@ public final class KeyStore { } return key } + + /// Exports a wallet as encoded private key data. + /// + /// - Parameters: + /// - wallet: wallet to export + /// - password: account password + /// - Returns: encoded private key data + public func exportPrivateKeyEncoded(wallet: Wallet, password: String) throws -> String { + guard let key = wallet.key.decryptPrivateKeyEncoded(password: Data(password.utf8)) else { + throw Error.invalidPassword + } + return key + } /// Exports a wallet as a mnemonic phrase. /// @@ -269,11 +322,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 +344,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 new file mode 100644 index 00000000000..88670f92ab4 --- /dev/null +++ b/swift/Sources/TWCardano.swift @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import Foundation + +public func CardanoMinAdaAmount(tokenBundle: Data) -> UInt64 { + let tokenBundleData = TWDataCreateWithNSData(tokenBundle) + defer { + TWDataDelete(tokenBundleData) + } + return TWCardanoMinAdaAmount(tokenBundleData) +} 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/Sources/Types/UniversalAssetID.swift b/swift/Sources/Types/UniversalAssetID.swift index acd4e1e0082..9cd4f59945d 100644 --- a/swift/Sources/Types/UniversalAssetID.swift +++ b/swift/Sources/Types/UniversalAssetID.swift @@ -23,7 +23,7 @@ public struct UniversalAssetID: CustomStringConvertible, Equatable, Hashable { return [prefix, suffix].joined(separator: "_") } - public init(coin: CoinType, token: String) { + public init(coin: CoinType, token: String = "") { self.coin = coin self.token = token } diff --git a/swift/Sources/Wallet.swift b/swift/Sources/Wallet.swift old mode 100755 new mode 100644 index 036d2a22d7e..04749b5d564 --- a/swift/Sources/Wallet.swift +++ b/swift/Sources/Wallet.swift @@ -43,6 +43,22 @@ public final class Wallet: Hashable, Equatable { return account } + /// Returns the account for a specific coin and derivation. + /// + /// - Parameters: + /// - password: wallet encryption password + /// - coin: coin type + /// - derivation: derivation, a specific or default + /// - Returns: the account + /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. + public func getAccount(password: String, coin: CoinType, derivation: Derivation) throws -> Account { + let wallet = key.wallet(password: Data(password.utf8)) + guard let account = key.accountForCoinDerivation(coin: coin, derivation: derivation, wallet: wallet) else { + throw KeyStore.Error.invalidPassword + } + return account + } + /// Returns the accounts for a specific coins. /// /// - Parameters: 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 dd8fd80e2e7..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 @@ -228,4 +226,28 @@ class BitcoinAddressTests: XCTestCase { XCTAssertFalse(CoinType.monacoin.validate(address: addressString3), "'\(addressString3)' should be an invalid Monacoin Bech32 address") } + + func testBitcoinDeriveAddress() { + let privateKey = PrivateKey(data: Data(hexString: "4646464646464646464646464646464646464646464646464646464646464646")!)! + let address = CoinType.bitcoin.deriveAddress(privateKey: privateKey) + XCTAssertEqual("bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv", address.description) + } + + func testDeriveOneThread() { + let n = 200 + for _ in 1...n { + testBitcoinDeriveAddress() + } + } + + func testMultiThreadedDerive() { + let nThread = 5 + let queue = OperationQueue() + for _ in 1...nThread { + queue.addOperation { + self.testDeriveOneThread() + } + } + queue.waitUntilAllOperationsAreFinished() + } } 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..cc155d2c458 --- /dev/null +++ b/swift/Tests/AnyAddressTests.swift @@ -0,0 +1,70 @@ +// 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") + + let address3 = AnyAddress(publicKey: pubkey, coin: coin, derivation: .bitcoinTaproot) + XCTAssertEqual(address3.description, "bc1pnncpg8s7gu7t6xmmzxqarcj8ydthmaz8gr4m76eephjfprs53maswgel0w") + } + + 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..ce362d845b8 --- /dev/null +++ b/swift/Tests/BarzTests.swift @@ -0,0 +1,215 @@ +// 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.scwExecute = EthereumTransaction.SCWalletExecute.with { + $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.scwExecute = EthereumTransaction.SCWalletExecute.with { + $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.scwBatch = EthereumTransaction.SCWalletBatch.with { + $0.calls = [ + EthereumTransaction.SCWalletBatch.BatchedCall.with { + $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" + $0.amount = Data(hexString: "00")! + $0.payload = approveCall + }, + EthereumTransaction.SCWalletBatch.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\"}") + } + + func testAuthorizationHash() { + let chainId = Data(hexString: "0x01")! + let contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + let nonce = Data(hexString: "0x01")! + + let authorizationHash = Barz.getAuthorizationHash(chainId: chainId, contractAddress: contractAddress, nonce: nonce)! + XCTAssertEqual(authorizationHash.hexString, "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem + } + + 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/Bech32Tests.swift b/swift/Tests/Bech32Tests.swift new file mode 100644 index 00000000000..f09bd9507fa --- /dev/null +++ b/swift/Tests/Bech32Tests.swift @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class Bech32Tests: XCTestCase { + func testEncode() { + let data = Data(hexString: "00443214c74254b635cf84653a56d7c675be77df")! + let encoded = Bech32.encode(hrp: "abcdef", data: data) + XCTAssertEqual(encoded, "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw") + } + + func testDecode() { + let decoded = Bech32.decode(string: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")! + XCTAssertEqual(decoded.hexString, "00443214c74254b635cf84653a56d7c675be77df") + } + + func testDecodeWrongChecksumVariant() { + // This is a Bech32m variant, not Bech32 variant. So it should fail using Bech32 decoder. + let decoded = Bech32.decode(string: "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx") + XCTAssertNil(decoded) + } + + func testEncodeM() { + let data = Data(hexString: "ffbbcdeb38bdab49ca307b9ac5a928398a418820")! + let encoded = Bech32.encodeM(hrp: "abcdef", data: data) + XCTAssertEqual(encoded, "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx") + } + + func testDecodeM() { + let decoded = Bech32.decodeM(string: "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")! + XCTAssertEqual(decoded.hexString, "ffbbcdeb38bdab49ca307b9ac5a928398a418820") + } + + func testDecodeMWrongChecksumVariant() { + // This is a Bech32 variant, not Bech32m variant. So it should fail using Bech32M decoder. + let decoded = Bech32.decodeM(string: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw") + XCTAssertNil(decoded) + } +} 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 5eb639089f2..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,24 +16,86 @@ 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 transaction = AlgorandTransactionPay.with { + let round: UInt64 = 1937767 + let transaction = AlgorandTransfer.with { $0.toAddress = "CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4" - $0.fee = 263000 $0.amount = 1000000000000 - $0.firstRound = 1937767 - $0.lastRound = 1938767 } let input = AlgorandSigningInput.with { $0.genesisID = "mainnet-v1.0" $0.genesisHash = Data(base64Encoded: "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")! $0.note = "hello".data(using: .utf8)! $0.privateKey = Data(hexString: "d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b")! - $0.transactionPay = transaction + $0.firstRound = round + $0.lastRound = round + 1000 + $0.fee = 263000 + $0.transfer = transaction } let output: AlgorandSigningOutput = AnySigner.sign(input: input, coin: .algorand) XCTAssertEqual(output.encoded.hexString, "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179") } + + func testSignVoteTx() { + // manual vote tx is 0 amount + note + + let round: UInt64 = 18426344 + let transaction = AlgorandTransfer.with { + $0.toAddress = "57QZ4S7YHTWPRAM3DQ2MLNSVLAQB7DTK4D7SUNRIEFMRGOU7DMYFGF55BY" + $0.amount = 0 + } + let note = """ + af/gov1:j{"com":1000000} + """ + let input = AlgorandSigningInput.with { + $0.genesisID = "mainnet-v1.0" + $0.genesisHash = Data(base64Encoded: "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")! + $0.note = note.data(using: .utf8)! + $0.privateKey = Data(hexString: "d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b")! + $0.firstRound = round + $0.lastRound = round + 1000 + $0.fee = 1000 + $0.transfer = transaction + } + let output: AlgorandSigningOutput = AnySigner.sign(input: input, coin: .algorand) + + // real key is 1p, posted by: echo '' | xxd -r -p | curl -X POST --data-binary @- + // https://algoexplorer.io/tx/OHYNQA7X5LHUKWEM6ZMUT6RCVOZUELXSYELV7CHQFQBDI3XEM4NQ + XCTAssertEqual(output.encoded.hexString, "82a3736967c440aad1e2d80fbdfc4dc5def13e1dc9f39c9261df9d5c6664478b951d28c3a688c4a261894d8c9bd686f5b2355f2edd54fd611eeaba8a871cc05af728a18598ed04a374786e89a3666565cd03e8a26676ce011929e8a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce01192dd0a46e6f7465c41861662f676f76313a6a7b22636f6d223a313030303030307da3726376c420efe19e4bf83cecf8819b1c34c5b65558201f8e6ae0ff2a36282159133a9f1b30a3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179") + } + + func testSignJSON() { + let json = """ + {"genesisId":"mainnet-v1.0","genesisHash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=","note":"aGVsbG8=","firstRound":"1937767","lastRound":"1938767","fee":"263000","transfer":{"toAddress":"CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4","amount":"1000000000000"}} + """ + let key = Data(hexString: "d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b")! + let result = AnySigner.signJSON(json, key: key, coin: .algorand) + + XCTAssertEqual(result, "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179") + } } diff --git a/swift/Tests/Blockchains/AptosTests.swift b/swift/Tests/Blockchains/AptosTests.swift new file mode 100644 index 00000000000..7a7cbf4c8f8 --- /dev/null +++ b/swift/Tests/Blockchains/AptosTests.swift @@ -0,0 +1,152 @@ +// 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 testBlindSignWithABI() { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet + let payloadJson = """ + { + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + } + """ + let privateKeyData = Data(hexString: "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")! + let input = AptosSigningInput.with { + $0.chainID = 1 + $0.anyEncoded = payloadJson + $0.expirationTimestampSecs = 1735902711 + $0.gasUnitPrice = 100 + $0.maxGasAmount = 50000 + $0.sequenceNumber = 69 + $0.sender = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.privateKey = privateKeyData + $0.abi = """ + [ + "vector", + "u64", + "bool" + ] + """ + } + let output: AptosSigningOutput = AnySigner.sign(input: input, coin: .aptos) + let expectedRawTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001" + let expectedSignature = "13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304" + let expectedSignedTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304" + 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) + } + + func testSignFungibleAssetTransfer() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet + let privateKeyData = Data(hexString: "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")! + let fungibleAssetTransferMsg = AptosFungibleAssetTransferMessage.with { + $0.metadataAddress = "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12" + $0.to = "0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52" + $0.amount = 100000000 + } + let input = AptosSigningInput.with { + $0.chainID = 1 + $0.sender = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.expirationTimestampSecs = 1736060099 + $0.gasUnitPrice = 100 + $0.maxGasAmount = 20 + $0.sequenceNumber = 74 + $0.fungibleAssetTransfer = fungibleAssetTransferMsg + $0.privateKey = privateKeyData + } + let output: AptosSigningOutput = AnySigner.sign(input: input, coin: .aptos) + let expectedRawTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001" + let expectedSignature = "2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c" + let expectedSignedTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c" + 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 new file mode 100644 index 00000000000..cc0da61aaae --- /dev/null +++ b/swift/Tests/Blockchains/AvalancheTests.swift @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class AvalancheTests: XCTestCase { + + func testCChainAddress() { + let key = PrivateKey(data: Data(hexString: "98cb077f972feb0481f1d894f272c6a1e3c15e272a1658ff716444f465200070")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .avalancheCChain) + let addressETH = AnyAddress(publicKey: pubkey, coin: .ethereum) + + 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 78e62e3f314..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 @@ -27,7 +25,7 @@ class BandChainTests: XCTestCase { $0.fromAddress = fromAddress $0.toAddress = "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh" $0.amounts = [CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "uband" }] } @@ -39,7 +37,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } @@ -107,7 +105,7 @@ class BandChainTests: XCTestCase { $0.delegatorAddress = "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz" $0.validatorAddress = "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" $0.amount = CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "uband" } } @@ -119,7 +117,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } @@ -191,7 +189,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] $0.gas = 200000 @@ -253,7 +251,7 @@ class BandChainTests: XCTestCase { $0.delegatorAddress = "band13tug898kgtwprg7fevzzqgh45draa3cyffw3kp" $0.validatorAddress = "bandvaloper1jp633fleakzv4uxxvl707j9u2jj6j5x2rg7glv" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "uband" } } @@ -265,7 +263,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } @@ -331,7 +329,7 @@ class BandChainTests: XCTestCase { $0.validatorSrcAddress = "bandvaloper1hln9scsl9yqup8nxyum06rmggql5m5zqwxmt3p" $0.validatorDstAddress = "bandvaloper1hydxm5h8v6tty2x623az65x3r39tl3paxyxtr0" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "uband" } } @@ -343,7 +341,7 @@ class BandChainTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "uband" }] } diff --git a/swift/Tests/Blockchains/BinanceChainTests.swift b/swift/Tests/Blockchains/BinanceChainTests.swift index bb904ef5b2e..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 @@ -19,7 +17,7 @@ class BinanceChainTests: XCTestCase { } func testBinanceMainnet() { - 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 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 key = wallet.getKeyForCoin(coin: .binance) let address = CoinType.binance.deriveAddress(privateKey: key) @@ -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) @@ -210,4 +218,53 @@ class BinanceChainTests: XCTestCase { XCTAssertEqual(result, "ca01f0625dee0a4a2a2c87fa0a210a1412e654edef9e508b833736a987d069da5a89aedb12090a03424e4210cb8d5212210a1433bbf307b98146f13d20693cf946c2d77a4caf2812090a03424e4210cb8d52126d0a26eb5ae9872102e58176f271a9796b4288908be85094a2ac978e25535fd59a37b58626e3a84d9e1240015b4beb3d6ef366a7a92fd283f66b8f0d8cdb6b152a9189146b27f84f507f047e248517cf691a36ebc2b7f3b7f64e27585ce1c40f1928d119c31af428efcf3e1882671a0754657374696e672002") } + + func testSignOrderOneThread() { + let n = 50 + for _ in 1...n { + testSignSendOrder() + } + } + + func testMultiThreadedSign() { + let nThread = 5 + let queue = OperationQueue() + for _ in 1...nThread { + queue.addOperation { + self.testSignOrderOneThread() + } + } + 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 4e367c9f070..3ae42d7d925 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,223 @@ 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 = UtxoOutPoint.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.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v2 + $0.inputs = [utxo0] + $0.outputs = [out0, changeOut] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + } + $0.privateKeys = [privateKeyData] + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 0 + $0.p2ShPrefix = 5 + } + } + + 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 = UtxoOutPoint.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.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v2 + $0.inputs = [utxo0] + $0.outputs = [out0] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + } + $0.privateKeys = [privateKeyData] + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 0 + $0.p2ShPrefix = 5 + } + $0.dangerousUseFixedSchnorrRng = true + } + + 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 = UtxoOutPoint.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 = UtxoOutPoint.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.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v2 + $0.inputs = [utxo0, utxo1] + $0.outputs = [out0, changeOut] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + } + $0.privateKeys = [privateKeyData] + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 0 + $0.p2ShPrefix = 5 + } + } + + 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 +235,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")! @@ -88,7 +306,7 @@ class BitcoinTransactionSignerTests: XCTestCase { let pubkey = key.getPublicKeySecp256k1(compressed: true) let utxos = [ BitcoinUnspentTransaction.with { - $0.outPoint.hash = Data(Data(hexString: "8b5f4861c6d4a4ea361aa4066d720067f73854d9a1b1d01e2b0e3c9e150bc5a3")!.reversed()) + $0.outPoint.hash = Data.reverse(hexString: "8b5f4861c6d4a4ea361aa4066d720067f73854d9a1b1d01e2b0e3c9e150bc5a3") $0.outPoint.index = 0 $0.outPoint.sequence = UINT32_MAX $0.script = lockScript.data @@ -106,6 +324,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 @@ -127,4 +346,130 @@ class BitcoinTransactionSignerTests: XCTestCase { XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoinCash), 0x41) XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoinGold), 0x4f41) } + + func testSignExtendedPubkeyUTXO() { + // compressed WIF, real key is 5KCr + let wif = "L4BeKzm3AHDUMkxLRVKTSVxkp6Hz9FcMQPh18YCKU1uioXfovzwP" + let decoded = Base58.decode(string: wif)! + let key = PrivateKey(data: decoded[1 ..< 33])! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + + // shortcut methods only support compressed public key + let address = BitcoinAddress(data: [0x0] + Hash.sha256RIPEMD(data: pubkey.data))! + let script = BitcoinScript.lockScriptForAddress(address: address.description, coin: .bitcoin) + + // utxos from: https://blockchair.com/bitcoin/address/1KRhiKNai3ke3hZgSPZ5TpJoSJvs1aZfWo + let utxos: [BitcoinUnspentTransaction] = [ + .with { + $0.outPoint.hash = Data.reverse(hexString: "6ae3f1d245521b0ea7627231d27d613d58c237d6bf97a1471341a3532e31906c") + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.amount = 16874 + $0.script = script.data + }, + .with { + $0.outPoint.hash = Data.reverse(hexString: "fd1ea8178228e825d4106df0acb61a4fb14a8f04f30cd7c1f39c665c9427bf13") + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.amount = 10098 + $0.script = script.data + } + ] + + let input = BitcoinSigningInput.with { + $0.utxo = utxos + $0.privateKey = [key.data] + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) + $0.useMaxAmount = true + $0.byteFee = 10 + $0.toAddress = "1FeyttPotRsSd4equWr678dbEvXaNSqmDT" + $0.coinType = CoinType.bitcoin.rawValue + $0.amount = utxos.map { $0.amount } .reduce(0, +) + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) + + // https://blockchair.com/bitcoin/transaction/1d73706d33ec249beae6804c2e636ab9d7adbc2e9548757f6fcf8118771cb311 + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.encoded.hexString, "01000000026c90312e53a3411347a197bfd637c2583d617dd2317262a70e1b5245d2f1e36a000000008a47304402201a631068ea5ddea19467ef7c932a0f3b04f366ca2beaf70e18958e47456124980220614816c449e39cf6acc6625e1cf3100db1db7c0b755bdbb6804d4fa3c4b735d10141041b3937fac1f14074447cde9d3a324ed292d2865ed0d7a7da26cb43558ce4db4ef33c47e820e53031ae16bb0c39205def059a5ca8e1d617650eabc72c5206a81dffffffff13bf27945c669cf3c1d70cf3048f4ab14f1ab6acf06d10d425e8288217a81efd000000008a473044022051d381d8f48a9a4866ca4109f12647922514604a4733e8da8aac046e19275f700220797c3ebf20df7d2a9fed283f9d0ad14cbd656cafb5ec70a2b1c85646ea7485190141041b3937fac1f14074447cde9d3a324ed292d2865ed0d7a7da26cb43558ce4db4ef33c47e820e53031ae16bb0c39205def059a5ca8e1d617650eabc72c5206a81dffffffff0194590000000000001976a914a0c0a50f986924e65ae9bd18eafae448f83117ed88ac00000000") + } + + func testSignPsbtThorSwap() throws { + let privateKey = Data(hexString: "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")! + let psbt = Data(hexString: "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000")! + + let input = BitcoinV2SigningInput.with { + $0.psbt = BitcoinV2Psbt.with { + $0.psbt = psbt + } + $0.privateKeys = [privateKey] + } + + let legacy = BitcoinSigningInput.with { + $0.signingV2 = input + } + let output: BitcoinSigningOutput = AnySigner.sign(input: legacy, coin: .bitcoin) + + XCTAssertEqual(output.error, .ok) + XCTAssert(output.hasSigningResultV2) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.psbt.psbt.hexString, "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") + XCTAssertEqual(outputV2.encoded.hexString, "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") + XCTAssertEqual(outputV2.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 = BitcoinV2SigningInput.with { + // TODO check if it works + $0.psbt.psbt = psbt + $0.publicKeys = [publicKey.data] + } + + let legacy = BitcoinSigningInput.with { + $0.signingV2 = input + } + let legacyPlan: BitcoinTransactionPlan = AnySigner.plan(input: legacy, coin: .bitcoin) + + XCTAssertEqual(legacyPlan.error, .ok) + XCTAssert(legacyPlan.hasPlanningResultV2) + let plan = legacyPlan.planningResultV2 + + XCTAssertEqual(plan.inputs[0].receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + XCTAssertEqual(plan.inputs[0].value, 66_406) + + // Vault transfer + XCTAssertEqual(plan.outputs[0].toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") + XCTAssertEqual(plan.outputs[0].value, 60_000) + + // OP_RETURN + XCTAssertEqual( + plan.outputs[1].customScriptPubkey.hexString, + "6a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a3530" + ) + XCTAssertEqual(plan.outputs[1].value, 0) + + // Change output + XCTAssertEqual(plan.outputs[2].toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + XCTAssertEqual(plan.outputs[2].value, 4_670) + + XCTAssertEqual(plan.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(plan.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 d6f03943228..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 @@ -61,14 +59,14 @@ class BitcoinCashTests: XCTestCase { } func testSign() throws { - let utxoTxId = Data(hexString: "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2")! + let utxoTxId = "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2" let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!)! let address = CoinType.bitcoinCash.deriveAddress(privateKey: privateKey) let utxo = BitcoinUnspentTransaction.with { - $0.outPoint.hash = Data(utxoTxId.reversed()) // reverse of UTXO tx id, Bitcoin internal expects network byte order - $0.outPoint.index = 2 // outpoint index of this this UTXO + $0.outPoint.hash = Data.reverse(hexString: utxoTxId) // reverse of UTXO tx id, Bitcoin internal expects network byte order + $0.outPoint.index = 2 // outpoint index of this this UTXO $0.outPoint.sequence = UINT32_MAX - $0.amount = 5151 // value of this UTXO + $0.amount = 5151 // value of this UTXO $0.script = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoinCash).data // Build lock script from address or public key hash } diff --git a/swift/Tests/Blockchains/BluzelleTests.swift b/swift/Tests/Blockchains/BluzelleTests.swift new file mode 100644 index 00000000000..78d32c8843f --- /dev/null +++ b/swift/Tests/Blockchains/BluzelleTests.swift @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +import XCTest +import WalletCore + +class BluzelleAddressTests: XCTestCase { + + func testAddressPublicKey() { + + let privateKeyData = Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")! + let privateKey = PrivateKey(data: privateKeyData)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + + let expectedAddress = "bluzelle1jf9aaj9myrzsnmpdr7twecnaftzmku2myvn4dg" + let actualAddress = AnyAddress(publicKey: publicKey, coin: .bluzelle).description + + let expectedPublicKeyData = "035df185566521d6a7802319ee06e1a28e97b7772dfb5fdd13ca6f0575518968e4" + let actualPublicKeyData = publicKey.data.hexString + + XCTAssertEqual(expectedAddress, actualAddress) + XCTAssertEqual(expectedPublicKeyData, actualPublicKeyData) + } + + func testAddressValidation() { + let bluzelle = CoinType.bluzelle + for address in [ + "bluzelle1yhtq5zm293m2r3sp2guj9m5pg5e273n6r0szul", + "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund", + ] { + XCTAssertTrue(bluzelle.validate(address: address)) + XCTAssertEqual(bluzelle.address(string: address)?.description, address) + } + } +} + +class BluzelleSignerTests: XCTestCase { + + let privateKeyData = Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")! + let myAddress = "bluzelle1hsk6jryyqjfhp5dhc55tc9jtckygx0epzzw0fm" + + + func testSigningJSON() { + + // Submitted realworld tx for the following test : https://bigdipper.net.bluzelle.com/transactions/DEF394BE0DD1075CDC8B8618A7858AAA4A03A43D04476381224E316E06FD3A5B + + let toAddress = "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + let inputJson = + """ + { + "accountNumber": "590", + "chainId": "net-6", + "sequence": "3", + "fee": { + "amounts": [{ + "denom": "ubnt", + "amount": "1000" + }], + "gas": "500000" + }, + "memo": "Testing", + "messages": [{ + "sendCoinsMessage": { + "fromAddress": "\(myAddress)", + "toAddress": "\(toAddress)", + "amounts": [{ + "denom": "ubnt", + "amount": "2" + }] + } + }] + } + """ + + let expectedSignedJson = + """ + {"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"ubnt"}],"gas":"500000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"2","denom":"ubnt"}],"from_address":"bluzelle1hsk6jryyqjfhp5dhc55tc9jtckygx0epzzw0fm","to_address":"bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"2JcfVwq9N3UAk5Lfki7+CngqcefgjfH1q/8chtJMJvEHRe6PvuYKMv5pjeN0Z5Vk2BArJT3V3EHxbpbiY2eLWw=="}]}} + """ + + let actualSignedJson = AnySigner.signJSON(inputJson, key: privateKeyData, coin: .bluzelle) + + XCTAssertJSONEqual(expectedSignedJson, actualSignedJson) + + } + + func testSigningMessage() { + // Submitted Realworld tx for the following test : https://bigdipper.net.bluzelle.com/transactions/B3A7F30539CCDF72D210BC995FAF65B43F9BE04FA9F8AFAE0EC969660744002F + + let privateKey = PrivateKey(data: privateKeyData)! + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = myAddress + $0.toAddress = "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + $0.amounts = [CosmosAmount.with { + $0.amount = "1" + $0.denom = "ubnt" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 500000 + $0.amounts = [CosmosAmount.with { + $0.amount = "1000" + $0.denom = "ubnt" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 590 + $0.chainID = "net-6" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bluzelle) + + let expectedJSON: String = + """ + {"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"ubnt"}],"gas":"500000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"ubnt"}],"from_address":"bluzelle1hsk6jryyqjfhp5dhc55tc9jtckygx0epzzw0fm","to_address":"bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"5e3e13x1F+Y4+cPNvE1jQ/Mrz0J2RQCY69re3g4xuTY3Gw7MNGQ+8E34d9DgvcNLPM05nehdOv/0SvekY/ihIQ=="}]}} + """ + + XCTAssertJSONEqual(expectedJSON, output.json) + } +} diff --git a/swift/Tests/Blockchains/CardanoTests.swift b/swift/Tests/Blockchains/CardanoTests.swift index 8e7d5ed1b86..4acef413ba3 100644 --- a/swift/Tests/Blockchains/CardanoTests.swift +++ b/swift/Tests/Blockchains/CardanoTests.swift @@ -1,20 +1,337 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms 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 class CardanoTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4")!)! - let pubkey = key.getPublicKeyEd25519Extended() + let key = PrivateKey(data: Data(hexString: "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")!)! + let pubkey = key.getPublicKeyEd25519Cardano() let address = AnyAddress(publicKey: pubkey, coin: .cardano) - let addressFromString = AnyAddress(string: "addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668", coin: .cardano)! + let addressFromString = AnyAddress(string: "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", coin: .cardano)! - XCTAssertEqual(pubkey.data.hexString, "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") + XCTAssertEqual(pubkey.data.hexString, "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276") XCTAssertEqual(address.description, addressFromString.description) } + + func testDeriveAddressWallet() { + let wallet = HDWallet(mnemonic: "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh", passphrase: "")! + let privateKey = wallet.getKeyForCoin(coin: .cardano) + XCTAssertEqual(privateKey.data.hexString, "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276") + let address = CoinType.cardano.deriveAddress(privateKey: privateKey) + XCTAssertEqual(address, "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq") + } + + func testSignTransfer1() { + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5" + $0.transferMessage.changeAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.transferMessage.amount = 7000000 + $0.ttl = 53333333 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767")! + $0.outPoint.outputIndex = 1 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 1500000 + } + input.utxos.append(utxo1) + + let utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0")! + $0.outPoint.outputIndex = 0 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 6500000 + } + 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, + "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6") + + 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 { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "SUNDAE" + $0.amount = Data(hexString: "01312d00")! // 20000000 + } + var toTokenBundle = CardanoTokenBundle(); + toTokenBundle.token.append(toToken) + + // check min ADA amount, set it + let inputTokenAmountSerialized = try toTokenBundle.serializedData() + let minAmount = CardanoMinAdaAmount(tokenBundle: inputTokenAmountSerialized) + XCTAssertEqual(minAmount, 1444443) + + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5" + $0.transferMessage.changeAddress = "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq" + $0.transferMessage.amount = minAmount + $0.transferMessage.useMaxAmount = false + $0.transferMessage.tokenAmount = toTokenBundle + $0.ttl = 53333333 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + var utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767")! + $0.outPoint.outputIndex = 1 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 8051373 + } + let token3 = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "CUBY" + $0.amount = Data(hexString: "2dc6c0")! // 3000000 + } + utxo1.tokenAmount.append(token3) + input.utxos.append(utxo1) + + var utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767")! + $0.outPoint.outputIndex = 2 + $0.address = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + $0.amount = 2000000 + } + let token1 = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetNameHex = "53554e444145" + $0.amount = Data(hexString: "04d3e8d9")! // 80996569 + } + utxo2.tokenAmount.append(token1) + let token2 = CardanoTokenAmount.with { + $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + $0.assetName = "CUBY" + $0.amount = Data(hexString: "1e8480")! // 2000000 + } + utxo2.tokenAmount.append(token2) + 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, + "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, "87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628") + } } diff --git a/swift/Tests/Blockchains/CeloTests.swift b/swift/Tests/Blockchains/CeloTests.swift new file mode 100644 index 00000000000..367d9531e60 --- /dev/null +++ b/swift/Tests/Blockchains/CeloTests.swift @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class CeloTests: XCTestCase { + func testTransfer() { + // real key is bnb-eth + let keyData = Data(hexString: "0x4646464646464646464646464646464646464646464646464646464646464646")! + + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "a4ec")! + $0.nonce = Data(hexString: "00")! + $0.gasPrice = Data(hexString: "0x1dcd6500")! // 500000000 + $0.gasLimit = Data(hexString: "0x520c")! // 21004 + $0.toAddress = "0x01EfD468c9e58B63e54BEFa100cF1690880fFCcB" + $0.privateKey = keyData + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "0DE0B6B3A7640000")! // 1 CELO + } + } + } + + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .celo) + + // https://explorer.celo.org/tx/0x381252b2de73e157cb669d2aff8f9e13964e3e70cbacab39aaadf05abca9b7ca + XCTAssertEqual("0x" + output.encoded.hexString, "0xf86e80841dcd650082520c9401efd468c9e58b63e54befa100cf1690880ffccb880de0b6b3a764000080830149fca0b5702b6a650264e23917fcd2c9c73c687ab4ad0a825deff3519cf3f74ad69508a078d6c8afbcff9e2af748d065a926498b129c33f9dac1efc64a0432270bb6ed10") + } +} 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 21a7ab0dd4c..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 @@ -34,7 +32,7 @@ class CosmosSignerTests: XCTestCase { $0.fromAddress = fromAddress.description $0.toAddress = "cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573" $0.amounts = [CosmosAmount.with { - $0.amount = 1 + $0.amount = "1" $0.denom = "muon" }] } @@ -46,12 +44,13 @@ class CosmosSignerTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 200 + $0.amount = "200" $0.denom = "muon" }] } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.accountNumber = 1037 $0.chainID = "gaia-13003" $0.memo = "" @@ -63,50 +62,80 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "200", - "denom": "muon" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "1", - "denom": "muon" + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + 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 } - ], - "from_address": "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", - "to_address": "cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573" + $0.expiration = 1692309600 } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg==" - } - ] - } -} -""" + 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(expectedJSON, output.json) + 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() { @@ -114,7 +143,7 @@ class CosmosSignerTests: XCTestCase { $0.delegatorAddress = "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02" $0.validatorAddress = "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp" $0.amount = CosmosAmount.with { - $0.amount = 10 + $0.amount = "10" $0.denom = "muon" } } @@ -126,12 +155,13 @@ class CosmosSignerTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 101721 $0.amounts = [CosmosAmount.with { - $0.amount = 1018 + $0.amount = "1018" $0.denom = "muon" }] } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.accountNumber = 1037 $0.chainID = "gaia-13003" $0.memo = "" @@ -143,47 +173,8 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "1018", - "denom": "muon" - } - ], - "gas": "101721" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgDelegate", - "value": { - "amount": { - "amount": "10", - "denom": "muon" - }, - "delegator_address": "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", - "validator_address": "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ==" - } - ] - } -} - -""" - XCTAssertJSONEqual(expectedJSON, output.json) + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\"}") + XCTAssertEqual(output.errorMessage, "") } func testWithdraw() { @@ -209,12 +200,13 @@ class CosmosSignerTests: XCTestCase { let fee = CosmosFee.with { $0.amounts = [CosmosAmount.with { $0.denom = "uatom" - $0.amount = 1 + $0.amount = "1" }] $0.gas = 220000 } let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; $0.fee = fee $0.accountNumber = 8698 $0.chainID = "cosmoshub-2" @@ -226,54 +218,56 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - let expectedJSON = """ - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "1", - "denom": "uatom" - } - ], - "gas": "220000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "cosmos100rhxclqasy6vnrcervgh99alx5xw7lkfp4u54", - "validator_address": "cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc46pzjrm873ae8" - } - },{ - "type": "cosmos-sdk/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "cosmos100rhxclqasy6vnrcervgh99alx5xw7lkfp4u54", - "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" - } - },{ - "type": "cosmos-sdk/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "cosmos100rhxclqasy6vnrcervgh99alx5xw7lkfp4u54", - "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "2k5bSnfWxaauXHBNJTKmf4CpLiCWLg7UAC/q2SVhZNkU+n0DdLBSTdmYhKYmmtpl/Njm4YrcxE0WLb/hVccQ+g==" - } - ] - } + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CukDCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczEwMHJoeGNscWFzeTZ2bnJjZXJ2Z2g5OWFseDV4dzdsa2ZwNHU1NBI0Y29zbW9zdmFsb3BlcjFleTY5cjM3Z2Z4dnhnNjJzaDRyMGt0cHVjNDZwempybTg3M2FlOAqgAQo3L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd0RlbGVnYXRvclJld2FyZBJlCi1jb3Ntb3MxMDByaHhjbHFhc3k2dm5yY2VydmdoOTlhbHg1eHc3bGtmcDR1NTQSNGNvc21vc3ZhbG9wZXIxc2psbHNucmFtdGczZXd4cXd3cndqeGZnYzRuNGVmOXUybGNuajAKoAEKNy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdEZWxlZ2F0b3JSZXdhcmQSZQotY29zbW9zMTAwcmh4Y2xxYXN5NnZucmNlcnZnaDk5YWx4NXh3N2xrZnA0dTU0EjRjb3Ntb3N2YWxvcGVyMTY0OHlubHBkdzdmcWEyYXh0MHcyeXAzZms1NDJqdW5sN3JzdnE2EmUKUQpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARi+AhIQCgoKBXVhdG9tEgExEOC2DRpAXLgJ+8xEMUn7nkFj3ukg2V65Vh5ob7HKeCaNpMM6OPQrpW2r6askfssIFcOd8ThiBEz65bJz81Fmb5MtDTGv4g==\"}") + XCTAssertEqual(output.errorMessage, "") + } + + func testIbcTransfer() { + let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .cosmos) + + let transferMessage = CosmosMessage.Transfer.with { + $0.sourcePort = "transfer" + $0.sourceChannel = "channel-141" + $0.sender = fromAddress.description + $0.receiver = "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn" + $0.token = CosmosAmount.with { + $0.amount = "100000" + $0.denom = "uatom" + } + $0.timeoutHeight = CosmosHeight.with { + $0.revisionNumber = 1 + $0.revisionHeight = 8800000 + } + } + + let message = CosmosMessage.with { + $0.transferTokensMessage = transferMessage + } + + let fee = CosmosFee.with { + $0.gas = 500000 + $0.amounts = [CosmosAmount.with { + $0.amount = "12500" + $0.denom = "uatom" + }] } - """ - XCTAssertJSONEqual(expectedJSON, output.json) + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 546179 + $0.chainID = "cosmoshub-4" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) + + // 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.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/CronosTests.swift b/swift/Tests/Blockchains/CronosTests.swift new file mode 100644 index 00000000000..a1e917f30ce --- /dev/null +++ b/swift/Tests/Blockchains/CronosTests.swift @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class CronosTests: XCTestCase { + + func testAddress() { + let address = AnyAddress(string: "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", coin: .cronosChain)! + + XCTAssertEqual(address.data.hexString, "ec49280228b0d05aa8e8b756503254e1ee7835ab") + } +} diff --git a/swift/Tests/Blockchains/CryptoorgTests.swift b/swift/Tests/Blockchains/CryptoorgTests.swift new file mode 100644 index 00000000000..9f781e24982 --- /dev/null +++ b/swift/Tests/Blockchains/CryptoorgTests.swift @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class CryptoorgTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .cryptoOrg) + let addressFromString = AnyAddress(string: "cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd", coin: .cryptoOrg)! + + XCTAssertEqual(pubkey.data.hexString, "03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77") + XCTAssertEqual(address.description, addressFromString.description) + } + + let privateKey = PrivateKey(data: Data(hexString: "200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d")!)! + + func testSign() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .cryptoOrg) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" + $0.amounts = [CosmosAmount.with { + $0.amount = "50000000" + $0.denom = "basecro" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "5000" + $0.denom = "basecro" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 125798 + $0.chainID = "crypto-org-chain-mainnet-1" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cryptoOrg) + + // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") + 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 fec1ebfaead..e13b23dcfad 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 @@ -35,7 +33,7 @@ class DecredTests: XCTestCase { // https://mainnet.decred.org/tx/bcc5228e9d956918984d1853c31d7edcd862f8a7fca20ded114d93f8a74ad32a let key = PrivateKey(data: Data(hexString: "ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764")!)! - let txHash = Data(Data(hexString: "5015d14dcfd78998cfa13e0325798a74d95bbe75f167a49467303f70dde9bffd")!.reversed()) + let txHash = Data.reverse(hexString: "5015d14dcfd78998cfa13e0325798a74d95bbe75f167a49467303f70dde9bffd") let utxoAddress = CoinType.decred.deriveAddress(privateKey: key) let script = BitcoinScript.lockScriptForAddress(address: utxoAddress, coin: .decred) @@ -71,4 +69,71 @@ class DecredTests: XCTestCase { XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) XCTAssertEqual(output.encoded.hexString, "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ac40b6c6010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6") } + + func testSignV2() { + // https://dcrdata.decred.org/tx/0934163f403cf9d256447890fed972e1f8b66309ecd41dec8a4dcfb657906a68 + let privateKeyData = Data(hexString: "99ed469e6b7d9f188962940d9d0f9fd8582c6c37e52394348f177ff0526b8a03")! + let privateKey = PrivateKey(data: privateKeyData)! + let senderAddress = CoinType.decred.deriveAddress(privateKey: privateKey) + let toAddress = "Dsofok7qyhDLVRXcTqYdFgmGsUFSiHonbWH" + + let txHash = Data.reverse(hexString: "c5cc3b1fc20c9e43a7d1127ba7e4802d04c16515a7eaaad58a1bc388acacfeae") + let dustAmount = Int64(546) + + let utxo = BitcoinV2Input.with { + $0.outPoint = UtxoOutPoint.with { + $0.hash = txHash + $0.vout = 0 + } + $0.value = 100_000_000 + $0.sighashType = BitcoinSigHashType.all.rawValue + $0.receiverAddress = senderAddress + } + + // 0.1 DCR to another address. + let out0 = BitcoinV2Output.with { + $0.value = 10_000_000 + $0.toAddress = toAddress + } + + // 0.05 DCR to self. + let out1 = BitcoinV2Output.with { + $0.value = 5_000_000 + $0.toAddress = senderAddress + } + + // Send remaining amount to self by my address. + let changeOut = BitcoinV2Output.with { + $0.toAddress = senderAddress + } + + let signingInput = BitcoinV2SigningInput.with { + $0.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v1 + $0.inputs = [utxo] + $0.outputs = [out0, out1] + $0.changeOutput = changeOut + $0.inputSelector = .selectDescending + $0.fixedDustThreshold = dustAmount + $0.feePerVb = 10 + } + $0.privateKeys = [privateKeyData] + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 63 + $0.p2ShPrefix = 26 + } + } + + let legacySigningInput = BitcoinSigningInput.with { + $0.signingV2 = signingInput + $0.coinType = CoinType.decred.rawValue + } + + let output: DecredSigningOutput = AnySigner.sign(input: legacySigningInput, coin: .decred) + XCTAssertEqual(output.error, .ok) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.error, .ok) + XCTAssertEqual(outputV2.encoded.hexString, "0100000001aefeacac88c31b8ad5aaeaa71565c1042d80e4a77b12d1a7439e0cc21f3bccc50000000000ffffffff03809698000000000000001976a914f90f06478396b97df24c012b922f953fa894676188ac404b4c000000000000001976a9147d15c291fb7de05a38e39121df1e02f875a49f8988acf6f310050000000000001976a9147d15c291fb7de05a38e39121df1e02f875a49f8988ac00000000000000000100e1f5050000000000000000ffffffff6b483045022100c5464db9df8196c563db40ba1f7650291c755713ad87920a70c38c15db17bc3d02201c6bc9e4f8e43fdd548b0be387dd7baf7270bced8c9da4148128a655bed52e1a01210241d97dd9273a477c3560f1c5dba9dfd49624d8718ccca5619ff4a688dfcef01b"); + XCTAssertEqual(outputV2.txid.hexString, "686a9057b6cf4d8aec1dd4ec0963b6f8e172d9fe90784456d2f93c403f163409") + } } 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 new file mode 100644 index 00000000000..7f07d59b0ba --- /dev/null +++ b/swift/Tests/Blockchains/ECashTests.swift @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class ECashTests: XCTestCase { + + func testExtendedKeys() { + let wallet = HDWallet.test + + let xprv = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .ecash, version: .xprv) + let xpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .ecash, version: .xpub) + + XCTAssertEqual(xprv, "xprv9xjBcTizebJaV61xMkuMJ89vis7saMmwFgTYeF83KwinEksJ4frk7wB4mDiKiwXDCbJmgmh6Bp1FkF8SopNZhbF3B5wyX32cuDVFZtuUDvB") + XCTAssertEqual(xpub, "xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7") + } + + func testDeriveFromXPub() { + let xpub = "xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7" + + let coin = CoinType.ecash + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 2).description + )! + + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 9).description + )! + + XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr2), "ecash:qpttymfhuq3v8tasfv7drlglhq6ne6zxquqltu3dcj") + XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr9), "ecash:qqjraw2s5pwqwzql4znjpvp4vtvy3c9gmugq62r2j7") + } + + func testAddress() { + XCTAssertEqual( + "ecash:prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", + AnyAddress(string: "ecash:prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", coin: .ecash)?.description + ) + XCTAssertEqual( + "ecash:prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", + AnyAddress(string: "prm3srpqu4kmx00370m4wt5qr3cp7sekmc0adf8na6", coin: .ecash)?.description + ) + } + + func testLockScript() { + let address = AnyAddress(string: "pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3gkypy0vjg", coin: .ecash)! + let script = BitcoinScript.lockScriptForAddress(address: address.description, coin: .ecash) + XCTAssertEqual(script.data.hexString, "a914b9604b7820876bc510009b8247316c4b801aff8a87") + + let address2 = AnyAddress(string: "qphr8l8ns8wd99a8653ctfe5qcrxaumz5qck55dsl3", coin: .ecash)! + let script2 = BitcoinScript.lockScriptForAddress(address: address2.description, coin: .ecash) + XCTAssertEqual(script2.data.hexString, "76a9146e33fcf381dcd297a7d52385a73406066ef362a088ac") + } + + func testSign() throws { + let utxoTxId = "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2" + let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!)! + let address = CoinType.ecash.deriveAddress(privateKey: privateKey) + let utxo = BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data.reverse(hexString: utxoTxId) // reverse of UTXO tx id, Bitcoin internal expects network byte order + $0.outPoint.index = 2 // outpoint index of this this UTXO + $0.outPoint.sequence = UINT32_MAX + $0.amount = 5151 // value of this UTXO + $0.script = BitcoinScript.lockScriptForAddress(address: address, coin: .ecash).data // Build lock script from address or public key hash + } + + let input = BitcoinSigningInput.with { + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .ecash) + $0.amount = 600 + $0.byteFee = 1 + $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" + $0.utxo = [utxo] + $0.privateKey = [privateKey.data] + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .ecash) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + XCTAssertEqual(output.transactionID, "96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4") + XCTAssertEqual(output.encoded.hexString, "0100000001e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05020000006b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5bffffffff0258020000000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ace5100000000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000") + } +} 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 d9d5b1d746a..00000000000 --- a/swift/Tests/Blockchains/ElrondTests.swift +++ /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. - -import WalletCore -import XCTest - -class ElrondTests: XCTestCase { - - let aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - let aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" - let alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" - let bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" - - 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 testSign() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! - - let input = ElrondSigningInput.with { - $0.transaction = ElrondTransactionMessage.with { - $0.nonce = 0 - $0.value = "0" - $0.sender = aliceBech32 - $0.receiver = bobBech32 - $0.gasPrice = 1000000000 - $0.gasLimit = 50000 - $0.data = "foo" - $0.chainID = "1" - $0.version = 1 - } - - $0.privateKey = privateKey.data - } - - let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00" - let expectedEncoded = #"{"nonce":0,"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) - } -} diff --git a/swift/Tests/Blockchains/EthereumAbiTests.swift b/swift/Tests/Blockchains/EthereumAbiTests.swift index 6ba2ff5fb90..ce7ada79d07 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", @@ -100,4 +98,282 @@ class EthereumAbiTests: XCTestCase { """ XCTAssertJSONEqual(decoded, expected) } + + func testDecode1inch() throws { + // https://etherscan.io/tx/0xc2d113151124579c21332d4cc6ab2b7f61e81d62392ed8596174513cb47e35ba + let data = Data(hexString: "7c02520000000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd2000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd20000000000000000000000001611c227725c5e420ef058275ae772b41775e261000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000005c31df1da000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000")! + let url = Bundle(for: EthereumAbiTests.self).url(forResource: "1inch", withExtension: "json")! + let abi = try String(contentsOf: url) + let decoded = EthereumAbi.decodeCall(data: data, abi: abi)! + let expected = """ + { + "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" + }] + } + """ + XCTAssertJSONEqual(decoded, expected) + } + + func testEncodeTyped() throws { + let message = + """ + { + "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!" + } + } + """ + 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") + } + + func testEthereumAbiGetFunctionSignature() throws { + let abiJson = """ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + """ + + let functionSignature = EthereumAbi.getFunctionSignature(abi: abiJson) + XCTAssertEqual(functionSignature, "transfer(address,uint256)") + } } 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 8af0649d7e7..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) @@ -21,10 +19,11 @@ class EthereumTests: XCTestCase { XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .ethereum)) } - func testSigner() { + func testSigner() throws { let input = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! $0.nonce = Data(hexString: "09")! + // txMode not set, Legacy is the default $0.gasPrice = Data(hexString: "04a817c800")! $0.gasLimit = Data(hexString: "5208")! $0.toAddress = "0x3535353535353535353535353535353535353535" @@ -38,6 +37,7 @@ class EthereumTests: XCTestCase { let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + XCTAssertEqual(try input.serializedData().hexString, "0a0101120109220504a817c8002a025208422a3078333533353335333533353335333533353335333533353335333533353335333533353335333533354a204646464646464646464646464646464646464646464646464646464646464646520c0a0a0a080de0b6b3a7640000") XCTAssertEqual(output.encoded.hexString, "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") } @@ -45,6 +45,7 @@ class EthereumTests: XCTestCase { let input = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! $0.nonce = Data(hexString: "00")! + // txMode not set, Legacy is the default $0.gasPrice = Data(hexString: "09c7652400")! // 42000000000 $0.gasLimit = Data(hexString: "0130B9")! // 78009 $0.toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI @@ -62,10 +63,33 @@ class EthereumTests: XCTestCase { XCTAssertEqual(output.data.hexString, "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000") } + func testSignERC20Transfer_1559() { + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "01")! + $0.nonce = Data(hexString: "00")! + $0.txMode = .enveloped + $0.gasLimit = Data(hexString: "0130B9")! // 78009 + $0.maxInclusionFeePerGas = Data(hexString: "0077359400")! // 2000000000 + $0.maxFeePerGas = Data(hexString: "00B2D05E00")! // 3000000000 + $0.toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI + $0.privateKey = Data(hexString: "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")! + $0.transaction = EthereumTransaction.with { + $0.erc20Transfer = EthereumTransaction.ERC20Transfer.with { + $0.to = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + $0.amount = Data(hexString: "1bc16d674ec80000")! // 2000000000000000000 + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + + XCTAssertEqual(output.encoded.hexString, "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf") + } + func testSignERC20Approve() { let input = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! $0.nonce = Data(hexString: "00")! + // txMode not set, Legacy is the default $0.gasPrice = Data(hexString: "09c7652400")! // 42000000000 $0.gasLimit = Data(hexString: "0130B9")! // 78009 $0.toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI @@ -88,6 +112,7 @@ class EthereumTests: XCTestCase { let input = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! $0.nonce = Data(hexString: "02de")! // 734 + // txMode not set, Legacy is the default $0.gasPrice = Data(hexString: "22ecb25c00")! // 150000000000 $0.gasLimit = Data(hexString: "0130b9")! // 78009 $0.toAddress = "0x0d8c864DA1985525e0af0acBEEF6562881827bd5" // contract @@ -110,6 +135,7 @@ class EthereumTests: XCTestCase { let input = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! $0.nonce = Data(hexString: "00")! + // txMode not set, Legacy is the default $0.gasPrice = Data(hexString: "09C7652400")! // 42000000000 $0.gasLimit = Data(hexString: "0130b9")! // 78009 $0.toAddress = "0x4e45e92ed38f885d39a733c14f1817217a89d425" // contract @@ -130,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 = """ { @@ -149,4 +236,137 @@ class EthereumTests: XCTestCase { XCTAssertEqual(result, "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10") } + + func testGetPublicKeyFromXpub() throws { + let wallet = HDWallet(mnemonic: "broom ramp luggage this language sketch door allow elbow wife moon impulse", passphrase: "")! + let path = "m/44'/60'/0'/0/1" + let xpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .ethereum, version: .xpub) + + XCTAssertEqual(xpub, "xpub6C7LtZJgtz1BKXG9mExKUxYvX7HSF38UMMmGbpqNQw3DfYwAw8E6sH7VSVxFipvEEm2afSqTjoRgcLmycXX4zfxCWJ4HY73a9KdgvfHEQGB") + + let key = wallet.getKey(coin: .ethereum, derivationPath: path) + let pubkey = key.getPublicKeySecp256k1(compressed: true) + XCTAssertEqual(pubkey.data.hexString, "024516c4aa5352035e1bb5be132694e1389a4ac37d32e5e717d35ee4c4dfab5132") + + let pubkey2 = HDWallet.getPublicKeyFromExtended(extended: xpub, coin: .ethereum, derivationPath: path)! + XCTAssertEqual(pubkey2.data.hexString, "044516c4aa5352035e1bb5be132694e1389a4ac37d32e5e717d35ee4c4dfab513226a9d14ea37a55962ad3644a08e2ce551b4495beabb9b09e688c7b92eba18acc") + + let address = CoinType.ethereum.deriveAddressFromPublicKey(publicKey: pubkey2) + XCTAssertEqual(address, "0x996891c410FB76C19DBA72C6f6cEFF2d9DD069b1") + } + + func testSpanishMnemonic() throws { + let mnemonic = "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut" + XCTAssertNil(HDWallet(mnemonic: mnemonic, passphrase: "")) + + let wallet = HDWallet(mnemonic: mnemonic, passphrase: "", check: false)! + let btcXpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .bitcoin, version: .xpub) + let ethXpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .ethereum, version: .xpub) + + XCTAssertEqual(btcXpub, "xpub6Cq43Vqyvb2DwXzjzNeMpPuxXRCN1WnmRCmYLPaaSv2XZXM2yCwUHpWEyB3zQ3FGCQsvY21gecMaQR7b2zhhgiHnjzDYpKCE2LACueaSMuR") + XCTAssertEqual(ethXpub, "xpub6Bgma7boPVudhExmB97iySvatGfnXkfBxYZYNTFYJvVzigUPk1X2iE8VhJPPxVuzjH8wBuTqRBMKCbwMYQNLrFCwYzMugYw4RM5VGNeVDpp") + + let ethAddress = wallet.getAddressForCoin(coin: .ethereum) + let btcAddress = wallet.getAddressForCoin(coin: .bitcoin) + + 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 new file mode 100644 index 00000000000..e1df1d44943 --- /dev/null +++ b/swift/Tests/Blockchains/EvmosTests.swift @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class EvmosTests: XCTestCase { + + func testAddressData() throws { + let wallet = HDWallet(strength: 256, passphrase: "")! + let nativeEvmos = wallet.getAddressForCoin(coin: .nativeEvmos) + let evmos = wallet.getAddressForCoin(coin: .evmos) + + let addr1 = AnyAddress(string: nativeEvmos, coin: .nativeEvmos) + let addr2 = AnyAddress(string: evmos, coin: .evmos) + + XCTAssertEqual(addr1?.data.hexString, addr2?.data.hexString) + } + + func testSigningNativeTransfer() { + + let wallet = HDWallet(mnemonic: "glue blanket noodle name bring castle degree vibrant great joy usual mother pyramid cat balance swear diagram green split goat token day arm shoe", passphrase: "")! + let privateKey = wallet.getKeyForCoin(coin: .nativeEvmos) + let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) + + XCTAssertEqual(publicKey.data.hexString, "049475c9fa23ec693667baa76c4da69b49cccfdf058c4dcb27ba67cfbc9082d9ed9074786560aa698b19bb9729526b1c75934f3d4a78f7be719e4386b749b36310") + + let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeEvmos) + + print(publicKey.compressed.data.hexString) + + XCTAssertEqual(fromAddress.description, "evmos1rk39dk3wff5nps7emuhv3ntkn3nsz6z2erqfr0") + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "evmos10k9lrrruap9nu96mxwwye2f6a5wazeh33kq67z" // 1p + $0.amounts = [CosmosAmount.with { + $0.amount = "200000000000000" // 0.0002 Evmos + $0.denom = "aevmos" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 140000 + $0.amounts = [CosmosAmount.with { + $0.amount = "1400000000000000" + $0.denom = "aevmos" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 1982243 + $0.chainID = "evmos_9001-2" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .nativeEvmos) + // https://www.mintscan.io/evmos/txs/B05D2047086B158665EC552879270AEF40AEAAFEE7D275B63E9674E3CC4C4E55 + let expected = """ + {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKLGV2bW9zMXJrMzlkazN3ZmY1bnBzN2VtdWh2M250a24zbnN6NnoyZXJxZnIwEixldm1vczEwazlscnJydWFwOW51OTZteHd3eWUyZjZhNXdhemVoMzNrcTY3ehoZCgZhZXZtb3MSDzIwMDAwMDAwMDAwMDAwMBJ7ClcKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiEClHXJ+iPsaTZnuqdsTaabSczP3wWMTcsnumfPvJCC2e0SBAoCCAESIAoaCgZhZXZtb3MSEDE0MDAwMDAwMDAwMDAwMDAQ4MUIGkAz9vh1EutbLrLZmRA4eK72bA6bhfMX0YnhtRl5jeaL3AYmk0qdrwG9XzzleBsZ++IokJIk47cgOOyvEjl92Jhj"} + """ + XCTAssertJSONEqual(output.serialized, expected) + 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 c701b3dcb19..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 @@ -33,7 +31,7 @@ class GroestlcoinTests: XCTestCase { } func testExtendedKeys() { - let wallet = HDWallet(mnemonic: "all all all all all all all all all all all all", passphrase: "") + let wallet = HDWallet(mnemonic: "all all all all all all all all all all all all", passphrase: "")! // .bip44 let xprv = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .groestlcoin, version: .xprv) 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 dd40c71224c..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 @@ -10,6 +8,12 @@ import XCTest class HarmonyTests: XCTestCase { let localNet = "0x02" + func testAddressData() { + let address = AnyAddress(string: "one1c8dpswxg2p50znzecnq0peuxlxtcm9je7q7yje", coin: .harmony)! + + XCTAssertEqual(address.data.hexString, "c1da1838c85068f14c59c4c0f0e786f9978d9659") + } + func testSigner() { let transaction = HarmonyTransactionMessage.with { $0.nonce = Data(hexString: "0x09")! @@ -313,4 +317,14 @@ class HarmonyTests: XCTestCase { XCTAssertEqual(output.r.hexString, "4c15c72f42577001083a9c7ff9d9724077aec704a524e53dc7c9afe97ca4e625") XCTAssertEqual(output.s.hexString, "55c13ea17c3efd1cd91f2988c7e7673950bac5a08c174f2d0af27a82039f1e3d") } + + func testSignJSON() { + let json = """ + {"chainId":"Ag==","transactionMessage":{"nonce":"AQ==","gasPrice":"AA==","gasLimit":"Ugg=","toAddress":"one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k","amount":"Br/I2l7oIgAA","fromShardId":"AQ==","toShardId":"AA=="}} + """ + let key = Data(hexString: "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")! + let result = AnySigner.signJSON(json, key: key, coin: .harmony) + + XCTAssertEqual(result, "f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc") + } } 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 b9ed2095a30..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 @@ -29,7 +27,7 @@ class KavaTests: XCTestCase { $0.fromAddress = fromAddress $0.toAddress = "kava1hdp298kaz0eezpgl6scsykxljrje3667hmlv0h" $0.amounts = [CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "ukava" }] } @@ -41,7 +39,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } @@ -101,7 +99,7 @@ class KavaTests: XCTestCase { $0.delegatorAddress = "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" $0.validatorAddress = "kavavaloper17498ffqdj49zca4jm7mdf3eevq7uhcsgjvm0uk" $0.amount = CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" $0.denom = "ukava" } } @@ -113,7 +111,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } @@ -179,7 +177,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] $0.gas = 200000 @@ -235,7 +233,7 @@ class KavaTests: XCTestCase { $0.delegatorAddress = "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" $0.validatorAddress = "kavavaloper17498ffqdj49zca4jm7mdf3eevq7uhcsgjvm0uk" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "ukava" } } @@ -247,7 +245,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } @@ -307,7 +305,7 @@ class KavaTests: XCTestCase { $0.validatorSrcAddress = "kavavaloper17498ffqdj49zca4jm7mdf3eevq7uhcsgjvm0uk" $0.validatorDstAddress = "kavavaloper14fkp35j5nkvtztmxmsxh88jks6p3w8u7p76zs9" $0.amount = CosmosAmount.with { - $0.amount = 500000 + $0.amount = "500000" $0.denom = "ukava" } } @@ -319,7 +317,7 @@ class KavaTests: XCTestCase { let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 100 + $0.amount = "100" $0.denom = "ukava" }] } 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 new file mode 100644 index 00000000000..6faee34cac3 --- /dev/null +++ b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class KuCoinCommunityChainTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .kuCoinCommunityChain) + let addressFromString = AnyAddress(string: "0xE5cA667d795685E9915E5F4b4254ca832eEB398B", coin: .kuCoinCommunityChain)! + + XCTAssertEqual(pubkey.data.hexString, "0413bde18e3329af54d51a24f424fe09a8d7d42c324c07e10e53a6e139cbee80e6288142dec2ed46f7b81dccbb28d6168cdc7b208928730cbeeb911f8db6a707bb") + XCTAssertEqual(address.description, addressFromString.description) + } + +} 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 2e1aae98cc6..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 @@ -119,7 +117,7 @@ class LitecoinTests: XCTestCase { let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .litecoin) let utxos = [ BitcoinUnspentTransaction.with { - $0.outPoint.hash = Data(Data(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407")!.reversed()) + $0.outPoint.hash = Data.reverse(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407") $0.outPoint.index = 0 $0.outPoint.sequence = UINT32_MAX $0.script = lockScript.data 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..2084f8b551d --- /dev/null +++ b/swift/Tests/Blockchains/MultiversXTests.swift @@ -0,0 +1,272 @@ +// 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 testSignGenericActionWithRelayer() { + 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.relayer = carolBech32 + } + $0.value = "1000000000000000000" + $0.data = "" + $0.version = 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 = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003" + let expectedEncoded = #"{"nonce":42,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"\#(expectedSignature)","relayer":"\#(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..d96ad33e5ce 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 @@ -16,10 +14,11 @@ class NimiqTests: XCTestCase { $0.value = 42042042 $0.fee = 1000 $0.validityStartHeight = 314159 + $0.networkID = .mainnetAlbatross } let output: NimiqSigningOutput = AnySigner.sign(input: input, coin: .nimiq) - XCTAssertEqual(output.encoded.hexString, "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b") + XCTAssertEqual(output.encoded.hexString, "000070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f180ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405") } } 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 new file mode 100644 index 00000000000..45fc92bc3e8 --- /dev/null +++ b/swift/Tests/Blockchains/OsmosisTests.swift @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class OsmosisTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .osmosis) + let addressFromString = AnyAddress(string: "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", coin: .osmosis)! + + XCTAssertEqual(pubkey.data.hexString, "02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSigningTransaction() { + let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .osmosis) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn" + $0.amounts = [CosmosAmount.with { + $0.amount = "99800" + $0.denom = "uosmo" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "200" + $0.denom = "uosmo" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 124703 + $0.chainID = "osmosis-1" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + 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.errorMessage, "") + } +} diff --git a/swift/Tests/Blockchains/PactusTests.swift b/swift/Tests/Blockchains/PactusTests.swift new file mode 100644 index 00000000000..933bdeaebea --- /dev/null +++ b/swift/Tests/Blockchains/PactusTests.swift @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class PactusTests: XCTestCase { + var privateKey: PrivateKey! + + override func setUp() { + super.setUp() + privateKey = PrivateKey(data: Data(hexString: "4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6")!)! + } + + func testAddress() { + let pubkey = privateKey.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .pactus) + let addressFromString = AnyAddress(string: "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr", coin: .pactus)! + + XCTAssertEqual(pubkey.data.hexString, "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testTransferSign() { + // Successfully broadcasted transaction: + // https://pacviewer.com/transaction/1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f + let input = PactusSigningInput.with { + $0.privateKey = privateKey.data + $0.transaction = PactusTransactionMessage.with { + $0.lockTime = 2335524 + $0.fee = 10000000 + $0.memo = "wallet-core" + $0.transfer = PactusTransferPayload.with { + $0.sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr" + $0.receiver = "pc1r0g22ufzn8qtw0742dmfglnw73e260hep0k3yra" + $0.amount = 200000000 + } + } + } + + let output: PactusSigningOutput = AnySigner.sign(input: input, coin: .pactus) + + let expectedTransactionID = "1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f" + let expectedSignature = "4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9b49d33a0fc" + + "8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736693eda8506" + let expectedSignedData = "000124a3230080ade2040b77616c6c65742d636f726501037098338e0b680811" + + "9dfd4457ab806b9c2059b89b037a14ae24533816e7faaa6ed28fcdde8e55a7df" + + "218084af5f4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9" + + "b49d33a0fc8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736" + + "693eda850695794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560b" + + "b72145f4fa"; + XCTAssertEqual(output.transactionID.hexString, expectedTransactionID) + XCTAssertEqual(output.signature.hexString, expectedSignature) + XCTAssertEqual(output.signedTransactionData.hexString, expectedSignedData) + } + + func testBondWithPublicKeySign() { + // Successfully broadcasted transaction: + // https://pacviewer.com/transaction/d194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f + let input = PactusSigningInput.with { + $0.privateKey = privateKey.data + $0.transaction = PactusTransactionMessage.with { + $0.lockTime = 2339009 + $0.fee = 10000000 + $0.memo = "wallet-core" + $0.bond = PactusBondPayload.with { + $0.sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr" + $0.receiver = "pc1p9y5gmu9l002tt60wak9extgvwm69rq3a9ackrl" + $0.stake = 1000000000 + $0.publicKey = "public1pnz75msstqdrq5eguvcwanug0zauhqjw2cc4flmez3qethnp68y64ehc4k69amapj7x4na2uda0snqz4yxujgx3jsse4f64fgy7jkh0xauvhrc5ts09vfk48g85t0js66hvajm6xruemsvlxqv3xvkyur8v9v0mtn" + } + } + } + + let output: PactusSigningOutput = AnySigner.sign(input: input, coin: .pactus) + + let expectedTransactionID = "d194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f" + let expectedSignature = "0d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda55b4338328adac7" + + "9d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff65e39ce7006300" + let expectedSignedData = "0001c1b0230080ade2040b77616c6c65742d636f726502037098338e0b680811" + + "9dfd4457ab806b9c2059b89b0129288df0bf7bd4b5e9eeed8b932d0c76f45182" + + "3d6098bd4dc20b03460a651c661dd9f10f17797049cac62a9fef228832bbcc3a" + + "39355cdf15b68bddf432f1ab3eab8debe1300aa43724834650866a9d552827a5" + + "6bbcdde32e3c517079589b54e83d16f9435abb3b2de8c3e677067cc0644ccb13" + + "833b8094ebdc030d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda5" + + "5b4338328adac79d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff" + + "65e39ce700630095794161374b22c696dabb98e93f6ca9300b22f3b904921fbf" + + "560bb72145f4fa" + + XCTAssertEqual(output.transactionID.hexString, expectedTransactionID) + XCTAssertEqual(output.signature.hexString, expectedSignature) + XCTAssertEqual(output.signedTransactionData.hexString, expectedSignedData) + } + + func testBondWithoutPublicKeySign() { + // Successfully broadcasted transaction: + // https://pacviewer.com/transaction/f83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80 + let input = PactusSigningInput.with { + $0.privateKey = privateKey.data + $0.transaction = PactusTransactionMessage.with { + $0.lockTime = 2335580 + $0.fee = 10000000 + $0.memo = "wallet-core" + $0.bond = PactusBondPayload.with { + $0.sender = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr" + $0.receiver = "pc1p6taz5l2kq5ppnxv4agnqj48svnvsy797xpe6wd" + $0.stake = 1000000000 + } + } + } + + let output: PactusSigningOutput = AnySigner.sign(input: input, coin: .pactus) + + let expectedTransactionID = "f83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80" + let expectedSignature = "9e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d85c087a8748ff0" + + "715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc436aa58f9a8f00d" + let expectedSignedData = "00015ca3230080ade2040b77616c6c65742d636f726502037098338e0b680811" + + "9dfd4457ab806b9c2059b89b01d2fa2a7d560502199995ea260954f064d90278" + + "be008094ebdc039e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d" + + "85c087a8748ff0715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc4" + + "36aa58f9a8f00d95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf" + + "560bb72145f4fa"; + + XCTAssertEqual(output.transactionID.hexString, expectedTransactionID) + XCTAssertEqual(output.signature.hexString, expectedSignature) + XCTAssertEqual(output.signedTransactionData.hexString, expectedSignedData) + } +} diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index 636d216cdf0..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 @@ -10,7 +8,6 @@ import XCTest class PolkadotTests: XCTestCase { let genesisHash = Data(hexString: "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")! - let privateKeyThrow2 = Data(hexString: "0x70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f")! func testAddressValidation() { let polkadot = CoinType.polkadot @@ -45,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 { @@ -59,7 +56,7 @@ class PolkadotTests: XCTestCase { } let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) - // https://polkadot.subscan.io/extrinsic/0x72dd5b5a3e01e0f3e779c5fa39e53de05ee381b9138d24e2791a775a6d1ff679 + // https://polkadot.subscan.io/extrinsic/3910744-1 XCTAssertEqual(output.encoded.hexString, "410284008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0038ec4973ab9773dfcbf170b8d27d36d89b85c3145e038d68914de83cf1f7aca24af64c55ec51ba9f45c5a4d74a9917dee380e9171108921c3e5546e05be15206050104000500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402") } @@ -74,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 { @@ -85,33 +82,119 @@ class PolkadotTests: XCTestCase { } let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) - // https://polkadot.subscan.io/extrinsic/0x5ec2ec6633b4b6993d9cf889ef42c457a99676244dc361a9ae17935d331dc39a + // https://polkadot.subscan.io/extrinsic/985084-3 XCTAssertEqual(output.encoded.hexString, "3902848d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa00a559867d1304cc95bac7cfe5d1b2fd49aed9f43c25c7d29b9b01c1238fa1f6ffef34b9650e42325de41e20fd502af7b074c67a9ec858bd9a1ba6d4212e3e0d0f00000007008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0700e40b540200") } func testSigningBondAndNominate() { + // real key in 1p test + let key = HDWallet.test.getKeyForCoin(coin: .polkadot) + + let input = PolkadotSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = genesisHash + $0.nonce = 4 + $0.specVersion = 30 + $0.network = CoinType.polkadot.ss58Prefix + $0.transactionVersion = 7 + $0.privateKey = key.data + $0.stakingCall.bondAndNominate = PolkadotStaking.BondAndNominate.with { + $0.controller = "13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5" + $0.value = Data(hexString: "0x02540be400")! // 1 DOT + $0.rewardDestination = .stash + $0.nominators = [ + "1zugcavYA9yCuYwiEYeMHNJm9gXznYjNfXQjZsZukF1Mpow", // Zug Capital / 12 + "15oKi7HoBQbwwdQc47k71q4sJJWnu5opn1pqoGx4NAEYZSHs" // P2P Validator + ] + } + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + // https://polkadot.subscan.io/extrinsic/4955314-2 + XCTAssertEqual("0x" + output.encoded.hexString, "0x6103840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00a8b1f859d788f11a958e98b731358f89cf3fdd41a667ea992522e8d4f46915f4c03a1896f2ac54bdc5f16e2ce8a2a3bf233d02aad8192332afd2113ed6688e0d0010001a02080700007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b540201070508002c2a55b5ffdca266bd0207df97565b03255f70783ca1a349be5ed9f44589c36000d44533a4d21fd9d6f5d57c8cd05c61a6f23f9131cec8ae386b6b437db399ec3d") + } + + func testSigningBondExtra() { + // real key in 1p test + let key = HDWallet.test.getKeyForCoin(coin: .polkadot) + + let input = PolkadotSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = genesisHash + $0.nonce = 5 + $0.specVersion = 30 + $0.network = CoinType.polkadot.ss58Prefix + $0.transactionVersion = 7 + $0.privateKey = key.data + $0.stakingCall.bondExtra = PolkadotStaking.BondExtra.with { + $0.value = Data(hexString: "0x77359400")! // 0.2 DOT + } + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + // https://polkadot.subscan.io/extrinsic/4999416-1 + XCTAssertEqual("0x" + output.encoded.hexString, "0xb501840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00c8268c2dfd4074f41d225e12e62e5975ff8debf0f828d31ddbfed6f7593e067fb860298eb12f50294f7ba0f82795809c84fc5cce6fcb36cde4cb1c07edbbb60900140007010300943577") + } + + func testChillAndUnbond() { + // real key in 1p test + let key = PrivateKey(data: Data(hexString: "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")!)! + let input = PolkadotSigningInput.with { $0.genesisHash = genesisHash - $0.blockHash = Data(hexString: "0x3a886617f4bbd4fe2bbe7369acae4163ed0b19ffbf061083abc5e0836ad58f77")! + $0.blockHash = Data(hexString: "0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0")! + $0.era = PolkadotEra.with { + $0.blockNumber = 10541373 + $0.period = 64 + } $0.nonce = 6 - $0.specVersion = 27 - $0.network = .polkadot - $0.transactionVersion = 5 - $0.privateKey = privateKeyThrow2 + $0.specVersion = 9200 + $0.network = CoinType.polkadot.ss58Prefix + $0.transactionVersion = 12 + $0.privateKey = key.data + $0.stakingCall.chillAndUnbond = PolkadotStaking.ChillAndUnbond.with { + $0.value = Data(hexString: "0x1766444D00")! // 10.05 DOT + } + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + // 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 = 3856651 + $0.blockNumber = 3893613 $0.period = 64 } - $0.stakingCall.bondAndNominate = PolkadotStaking.BondAndNominate.with { - $0.controller = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2" - $0.value = Data(hexString: "0x77359400")! // 0.2 - $0.rewardDestination = .stash - $0.nominators = ["14xKzzU1ZYDnzFj7FgdtDAYSMJNARjDc2gNw4XAFDgr4uXgp", "1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"] + $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://polkadot.subscan.io/extrinsic/0xc7a016f961dbf35d58feea22694e7d79ac77175a8cc40cb017bb5e87d56142ce - XCTAssertEqual(output.encoded.hexString, "5103849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783007d549324f270eb5932b898ce5fc166c3f30942c96668f52d6cc86c7b61a8d65680cd0a979f1e0a43ef9418e6571edab6d9c391a1696abdf56db2af348862d50eb50018001a000807009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783030094357701070508aee72821ca00e62304e4f0d858122a65b87c8df4f0eae224ae064b951d39f610127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a") + // 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/PolymeshTests.swift b/swift/Tests/Blockchains/PolymeshTests.swift new file mode 100644 index 00000000000..bbd695266b7 --- /dev/null +++ b/swift/Tests/Blockchains/PolymeshTests.swift @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class PolymeshTests: XCTestCase { + let genesisHash = Data(hexString: "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063")! + // Private key for testing. DO NOT USE, since this is public. + let testKey1 = Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")! + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")!)! + let pubkey = key.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .polymesh) + let addressFromString = AnyAddress(string: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", coin: .polymesh)! + + XCTAssertEqual(pubkey.data.hexString, "4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSignTransfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let blockHash = Data(hexString: "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d")! + + let input = PolymeshSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = blockHash + $0.nonce = 1 + $0.specVersion = 7000005 + $0.network = CoinType.polymesh.ss58Prefix + $0.transactionVersion = 7 + $0.privateKey = testKey1 + $0.era = PolkadotEra.with { + $0.blockNumber = 16102106 + $0.period = 64 + } + $0.runtimeCall = PolymeshRuntimeCall.with { + $0.balanceCall.transfer = PolymeshBalance.Transfer.with { + $0.toAddress = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7" + $0.value = Data(hexString: "0x0F4240")! // 1.0 POLYX + } + } + } + let output: PolymeshSigningOutput = AnySigner.sign(input: input, coin: .polymesh) + + XCTAssertEqual(output.encoded.hexString, "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00") + } +} 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..d4f9cf22689 100644 --- a/swift/Tests/Blockchains/RippleTests.swift +++ b/swift/Tests/Blockchains/RippleTests.swift @@ -1,25 +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. import XCTest import WalletCore class RippleTests: XCTestCase { - - func testAddressValidation() { - let coin = CoinType.xrp - let string = "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh" - let xaddr = RippleXAddress(string: string) - - XCTAssertTrue(coin.validate(address: "rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF")) - XCTAssertTrue(coin.validate(address: "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh")) - XCTAssertEqual(xaddr?.description, string) - XCTAssertEqual(xaddr?.tag, 12345) - } - func testAddress() { let key = PrivateKey(data: Data(hexString: "9c3d42d0515f0406ed350ab2abf3eaf761f8907802469b64052ac17e2250ae13")!)! let pubkey = key.getPublicKeySecp256k1(compressed: true) @@ -29,25 +15,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 new file mode 100644 index 00000000000..fc8e102ca12 --- /dev/null +++ b/swift/Tests/Blockchains/RoninTests.swift @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class RoninTests: XCTestCase { + + func testTransferAXS() { + // real key is 1p + let keyData = Data(hexString: "0x4646464646464646464646464646464646464646464646464646464646464646")! + + // How to send free 0 gas tx: + // 1. Registered your address at https://marketplace.axieinfinity.com/ + // 2. Query free quota by rpc eth_getFreeGasRequests + // 3. Send tx to proxy node + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "0x07e4")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() + $0.gasLimit = Data(hexString: "0xca98")! // 51,864 + $0.toAddress = "ronin:97a9107c1793bc407d6f527b77e7fff4d812bece" // AXS + $0.privateKey = keyData + $0.transaction = EthereumTransaction.with { + $0.erc20Transfer = EthereumTransaction.ERC20Transfer.with { + $0.to = "ronin:47331175b23c2f067204b506ca1501c26731c990" + $0.amount = Data(hexString: "0x016345785d8a0000")! // 0.1 AXS + } + } + } + + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ronin) + + // https://explorer.roninchain.com/tx/0x021e77da4ce4cfab90c4522ab8a5affddbd81dda819543f42eac3aa7102ea5bc + XCTAssertEqual("0x" + output.encoded.hexString, "0xf8a6028082ca989497a9107c1793bc407d6f527b77e7fff4d812bece80b844a9059cbb00000000000000000000000047331175b23c2f067204b506ca1501c26731c990000000000000000000000000000000000000000000000000016345785d8a0000820feba0de07884db1de6db69cb3d3612f7974d9078cc86fd23103de0414be7684967708a071119e5d151f8616e004e7e8e8aed0e078a019d3f84cde7d08a94f62dee99f59") + } +} 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 new file mode 100644 index 00000000000..5385549a3b8 --- /dev/null +++ b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class SmartBitcoinCashTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .smartBitcoinCash) + let addressFromString = AnyAddress(string: "0x8bFC9477684987dcAf0970b9bce5E3D9267C99C0", coin: .smartBitcoinCash)! + + 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 67c3d836cc0..e84ced805eb 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 @@ -54,12 +52,13 @@ class SolanaTests: XCTestCase { } func testDelegateStakeSigner() throws { - let delegateStakeMessage = SolanaStake.with { + let delegateStakeMessage = SolanaDelegateStake.with { $0.validatorPubkey = "4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC" $0.value = 42 + $0.stakeAccount = "" } let input = SolanaSigningInput.with { - $0.stakeTransaction = delegateStakeMessage + $0.delegateStakeTransaction = delegateStakeMessage $0.recentBlockhash = "11111111111111111111111111111111" $0.privateKey = Data(Base58.decodeNoCheck( string: "AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746")!) } @@ -67,16 +66,16 @@ class SolanaTests: XCTestCase { let output: SolanaSigningOutput = AnySigner.sign(input: input, coin: .solana) let expectedString = """ - W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7\ - uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQP\ - ouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy\ - 7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xy\ - ATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGv\ - wfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWu\ - kgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgzU285SkncqNSgxkpZVhmenTXpuZv74\ - rXzariX8P4sprRgKUoj4b7Nu72Pya1zr7k45isMwgxtLnnnTK5k7mrZRDw3jBSBuukJBja93zaidm8HCQdwQsBt5CN\ - SgSXug1R2t6Sdm5tjJrsd1gyRv7udFbHCdbVEeatzULNSSGdwjwwJDy1DTC12ddBNHd8k5ic5TDwrWdfCxbDRoFYw8\ - 49YNNUuyNAPz1jDCkLG9af6KFFLxfuR9pnF8jSyTcQAq95YiiD9sC3mAUoe8AkYfy929XzTEatP1vasMvo + j24mVM9Zgu5vDZhPLGGuCRXQnP9djNtxdHh4txN3S7dwJsNNL5fbhzGpPgSUAcLGoMVCfF9TuqTYfpfJnb4sJFe1ah\ + M8yPL5HwuKL6py5AZJFi8SWx9fvaVB699dCPo1GT3JoEBLPCZ9o2jQtnwzLkzTYJnKv2axqhKWFE2sz6TBA5J39eZc\ + jMFUYgyxz6Q5S4MWqYQCb8UET2NAEZoKcfy7j8N25WXL6Gj4j3hBZjpHQQNaGaNEprEqyma3ZuVhpGiCALSsuzVLX3\ + wZVo4icXwe952deMFA4tH3BK1jcSQCgfmcKDJ9nd7bdrnUUs4BoMdF1uDZB5LxE2UH8QiqtYvaUcorF4SJ3gPxM5yk\ + byPsNK1cSYZF9NMpW2GofyC17eELwnHQTQB2kqphxJZu7BahvkwiDPPeeydiXAkBspJ3nc3PCBujv6WJw22ZHw5j6z\ + AP8ZGnCW44pqtWD5qifF9tTKhySKdANNiWifs3tSCCPQqjfJXu14drNinR6VG8rJxS1qgmRYiRQUa7m1vtoaZFRN5q\ + KUeAfoFKkAVaNnMdwgsNqNH4dqBodTCJFs1LkYwhgRZdZGbwXTn1j7vpR3DSnv4g72i2H556srzK53jdUmdv6yfxt5\ + 16XDSshqZtHnKZ1tudxKjBXwsqT3imDiZFVka9wKWUAYMCi4XZ79CY6Xpsd9c18U2e9TCngQmgkTATFgrqysfraokN\ + ffgqWxvsPMugksbvbPjJs3iCzByvphkC9p7hCf6LwbeF8XnVB91EAgRDA4VLE1f9wkcq5zjy879YWJ4r516h3PQszT\ + z1EaJXNAXdbk5Em7eyuuabGP1Q3nijFTL2yhMDsXpgrjAuEAABNxFMd4J1JRMaic615mHrhwociksrsfQK """ XCTAssertEqual(output.encoded, expectedString) @@ -84,7 +83,7 @@ class SolanaTests: XCTestCase { func testDeactivateStakeSigner() throws { let deactivateStakeMessage = SolanaDeactivateStake.with { - $0.validatorPubkey = "4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC" + $0.stakeAccount = "6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv" } let input = SolanaSigningInput.with { $0.deactivateStakeTransaction = deactivateStakeMessage @@ -106,7 +105,7 @@ class SolanaTests: XCTestCase { func testWithdrawStakeSigner() throws { let withdrawMessage = SolanaWithdrawStake.with { - $0.validatorPubkey = "4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC" + $0.stakeAccount = "6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv" $0.value = 42 } let input = SolanaSigningInput.with { @@ -118,11 +117,11 @@ class SolanaTests: XCTestCase { let output: SolanaSigningOutput = AnySigner.sign(input: input, coin: .solana) let expectedString = """ - 7Y1Wg1yHNs8MgWFiFSfcsRtqdMwZg8oGeQnTABYDfyDnof4VSFw63s3PuSxvUCJqqHKgYNVb8UTNcNiYHY8kng4NqT\ - cVV5SA1KAWRzKHVGUxNWioAEXXVot5iJ1XbUWuuZUZBtsraaBjNyfmgWEDje3ESdGhiVL7vadU1uHeBuUKwM3nqB6y\ - oeggeNyzmT34hs9utyehTFg48MAfrKEFKxaby7YZD6JbXFS1SyG1kxKWnCpoPgX3efwDwukmyDwxrKdABt9eTwmaiX\ - KbTnK1hzBTatNfnJ9ePuWkhWFrjyDrGdx5S5KpybxET2vV9CSpExcD51BA6NPemTpjbhLYnJEzHWBGfYqfxu7p3257\ - NHhpQQrSU56adk4dAQFjEYP + NL7WgagucfLd6AkTtcKe1dqd47xxzF356Q7tEhPrz1LRzZiAmokAaUkpwJ7X71Pmz97zZf9gZQU5BNswdcdpqUL8n1\ + jwn4CoZMaPJhX5LF43Sj817cgreSG14TEWfKertpVpTtc5zY7vkDM7t9wjYhkaqgYz76HQtqAqRHnHF2Qr9EEfLj4z\ + YRerWtyfS3EVyVUaasPxJ5vkcaonEfpGc6uWecaFr2A3YbzEBQpWXjMaXLqmMDtNS8rTNZmwvToa71ddFZKDgaHDcc\ + 6Lkg8qriZ3aQbUqL1TbeYp2mk9dWTKY62L1YFE2DyZV5P2qz5feywcMZ9JW6X1wBmiHFCseC42QbnbTibr1VdqLbGx\ + 7UWn5tHWk5jCN2aatEPfbFDZ """ XCTAssertEqual(output.encoded, expectedString) @@ -217,4 +216,178 @@ class SolanaTests: XCTestCase { let defaultAddress = mainAddress.defaultTokenAddress(tokenMintAddress: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt") XCTAssertEqual(defaultAddress, "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP") } + + func testSignJSON() { + let json = """ + {"recentBlockhash":"11111111111111111111111111111111","transferTransaction":{"recipient":"EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd","value":"42"}} + """ + let key = Data(hexString: "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63")! + let result = AnySigner.signJSON(json, key: key, coin: .solana) + + 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) + } + + func testSetPriorityFee() throws { + let privateKey = Data(hexString: "baf2b2dbbbad7ca96c1fa199c686f3d8fbd2c7b352f307e37e04f33df6741f18")! + let originalTx = "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAEDODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG6GdPcA92ORzVJe2jfG8KQqqMHr9YTLu30oM4i7MFEoBAgIAAQwCAAAA6AMAAAAAAAA=" + + // Step 1 - Check if there are no price and limit instructions in the original transaction. + XCTAssertEqual(SolanaTransaction.getComputeUnitPrice(encodedTx: originalTx), nil) + XCTAssertEqual(SolanaTransaction.getComputeUnitLimit(encodedTx: originalTx), nil) + + // Step 2 - Set price and limit instructions. + let txWithPrice = SolanaTransaction.setComputeUnitPrice(encodedTx: originalTx, price: "1000")! + let updatedTx = SolanaTransaction.setComputeUnitLimit(encodedTx: txWithPrice, limit: "10000")! + + XCTAssertEqual(updatedTx, "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA") + + // Step 3 - Check if price and limit instructions are set successfully. + XCTAssertEqual(SolanaTransaction.getComputeUnitPrice(encodedTx: updatedTx), "1000") + XCTAssertEqual(SolanaTransaction.getComputeUnitLimit(encodedTx: updatedTx), "10000") + + // Step 4 - Decode transaction into a `RawMessage` Protobuf. + let updatedTxData = Base64.decode(string: updatedTx)! + let decodeOutputData = TransactionDecoder.decode(coinType: .solana, encodedTx: updatedTxData) + var decodeOutput = try SolanaDecodingTransactionOutput(serializedData: decodeOutputData) + + XCTAssertEqual(decodeOutput.error, .ok) + + // Step 5 - Sign the decoded `RawMessage` transaction. + let signingInput = SolanaSigningInput.with { + $0.privateKey = privateKey + $0.rawMessage = decodeOutput.transaction + $0.txEncoding = .base64 + } + + let output: SolanaSigningOutput = AnySigner.sign(input: signingInput, coin: .solana) + XCTAssertEqual(output.error, .ok) + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/2ho7wZUXbDNz12xGfsXg2kcNMqkBAQjv7YNXNcVcuCmbC4p9FZe9ELeM2gMjq9MKQPpmE3nBW5pbdgwVCfNLr1h8 + XCTAssertEqual(output.encoded, "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA") + } + + func testSetFeePayer() throws { + // base64 encoded + let originalTx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA==" + + // Step 1 - Add fee payer to the transaction. + let updatedTx = SolanaTransaction.setFeePayer(encodedTx: originalTx, feePayer: "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ")! + + XCTAssertEqual(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==") + + // Step 2 - Decode transaction into a `RawMessage` Protobuf. + let updatedTxData = Base64.decode(string: updatedTx)! + let decodeOutputData = TransactionDecoder.decode(coinType: .solana, encodedTx: updatedTxData) + let decodeOutput = try SolanaDecodingTransactionOutput(serializedData: decodeOutputData) + + XCTAssertEqual(decodeOutput.error, .ok) + + // Step 3 - Obtain preimage hash. + let signingInput = SolanaSigningInput.with { + $0.rawMessage = decodeOutput.transaction + $0.txEncoding = .base64 + } + let txInputData = try signingInput.serializedData() // Serialize input + let preImageHashes = TransactionCompiler.preImageHashes(coinType: .solana, txInputData: txInputData) + let preSigningOutput: SolanaPreSigningOutput = try SolanaPreSigningOutput(serializedData: preImageHashes) + XCTAssertEqual(preSigningOutput.error, CommonSigningError.ok) + XCTAssertEqual(preSigningOutput.data.hexString, "8002000104cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b576b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea64d77772adc14c8915f46cd8f05f7905bcc42119bcdaffe49fd3c7c96d6e7d29c00000000000000000000000000000000000000000000000000000000000000002a3e4116ef5d634aa0e7da38be1c4a97d8ae69ffd9357e74199cb7e1ec9a6c1d01030201020c02000000009c9f060000000000") + + // Step 4 - Compile transaction info. + // Simulate signature, normally obtained from signature server. + let signatureVec = DataVector() + let pubkeyVec = DataVector() + let feePayerSignature = Data(hexString: "feb9f15cc345fa156450676100033860edbe80a6f61dab8199e94fdc47678ecfdb95e3bc10ec0a7f863ab8ef5c38edae72db7e5d72855db225fd935fd59b700a")! + let feePayerPublicKey = Data(hexString: "cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b57")! + signatureVec.add(data: feePayerSignature) + pubkeyVec.add(data: feePayerPublicKey) + let solSenderSignature = Data(hexString: "936cd6d176e701d1f748031925b2f029f6f1ab4b99aec76e24ccf05649ec269569a08ec0bd80f5fee1cb8d13ecd420bf50c5f64ae74c7afa267458cabb4e5804")! + let solSenderPublicKey = Data(hexString: "6b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea6")! + signatureVec.add(data: solSenderSignature) + pubkeyVec.add(data: solSenderPublicKey) + + let compileWithSignatures = TransactionCompiler.compileWithSignatures(coinType: .solana, txInputData: txInputData, signatures: signatureVec, publicKeys: pubkeyVec) + let expectedTx = "Av658VzDRfoVZFBnYQADOGDtvoCm9h2rgZnpT9xHZ47P25XjvBDsCn+GOrjvXDjtrnLbfl1yhV2yJf2TX9WbcAqTbNbRducB0fdIAxklsvAp9vGrS5mux24kzPBWSewmlWmgjsC9gPX+4cuNE+zUIL9QxfZK50x6+iZ0WMq7TlgEgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==" + let output: SolanaSigningOutput = try SolanaSigningOutput(serializedData: compileWithSignatures) + XCTAssertEqual(output.encoded, expectedTx) + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/66PAVjxFVGP4ctrkXmyNRhp6BdFT7gDe1k356DZzCRaBDTmJZF1ewGsbujWRjDTrt5utnz8oHZw3mg8qBNyct41w?cluster=devnet + } + + func testSignUserMessage() throws { + let privateKey = Data(hexString: "44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d")! + + let input = SolanaMessageSigningInput.with { + $0.privateKey = privateKey + $0.message = "Hello world" + } + let outputData = MessageSigner.sign(coin: .solana, input: try input.serializedData())! + let output = try SolanaMessageSigningOutput(serializedData: outputData) + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.signature, "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG") + } } 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 new file mode 100644 index 00000000000..b13699a857b --- /dev/null +++ b/swift/Tests/Blockchains/THORChainSwapTests.swift @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class THORSwapTests: XCTestCase { + + func testSignerEthBnbWithFee() throws { + // prepare swap input + let input = THORChainSwapSwapInput.with { + $0.fromAsset = THORChainSwapAsset.with { + $0.chain = .eth + } + $0.fromAddress = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7" + $0.toAsset = THORChainSwapAsset.with { + $0.chain = .bnb + $0.symbol = "BNB" + $0.tokenID = "" + } + $0.toAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" + $0.vaultAddress = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC" + $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, "0a020802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a11353030303030303030303030303030303042063630303030334a2f7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c327463717952023130") + + // invoke swap + let outputData = THORChainSwap.buildSwap(input: inputSerialized) + XCTAssertEqual(outputData.count, 192) + + // parse result in proto + let outputProto = try THORChainSwapSwapOutput(serializedData: outputData) + XCTAssertEqual(outputProto.fromChain, TW_THORChainSwap_Proto_Chain.eth) + XCTAssertEqual(outputProto.toChain, TW_THORChainSwap_Proto_Chain.bnb) + XCTAssertEqual(outputProto.error.code, TW_THORChainSwap_Proto_ErrorCode.ok) + var txInput = outputProto.ethereum + + // set few fields before signing + txInput.chainID = Data(hexString: "01")! + txInput.nonce = Data(hexString: "03")! + txInput.gasPrice = Data(hexString: "06FC23AC00")! + txInput.gasLimit = Data(hexString: "013880")! + txInput.privateKey = Data(hexString: "4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")! + + // sign and encode resulting input + let output: EthereumSigningOutput = AnySigner.sign(input: txInput, coin: .ethereum) + + XCTAssertEqual(output.encoded.hexString, "02f8d90103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b86e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030333a7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c32746371793a3130c001a05c16871b66fd0fa8f658d6f171310bab332d09e0533d6c97329a59ddc93a9a11a05ed2be94e6dbb640e58920c8be4fa597cd5f0a918123245acb899042dd43777f") + } + + func testSignerBnbBtc() throws { + // prepare swap input + let input = THORChainSwapSwapInput.with { + $0.fromAsset = THORChainSwapAsset.with { + $0.chain = .bnb + } + $0.fromAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" + $0.toAsset = THORChainSwapAsset.with { + $0.chain = .btc + $0.symbol = "BTC" + $0.tokenID = "" + } + $0.toAddress = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8" + $0.vaultAddress = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse" + $0.routerAddress = "" + $0.fromAmount = "10000000" + $0.toAmountLimit = "10000000" + } + + // serialize input + let inputSerialized = try input.serializedData() + XCTAssertEqual(inputSerialized.hexString, "0a020803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030") + + // invoke swap + let outputData = THORChainSwap.buildSwap(input: inputSerialized) + XCTAssertEqual(outputData.count, 146) + + // parse result in proto + let outputProto = try THORChainSwapSwapOutput(serializedData: outputData) + XCTAssertEqual(outputProto.fromChain, TW_THORChainSwap_Proto_Chain.bnb) + XCTAssertEqual(outputProto.toChain, TW_THORChainSwap_Proto_Chain.btc) + XCTAssertEqual(outputProto.error.code, TW_THORChainSwap_Proto_ErrorCode.ok) + var txInput = outputProto.binance + + // set few fields before signing + + txInput.chainID = "Binance-Chain-Nile" + txInput.privateKey = Data(hexString: "bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da")! + + // sign and encode resulting input + let output: BinanceSigningOutput = AnySigner.sign(input: txInput, coin: .binance) + + XCTAssertEqual(output.encoded.hexString, "fd01f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e19124008455a84c90e73981ae098578d2ab2b498fe17b0436723c596501b9236d96697514467ed4c22ba8f1cb7506172b368a2ca8be0eb82cb93b5320f938209041f2c1a3d3d3a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030") + } +} diff --git a/swift/Tests/Blockchains/THORChainTests.swift b/swift/Tests/Blockchains/THORChainTests.swift new file mode 100644 index 00000000000..a0ae0db1d45 --- /dev/null +++ b/swift/Tests/Blockchains/THORChainTests.swift @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class THORChainAddressTests: XCTestCase { + func testAddressValidation() { + let thorchain = CoinType.thorchain + for address in [ + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + ] { + XCTAssertTrue(thorchain.validate(address: address)) + XCTAssertEqual(thorchain.address(string: address)?.description, address) + } + } +} + +class THORChainSignerTests: XCTestCase { + + let privateKey = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!)! + + func testJsonModeSigning() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .thorchain) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" + $0.amounts = [CosmosAmount.with { + $0.amount = "10000000" + $0.denom = "rune" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "2000000" + $0.denom = "rune" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 593 + $0.chainID = "thorchain" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .thorchain) + + let expectedJSON: String = """ + { + "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": "ZPhcYubhAd6iz/pBrtLfSJaK04ISnEo+jBFvFFzoToMJA9NGhhCFmsmXMQ1AtoJ6C1aylvUnck93A7ork8ZzEQ==" + } + ] + } + } + """ + + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testProtobufModeSigning() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .thorchain) + + let sendCoinsMessage = CosmosMessage.THORChainSend.with { + $0.fromAddress = fromAddress.data + $0.toAddress = AnyAddress(string: "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", coin: .thorchain)!.data + $0.amounts = [CosmosAmount.with { + $0.amount = "38000000" + $0.denom = "rune" + }] + } + + let message = CosmosMessage.with { + $0.thorchainSendMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 2500000 + $0.amounts = [CosmosAmount.with { + $0.amount = "200" + $0.denom = "rune" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 593 + $0.chainID = "thorchain-mainnet-v1" + $0.sequence = 21 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + $0.signingMode = .protobuf + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .thorchain) + let expectedJSON = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=" + } + """ + + XCTAssertJSONEqual(expectedJSON, output.serialized) + } +} diff --git a/swift/Tests/Blockchains/TONTests.swift b/swift/Tests/Blockchains/TONTests.swift deleted file mode 100644 index 6528e5fee2d..00000000000 --- a/swift/Tests/Blockchains/TONTests.swift +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 TONTests: XCTestCase { - func testAddress() { - let pubkey = PublicKey(data: Data(hexString: "F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3")!, type: PublicKeyType.ed25519)! - let address = AnyAddress(publicKey: pubkey, coin: .ton).description - let addressFromString = AnyAddress(string: "EQAkAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHUx3Y", coin: .ton)! - - XCTAssertEqual(pubkey.data.hexString, "f61cf0bc8e891ad7636e0cd35229d579323aa2da827eb85d8071407464dc2fa3") - XCTAssertEqual(address.description, addressFromString.description) - } -} 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 daa53853b13..af8b1d0e593 100644 --- a/swift/Tests/Blockchains/TerraTests.swift +++ b/swift/Tests/Blockchains/TerraTests.swift @@ -1,186 +1,130 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms 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 + func testSignSendTx() { + let publicKey = privateKey80e8.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terraV2).description + XCTAssertEqual(fromAddress, "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2") 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" - } - """ + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress + $0.toAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + $0.amounts = [CosmosAmount.with { + $0.amount = "1000000" + $0.denom = "uluna" + }] } } 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 testSigningTransaction() { - // https://finder.terra.money/soju-0013/tx/1403B07F2D218BCE961CB92D83377A924FEDB54C1F0B62E25C8B93B63470EBF7 - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + 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.sendCoinsMessage = CosmosMessage.Send.with { - $0.fromAddress = fromAddress - $0.toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" - $0.amounts = [CosmosAmount.with { - $0.amount = 1000000 - $0.denom = "uluna" - }] - $0.typePrefix = "bank/MsgSend" - } + $0.wasmExecuteContractTransferMessage = wasmTransferMessage } let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = 3000 + $0.amount = "3000" $0.denom = "uluna" }] } 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 = 0 + $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 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) + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + ) + XCTAssertEqual(output.errorMessage, "") } - func testStaking() { - // https://finder.terra.money/soju-0013/tx/4C0A6690ECB601ACB42D3ECAF4C24C0555B5E32E45B09C3B1607B144CD191F87 + func testSignStakeTx() throws { + let stakeMessage = CosmosMessage.Delegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.delegatorAddress = "terra1ncfyexz3nrrdru37ahqpp4wen48v7p5nany478" + $0.validatorAddress = "terravaloper1ekq8xuypdxtf3nfmffmydnhny59pjuy0p8wpn7" $0.amount = CosmosAmount.with { - $0.amount = 1000000 + $0.amount = "1000000" // 5 Luna $0.denom = "uluna" } - $0.typePrefix = "staking/MsgDelegate" } let message = CosmosMessage.with { @@ -188,268 +132,84 @@ class TerraTests: XCTestCase { } 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 = 1 + $0.signingMode = .protobuf; + $0.accountNumber = 127185 + $0.chainID = "phoenix-1" + $0.sequence = 6 $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" + $0.privateKey = privateKey1037.data // real key is terra: define... } - let message = CosmosMessage.with { - $0.withdrawStakeRewardMessage = withdrawMessage + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) + let expected = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "Cp8BCpwBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJ1Cix0ZXJyYTFuY2Z5ZXh6M25ycmRydTM3YWhxcHA0d2VuNDh2N3A1bmFueTQ3OBIzdGVycmF2YWxvcGVyMWVrcTh4dXlwZHh0ZjNuZm1mZm15ZG5obnk1OXBqdXkwcDh3cG43GhAKBXVsdW5hEgcxMDAwMDAwEmgKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNd8YVWZSHWp4AjGe4G4aKOl7d3Lftf3RPKbwV1UYlo5BIECgIIARgGEhQKDgoFdWx1bmESBTM4MjAzENrFDxpAamKyAvWhWCv0nUKz7yiYETpkZETflDvfe1vmuFIy31g+s0u1cgLNo+7jBRXRuzYJXukigtwoLUrxY/C8rowiJw==" } + """ - let fee = CosmosFee.with { - $0.amounts = [CosmosAmount.with { - $0.amount = 3000 - $0.denom = "uluna" - }] - $0.gas = 200000 - } + // https://finder.terra.money/mainnet/tx/4441c65f24783b8f59b20b1b80ee43f1f4f6ff827597d87b6bbc94982b45be0c + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.errorMessage, "") + } - 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 - } + func testSignClaimRewards() throws { + let delegator = "terra1ncfyexz3nrrdru37ahqpp4wen48v7p5nany478" + let validators = [ + "terravaloper1ekq8xuypdxtf3nfmffmydnhny59pjuy0p8wpn7" + ] - 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" + let withdrawals = validators.map { validator in + CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = delegator + $0.validatorAddress = validator } - }], - "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" + }.map { withdraw in + CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdraw } - $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.amount = "29513" $0.denom = "uluna" }] + $0.gas = 196749 } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 3 - $0.messages = [message] + $0.signingMode = .protobuf; $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 + $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 fee = CosmosFee.with { - $0.gas = 200000 - $0.amounts = [CosmosAmount.with { - $0.amount = 3000 - $0.denom = "uluna" - }] - } + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) - 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 expected = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "CqEBCp4BCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmMKLHRlcnJhMW5jZnlleHozbnJyZHJ1MzdhaHFwcDR3ZW40OHY3cDVuYW55NDc4EjN0ZXJyYXZhbG9wZXIxZWtxOHh1eXBkeHRmM25mbWZmbXlkbmhueTU5cGp1eTBwOHdwbjcSaApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjkEgQKAggBGAUSFAoOCgV1bHVuYRIFMjk1MTMQjYEMGkA/bh2va6RRZvkSLnej84dJgSSvbgcHgYDbkRt8wDge03W747BZcuBcg/U5EuE7zBqSJrKUTZl7oUCp//rYlJKV" } + """ - 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/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..841d79e54dd --- /dev/null +++ b/swift/Tests/Blockchains/TheOpenNetworkTests.swift @@ -0,0 +1,181 @@ +// 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 = Data(hexString: "0A")! + $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 = Data(hexString: "1DCD6500")! //500 * 1000 * 1000 + $0.toOwner = "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8" + $0.responseAddress = "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk" + $0.forwardAmount = Data(hexString: "01")!; + } + + let transfer = TheOpenNetworkTransfer.with { + $0.dest = "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja" + $0.amount = Data(hexString: "05F5E100")! //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 transfer = TheOpenNetworkTransfer.with { + $0.dest = dogeChatbotDeployingAddress + // 0.069 TON + $0.amount = Data(hexString: "041CDB40")! //69_000_000 + $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) + $0.bounceable = false + $0.stateInit = dogeChatbotStateInit + $0.customPayload = commentPayload + } + + 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..67ae3f91242 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, "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c") + 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..6d201b53ce1 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,67 @@ class ZcashTests: XCTestCase { XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) XCTAssertEqual(output.encoded.hexString, "0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000") } + + func testSignV2() throws { + // Successfully broadcasted: https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 + let privateKeyData = Data(hexString: "a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559")! + let dustAmount = 546 as Int64 + let txId = Data.reverse(hexString: "3a19dd44032dfed61bfca5ba5751aab8a107b30609cbd5d70dc5ef09885b6853") + let sapplingBranchId = Data(hexString: "bb09b876")! + + let privateKey = PrivateKey(data: privateKeyData)! + + let utxo0 = BitcoinV2Input.with { + $0.outPoint = UtxoOutPoint.with { + $0.hash = txId + $0.vout = 0 + } + $0.value = 494_000 + $0.sighashType = BitcoinSigHashType.all.rawValue + $0.receiverAddress = "t1gWVE2uyrET2CxSmCaBiKzmWxQdHhnvMSz" + } + + let out0 = BitcoinV2Output.with { + $0.value = 488_000 + $0.toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS" + } + + let signingInput = BitcoinV2SigningInput.with { + $0.builder = BitcoinV2TransactionBuilder.with { + $0.version = .useDefault + $0.inputs = [utxo0] + $0.outputs = [out0] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + $0.zcashExtraData = ZcashTransactionBuilderExtraData.with { + $0.branchID = sapplingBranchId + } + } + $0.privateKeys = [privateKeyData] + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 184 + $0.p2ShPrefix = 189 + } + } + + let legacySigningInput = BitcoinSigningInput.with { + $0.signingV2 = signingInput + $0.coinType = CoinType.zcash.rawValue + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: legacySigningInput, coin: .zcash) + XCTAssertEqual(output.error, .ok) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.error, .ok) + XCTAssertEqual(outputV2.encoded.hexString, "0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000"); + XCTAssertEqual(outputV2.txid.hexString, "ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256") + } + + 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 0aabb50b9a2..42b20607823 100644 --- a/swift/Tests/Blockchains/ZcoinTests.swift +++ b/swift/Tests/Blockchains/ZcoinTests.swift @@ -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. import XCTest import WalletCore class ZcoinTests: XCTestCase { - let zcoin = CoinType.zcoin + let coin = CoinType.firo func testValidAddresses() { - XCTAssertTrue(zcoin.validate(address: "a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3")) - XCTAssertTrue(zcoin.validate(address: "4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh")) + XCTAssertTrue(coin.validate(address: "a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3")) + XCTAssertTrue(coin.validate(address: "4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh")) } func testInvalidAddresses() { @@ -23,7 +21,7 @@ class ZcoinTests: XCTestCase { "Xm1iDLBP5tdxTxc6t7uJBCVjC4L2A5vB2J", "TKjdnbJxP4yHeLTHZ86DGnFFY6QhTjuBv2", ] { - XCTAssertFalse(zcoin.validate(address: addr)) + XCTAssertFalse(coin.validate(address: addr)) } } } 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 1be12c4ea9a..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 @@ -89,7 +87,6 @@ class ZilliqaTests: XCTestCase { ] ] let jsonData = try JSONSerialization.data(withJSONObject: json, options: [.sortedKeys]) - print(String(data: jsonData, encoding: .utf8)!) let input = ZilliqaSigningInput.with { $0.version = 65537 // mainnet tx version @@ -114,6 +111,15 @@ class ZilliqaTests: XCTestCase { let expected = try String(contentsOf: url) XCTAssertJSONEqual(expected, output.json) - print(output.json) + } + + func testSignJSON() { + let json = """ + {"version":65537,"nonce":"2","to":"zil10lx2eurx5hexaca0lshdr75czr025cevqu83uz","gasPrice":"O5rKAA==","gasLimit":"1","transaction":{"transfer":{"amount":"6NSlEAA="}}} + """ + let key = Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")! + let result = AnySigner.signJSON(json, key: key, coin: .zilliqa) + + XCTAssertEqual(result, "7b22616d6f756e74223a2231303030303030303030303030222c22636f6465223a22222c2264617461223a22222c226761734c696d6974223a2231222c226761735072696365223a2231303030303030303030222c226e6f6e6365223a322c227075624b6579223a22303366623330623139366365336539373635393365636332646132323064636139636465613863383464323337333737303034326139333062383932616330663563222c227369676e6174757265223a223030316661346466303863313161346137396539366536393339396565343865656563633738323331613738623033353561386361373833633737633133393433366533373933346665636332323532656438646163303065323335653232643138343130343631666238393636383563343237303634323733386564323638222c22746f41646472223a2237464363614366303636613546323645653341466663324544314641393831304465616136333243222c2276657273696f6e223a36353533377d") } } diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 3f54b789493..fa1a04747fe 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 @@ -10,7 +8,7 @@ import WalletCore class CoinAddressDerivationTests: XCTestCase { func testDerive() { - let wallet = HDWallet(mnemonic: "shoot island position soft burden budget tooth cruel issue economy destroy above", passphrase: "") + let wallet = HDWallet(mnemonic: "shoot island position soft burden budget tooth cruel issue economy destroy above", passphrase: "")! for _ in 0..<4 { for coin in CoinType.allCases { @@ -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) @@ -47,7 +54,7 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cardano: - let expectedResult = "addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk" + let expectedResult = "addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cosmos: let expectedResult = "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn" @@ -64,18 +71,67 @@ 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: + case .ethereum, + .smartChain, + .polygon, + .optimism, + .zksync, + .polygonzkEVM, + .scroll, + .arbitrum, + .arbitrumNova, + .ecochain, + .avalancheCChain, + .xdai, + .fantom, + .celo, + .cronosChain, + .smartBitcoinCash, + .kuCoinCommunityChain, + .boba, + .metis, + .aurora, + .evmos, + .moonriver, + .moonbeam, + .kavaEvm, + .kaia, + .meter, + .okxchain, + .confluxeSpace, + .opBNB, + .acalaEVM, + .neon, + .base, + .linea, + .greenfield, + .mantle, + .zenEON, + .mantaPacific, + .zetaEVM, + .merlin, + .lightlink, + .blast, + .bounceBit, + .zkLinkNova, + .sonic: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ronin: + let expectedResult = "ronin:8f348F300873Fd5DA36950B2aC75a26584584feE" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) 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) @@ -97,6 +153,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) @@ -115,6 +174,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) @@ -136,9 +198,15 @@ 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) + case .polymesh: + let expectedResult = "2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .qtum: let expectedResult = "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -151,24 +219,23 @@ 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 .ton: - let expectedResult = "EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo" - assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tron: let expectedResult = "TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -178,6 +245,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) @@ -190,7 +260,13 @@ class CoinAddressDerivationTests: XCTestCase { case .zcash: let expectedResult = "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .zcoin: + 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) case .zelcash: @@ -203,7 +279,133 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .oasis: - let expectedResult = "oasis1qr2wymrk4mmt4kyjg3rzkn6jsxku3kk6p5jvrvxz" + let expectedResult = "oasis1qzcpavvmuw280dk0kd4lxjhtpf0u3ll27yf7sqps" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .thorchain: + let expectedResult = "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bluzelle: + let expectedResult = "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .cryptoOrg: + let expectedResult = "cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .osmosis: + let expectedResult = "osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + 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) + case .pactus: + let expectedResult = "pc1r7ys2g5a4xc2qtm0t4q987m4mvs57w5g0v4pvzg" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() diff --git a/swift/Tests/CoinTypeTests.swift b/swift/Tests/CoinTypeTests.swift index 4878ebed2b2..9a6120fd503 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,9 +21,40 @@ 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) + XCTAssertEqual(CoinType.pactus.rawValue, 21888) + } + + 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.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinTaproot), "m/86'/0'/0'/0/0") + XCTAssertEqual(CoinType.solana.derivationPathWithDerivation(derivation: Derivation.solanaSolana), "m/44'/501'/0'/0'") + XCTAssertEqual(CoinType.pactus.derivationPathWithDerivation(derivation: Derivation.pactusMainnet), "m/44'/21888'/3'/0'") + XCTAssertEqual(CoinType.pactus.derivationPathWithDerivation(derivation: Derivation.pactusTestnet), "m/44'/21777'/3'/0'") + } + + func testDeriveAddressFromPublicKeyAndDerivationBitcoin() { + 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") + } + + func testDeriveAddressFromPublicKeyAndDerivationPactus() { + let pkData = Data(hexString: "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa")! + let publicKey = PublicKey(data: pkData, type: .ed25519)! + + let mainnet_address = CoinType.pactus.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.default) + XCTAssertEqual(mainnet_address, "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr") + + let testnet_address = CoinType.pactus.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.pactusTestnet) + XCTAssertEqual(testnet_address, "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg") } } 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 cbc704e4713..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 @@ -21,4 +19,9 @@ class DataTests: XCTestCase { XCTAssertNil(Data(hexString: "0x28fa6ae00")) XCTAssertNil(Data(hexString: "28fa6ae00")) } + + func testOddCharacters() { + XCTAssertNotNil(Data(hexString: String(repeating: "a", count: 64))) + XCTAssertNil(Data(hexString: String(repeating: "a", count: 63) + "z")) + } } 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 9bccda70462..a94fcec50a7 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -1,41 +1,170 @@ -// 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 extension HDWallet { - static let test = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "TREZOR") + static let test = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "TREZOR")! } class HDWalletTests: XCTestCase { - func testSeed() { + + 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 + XCTAssertEqual(wallet.mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal") + XCTAssertEqual(wallet.entropy.hexString, "ba5821e8c356c05ba5f025d9532fe0f21f65d594") XCTAssertEqual(wallet.seed.hexString, "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29") } - func testSeedNoPassword() { - let wallet = HDWallet(mnemonic: HDWallet.test.mnemonic, passphrase: "") + func testCreateFromMnemonicNoPassword() { + let wallet = HDWallet(mnemonic: HDWallet.test.mnemonic, passphrase: "")! XCTAssertEqual(wallet.seed.hexString, "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d") } + func testCreateFromMnemonicInvalid() { + let wallet = HDWallet(mnemonic: "THIS IS AN INVALID MNEMONIC", passphrase: "") + XCTAssertNil(wallet) + } + + func testGenerate() { + let wallet = HDWallet(strength: 128, passphrase: "")! + XCTAssertTrue(Mnemonic.isValid(mnemonic: wallet.mnemonic)) + } + + func testCreateFromEntropy() { + let wallet = HDWallet(entropy: Data(hexString: "ba5821e8c356c05ba5f025d9532fe0f21f65d594")!, passphrase: "TREZOR")! + XCTAssertEqual(wallet.mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal") + XCTAssertEqual(wallet.entropy.hexString, "ba5821e8c356c05ba5f025d9532fe0f21f65d594") + } + func testMasterKey() { - let wallet = HDWallet(mnemonic: "tiny escape drive pupil flavor endless love walk gadget match filter luxury", passphrase: "") + let wallet = HDWallet(mnemonic: "tiny escape drive pupil flavor endless love walk gadget match filter luxury", passphrase: "")! XCTAssertEqual(wallet.seed.hexString, "d430216f5b506dfd281d6ff6e92150d205868923df00774bc301e5ffdc2f4d1ad38a602017ddea6fc7d6315345d8b9cadbd8213ed2ffce5dfc550fa918665eb8") let masterKey = wallet.getMasterKey(curve: Curve.secp256k1) XCTAssertEqual(masterKey.data.hexString, "e120fc1ef9d193a851926ebd937c3985dc2c4e642fb3d0832317884d5f18f3b3") } + func testGetKeyForCoinBitcoin() { + let coin = CoinType.bitcoin + let wallet = HDWallet.test + let key = wallet.getKeyForCoin(coin: coin) + + let address = coin.deriveAddress(privateKey: key) + XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + func testGetKeyDerivationBitcoin() { + 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") + + let key4 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinTaproot) + XCTAssertEqual(key4.data.hexString, "a2c4d6df786f118f20330affd65d248ffdc0750ae9cbc729d27c640302afd030") + } + + func testGetAddressForCoinBitcoin() { + let coin = CoinType.bitcoin + let wallet = HDWallet.test + + let address = wallet.getAddressForCoin(coin: coin) + XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + func testGetAddressDerivationBitcoin() { + 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") + + let address4 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinTaproot) + XCTAssertEqual(address4, "bc1pgqks0cynn93ymve4x0jq3u7hne77908nlysp289hc44yc4cmy0hslyckrz") + } + + func testGetKeyDerivationPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let key1 = wallet.getKeyDerivation(coin: coin, derivation: .pactusMainnet) + XCTAssertEqual(key1.data.hexString, "153fefb8168f246f9f77c60ea10765c1c39828329e87284ddd316770717f3a5e") + + let key2 = wallet.getKeyDerivation(coin: coin, derivation: .pactusTestnet) + XCTAssertEqual(key2.data.hexString, "54f3c54dd6af5794bea1f86de05b8b9f164215e8deee896f604919046399e54d") + } + + func testGetAddressForCoinPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let address = wallet.getAddressForCoin(coin: coin) + XCTAssertEqual(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + func testGetAddressDerivationPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let address1 = wallet.getAddressDerivation(coin: coin, derivation: .pactusMainnet) + XCTAssertEqual(address1, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + + let address2 = wallet.getAddressDerivation(coin: coin, derivation: .pactusTestnet) + XCTAssertEqual(address2, "tpc1rjtamyqp203j4367q4plkp4qt32d7sv34kfmj5e") + } + func testDerive() { let wallet = HDWallet.test - let key0 = wallet.getKeyBIP44(coin: .ethereum, account: 0, change: 0, address: 0) - let key1 = wallet.getKeyBIP44(coin: .ethereum, account: 0, change: 0, address: 1) + let key0 = wallet.getDerivedKey(coin: .ethereum, account: 0, change: 0, address: 0) + let key1 = wallet.getDerivedKey(coin: .ethereum, account: 0, change: 0, address: 1) XCTAssertEqual(AnyAddress(publicKey: key0.getPublicKeySecp256k1(compressed: false), coin: .ethereum).description, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979") XCTAssertEqual(AnyAddress(publicKey: key1.getPublicKeySecp256k1(compressed: false), coin: .ethereum).description, "0x98f5438cDE3F0Ff6E11aE47236e93481899d1C47") @@ -98,7 +227,7 @@ class HDWalletTests: XCTestCase { let icon = CoinType.icon let wallet = HDWallet.test let key0 = wallet.getKeyForCoin(coin: icon) - let key1 = wallet.getKeyBIP44(coin: icon, account: 0, change: 0, address: 1) + let key1 = wallet.getDerivedKey(coin: icon, account: 0, change: 0, address: 1) let address0 = icon.deriveAddress(privateKey: key0) let address1 = icon.deriveAddress(privateKey: key1) @@ -134,7 +263,7 @@ class HDWalletTests: XCTestCase { } func testDeriveZcoin() { - let zcoin = CoinType.zcoin + let zcoin = CoinType.firo let wallet = HDWallet.test let key = wallet.getKeyForCoin(coin: zcoin) let address = zcoin.deriveAddress(privateKey: key) @@ -151,6 +280,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 @@ -196,7 +334,7 @@ class HDWalletTests: XCTestCase { } func testDeriveTezos2() { - let wallet = HDWallet(mnemonic: "kidney setup media hat relief plastic ghost census mouse science expect movie", passphrase: "") + let wallet = HDWallet(mnemonic: "kidney setup media hat relief plastic ghost census mouse science expect movie", passphrase: "")! let key = wallet.getKeyForCoin(coin: .tezos) let address = CoinType.tezos.deriveAddress(privateKey: key) @@ -207,7 +345,7 @@ class HDWalletTests: XCTestCase { func testDeriveNimiq() { // mnemonic is from https://github.com/Eligioo/nimiq-hd-wallet, compatible with ledger // but it's not compatible with safe.nimiq.com (can't import) - let wallet = HDWallet(mnemonic: "insane mixed health squeeze physical trust pipe possible garage hero flock stand profit power tooth review note camera express vicious clock machine entire heavy", passphrase: "") + let wallet = HDWallet(mnemonic: "insane mixed health squeeze physical trust pipe possible garage hero flock stand profit power tooth review note camera express vicious clock machine entire heavy", passphrase: "")! let coin = CoinType.nimiq let key = wallet.getKeyForCoin(coin: coin) let address = coin.deriveAddress(privateKey: key) @@ -265,7 +403,7 @@ class HDWalletTests: XCTestCase { } func testExtendedKeys() { - let wallet = HDWallet(mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", passphrase: "") + let wallet = HDWallet(mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", passphrase: "")! let xprv = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .bitcoin, version: .xprv) let xpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .bitcoin, version: .xpub) @@ -368,10 +506,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") } @@ -420,4 +558,30 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "band1pe8xm2r46rmctsukuqu7gl900vzprfsp4sguc3") } + + func testGenerateMultiThreaded() throws { + let group = DispatchGroup() + for _ in 0..<5 { + group.enter() + + Thread.init { + // multiple steps in one thread + for _ in 0..<10 { + // random wallet generation + let wallet = HDWallet(strength: 128, passphrase: "") + XCTAssertNotNil(wallet) + XCTAssertTrue(Mnemonic.isValid(mnemonic: wallet?.mnemonic ?? "")) + + // also try mnemonic-based generation + let mnemonic = wallet?.mnemonic ?? "" + let wallet2 = HDWallet(mnemonic: mnemonic, passphrase: "") + XCTAssertEqual(wallet2?.mnemonic, mnemonic) + } + + group.leave() + }.start() + } + + XCTAssertEqual(group.wait(timeout: .now() + .seconds(10)), .success) + } } diff --git a/swift/Tests/HashTests.swift b/swift/Tests/HashTests.swift index b85f3325d8c..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 @@ -15,11 +13,4 @@ class HashTests: XCTestCase { XCTAssertEqual(hashed.hexString, "e30d87cfa2a75db545eac4d61baf970366a8357c7f72fa95b52d0accb698f13a") } - - func testTwoXXhash64() { - let message = "ReservedBalance".data(using: .utf8)! - let hashed = Hash.twoXXHash64Concat(data: message) - - XCTAssertEqual(hashed.hexString, "3c22813def93ef32c365b55cb92f10f9") - } } diff --git a/swift/Tests/Keystore/Data/wallet.json b/swift/Tests/Keystore/Data/wallet.json index 3d723e90b64..8f7a53e8ac5 100755 --- a/swift/Tests/Keystore/Data/wallet.json +++ b/swift/Tests/Keystore/Data/wallet.json @@ -2,9 +2,9 @@ "version": 3, "id": "e0fe53d0-7a3d-4f65-88b1-9bb4e245a169", "crypto": { - "ciphertext": "64b5b416bb2bef882eb7cc63ed92c064e53c818ec46351e07ac140e5ba871596f1595fe6cad8333147fe68c031ba001b79b64dd1edd513043134217b7ffe1903ca23b1fbe823671827e3b2dff69bbd448d9cb79a3321ec8801f2a995", + "ciphertext": "3f6401e478074fc9c50a69dd88ea21baca70dd8064d8590b64f64b64d493e6e50bb6ff5ffc6aabcaac18c4aad25f29c53fe1029f8d6fa4ed24fc99938f27e38bea0b0cd7f8215f38d2526c655bff0b8f1638e948d8c1b9bdaa95ab0b", "cipherparams": { - "iv": "7aaf7eb6f4b0e7d995e8eac67e4d52eb" + "iv": "09246e7f7af92374eda0237da20c6696" }, "kdf": "scrypt", "kdfparams": { @@ -12,9 +12,9 @@ "p": 6, "n": 4096, "dklen": 32, - "salt": "80132842c6cde8f9d04582932ef92c3cad3ba6b41e1296ef681692372886db86" + "salt": "9cf4521a3543f0e116a86f188572f295a99081fd2e4143129cb5ee8760bec367" }, - "mac": "01816d0a5c31cd03b644f2d756ac8167c2498808040cbace8c35c46dcf06b7a1", + "mac": "67a8bf187bdeec076ac1e3647914e20b1dcbb15a5cb4643e6047fc2a07694055", "cipher": "aes-128-ctr" }, "type": "mnemonic", diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 61f658d0ee9..23db9ab0a71 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -31,6 +31,7 @@ class KeyStoreTests: XCTestCase { let keyAddress = AnyAddress(string: "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b", coin: .ethereum)! let walletAddress = AnyAddress(string: "0x32dd55E0BCF509a35A3F5eEb8593fbEb244796b1", coin: .ethereum)! let mnemonic = "often tobacco bread scare imitate song kind common bar forest yard wisdom" + let fileManager = FileManager.default var keyDirectory: URL! @@ -178,11 +179,77 @@ class KeyStoreTests: XCTestCase { let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) let storedData = wallet.key.decryptPrivateKey(password: Data("newPassword".utf8)) + XCTAssertFalse(wallet.key.hasPrivateKeyEncoded) XCTAssertNotNil(keyStore.keyWallet) 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 testImportKeyEncodedEthereum() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyHex = "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + let key = StoredKey.importPrivateKeyEncoded(privateKey: privateKeyHex, name: "name", password: Data("password".utf8), coin: .ethereum)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) + let storedEncoded = wallet.key.decryptPrivateKeyEncoded(password: Data("newPassword".utf8)) + + let exportedPrivateKey = try keyStore.exportPrivateKeyEncoded(wallet: wallet, password: "newPassword") + + XCTAssertTrue(wallet.key.hasPrivateKeyEncoded) + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedEncoded) + XCTAssertEqual(privateKeyHex, storedEncoded) + XCTAssertEqual(privateKeyHex, exportedPrivateKey) + } + + func testImportKeyEncodedSolana() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr" + let key = StoredKey.importPrivateKeyEncoded(privateKey: privateKeyBase58, name: "name", password: Data("password".utf8), coin: .solana)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.solana]) + let storedEncoded = wallet.key.decryptPrivateKeyEncoded(password: Data("newPassword".utf8)) + + let exportedPrivateKey = try keyStore.exportPrivateKeyEncoded(wallet: wallet, password: "newPassword") + + XCTAssertTrue(wallet.key.hasPrivateKeyEncoded) + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedEncoded) + XCTAssertEqual(privateKeyBase58, storedEncoded) + XCTAssertEqual(privateKeyBase58, exportedPrivateKey) + } + + func testImportPrivateKeyAES256Stellar() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyBase32 = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7" + let key = StoredKey.importPrivateKeyEncodedWithEncryption(privateKey: privateKeyBase32, name: "name", password: Data("password".utf8), coin: .stellar, encryption: StoredKeyEncryption.aes256Ctr)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.stellar]) + let storedEncoded = wallet.key.decryptPrivateKeyEncoded(password: Data("newPassword".utf8)) + + XCTAssertTrue(wallet.key.hasPrivateKeyEncoded) + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedEncoded) + XCTAssertEqual(privateKeyBase32, storedEncoded) + } + func testImportPrivateKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let privateKey = PrivateKey(data: Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")!)! @@ -208,35 +275,50 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(keyStore.hdWallet) } - func testImportJSON() throws { + 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)) + XCTAssertNotNil(storedData) + XCTAssertEqual(wallet.accounts.count, 1) + XCTAssertNotNil(keyStore.hdWallet) + } + + func testImportJSON() throws { let expected = """ { "activeAccounts": [{ "address": "bc1q4zehq85jqx9zzgzvzn9t64yjy66nunn3vehuv6", "coin": 0, "derivationPath": "m/84'/0'/0'/0/0", - "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q" + "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q", + "publicKey": "0334c47fa4eafef196f62eb53192a39bc36c5823ad4bd23db503170b9d3dbe80fd" }, { "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", "coin": 60, - "derivationPath": "m/44'/60'/0'/0/0" + "derivationPath": "m/44'/60'/0'/0/0", + "publicKey": "04906ab3a756b952c1f2ad41daf0c82cc12fb155cd73919b904ffb2630866abfe3feae7169c3e322465d119f4b20465b2a98f8bcb9e19bf22d84ba04e277c1c6ee" }, { "address": "bnb1njuczq3hgvupu2vnczrjz7rc8x4uxlmhjyq95z", "coin": 714, - "derivationPath": "m/44'/714'/0'/0/0" + "derivationPath": "m/44'/714'/0'/0/0", + "publicKey": "03397cf6ee9ddfee746dc750e9b1abd9824ff8fec3e29bb09b3b2c330a88b605b8" }, { "address": "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC", "coin": 10000714, - "derivationPath": "m/44'/714'/0'/0/0" + "derivationPath": "m/44'/714'/0'/0/0", + "publicKey": "04397cf6ee9ddfee746dc750e9b1abd9824ff8fec3e29bb09b3b2c330a88b605b81e46b99afe5dd84a5420b9e54f04b26aeb034f12849145a8163255875af1aef7" }, { "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", "coin": 20000714, - "derivationPath": "m/44'/60'/0'/0/0" + "derivationPath": "m/44'/60'/0'/0/0", + "publicKey": "04906ab3a756b952c1f2ad41daf0c82cc12fb155cd73919b904ffb2630866abfe3feae7169c3e322465d119f4b20465b2a98f8bcb9e19bf22d84ba04e277c1c6ee" }, { "address": "838f8aeba6bb083b5b6e22030fb051eaf1a8b6cd692d4ad533cba60c77e6b8f2", "coin": 397, - "derivationPath": "m/44'/397'/0'" + "derivationPath": "m/44'/397'/0'", + "publicKey": "838f8aeba6bb083b5b6e22030fb051eaf1a8b6cd692d4ad533cba60c77e6b8f2" }], "crypto": { "cipher": "aes-128-ctr", @@ -263,6 +345,13 @@ class KeyStoreTests: XCTestCase { let password = "e28ddf66cec05c1fc09939a00628b230459202b2493fccac288038ef37815723" let keyStore = try KeyStore(keyDirectory: keyDirectory) + + // Fill public key if needed + let btcAccount = try keyStore.bnbWallet.getAccount(password: password, coin: .bitcoin) + XCTAssertEqual(btcAccount.publicKey, "0334c47fa4eafef196f62eb53192a39bc36c5823ad4bd23db503170b9d3dbe80fd") + + // Fix all empty + _ = keyStore.bnbWallet.key.fixAddresses(password: Data(password.utf8)) _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.smartChainLegacy, .smartChain], password: password) // simulate migration code @@ -338,17 +427,14 @@ class KeyStoreTests: XCTestCase { for account in accounts { XCTAssertFalse(account.address.isEmpty) + XCTAssertFalse(account.publicKey.isEmpty) } XCTAssertEqual(coins.count, wallet.accounts.count) } func testSave() throws { - let fileManager = FileManager.default - let dir = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("keystore") - try? fileManager.removeItem(at: dir) - try fileManager.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil) - + let dir = try createTempDirURL() let keyStore = try KeyStore(keyDirectory: dir) try keyStore.watch([ Watch(coin: .ethereum, name: "name", address: "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b", xpub: nil) @@ -358,4 +444,77 @@ class KeyStoreTests: XCTestCase { XCTAssertTrue(fileManager.fileExists(atPath: dir.appendingPathComponent("watches.json").path)) XCTAssertTrue(fileManager.fileExists(atPath: wallet.keyURL.path)) } + + func testImportError() throws { + let url = try createTempDirURL() + let keystore = try KeyStore(keyDirectory: url) + + do { + _ = try keystore.import(json: Data(mnemonic.utf8), name: "", password: "", newPassword: "", coins: []) + } catch { + guard + let err = error as? KeyStore.Error, + err == .invalidJSON + else { + XCTFail("Should be invalid json error") + return + } + } + + do { + _ = try keystore.import(json: Data("{}".utf8), name: "", password: "", newPassword: "", coins: []) + } catch { + guard + let err = error as? KeyStore.Error, + err == .invalidJSON + else { + XCTFail("Should be invalid json error") + return + } + } + } + + func testCreateMultiAccount() throws { + let mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle" + let password = "password" + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: password, coins: [.bitcoin, .solana]) + + _ = try keyStore.addAccounts(wallet: wallet, coins: [.bitcoin, .solana], password: password) + + let btc1 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .default) + XCTAssertEqual(btc1.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny") + XCTAssertEqual(btc1.extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn") + + let btc2 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinLegacy) + XCTAssertEqual(btc2.address, "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz") + XCTAssertEqual(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV") + + let btc3 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinTaproot) + XCTAssertEqual(btc3.address, "bc1pyqkqf20fmmwmcxf98tv6k63e2sgnjy4zne6d0r32vxwm3au0hnksq6ec57") + XCTAssertEqual(btc3.extendedPublicKey, "zpub6qNRYbLLXquaD1GKxHZWDs3moUFQfP4iqXiDPCd8aD3oNHZkCAusAw5raKQEWV8BkXBTXhWBkgZTxzjjnQ5cRjWa6LNcjmrVVNdUKvbKTgm") + + let solana1 = try wallet.getAccount(password: password, coin: .solana, derivation: .default) + XCTAssertEqual(solana1.address, "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ") + XCTAssertEqual(solana1.derivationPath, "m/44'/501'/0'") + + let solana2 = try wallet.getAccount(password: password, coin: .solana, derivation: .solanaSolana) + XCTAssertEqual(solana2.address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C") + XCTAssertEqual(solana2.derivationPath, "m/44'/501'/0'/0'") + + let pactus_mainnet = try wallet.getAccount(password: password, coin: .pactus, derivation: .pactusMainnet) + XCTAssertEqual(pactus_mainnet.address, "pc1rzuswvfwde5hleqfemvpz4swlh6uud6nkukumdu") + XCTAssertEqual(pactus_mainnet.derivationPath, "m/44'/21888'/3'/0'") + + let pactus_testnet = try wallet.getAccount(password: password, coin: .pactus, derivation: .pactusTestnet) + XCTAssertEqual(pactus_testnet.address, "tpc1rxs9tperv58gvfwpn0vj5na7vrcffml40j2v6r9") + XCTAssertEqual(pactus_testnet.derivationPath, "m/44'/21777'/3'/0'") + } + + func createTempDirURL() throws -> URL { + let dir = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("keystore") + try? fileManager.removeItem(at: dir) + try fileManager.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil) + return dir + } } diff --git a/swift/Tests/Keystore/KeystoreKeyTests.swift b/swift/Tests/Keystore/KeystoreKeyTests.swift index dafa208495b..b33659ceaef 100755 --- a/swift/Tests/Keystore/KeystoreKeyTests.swift +++ b/swift/Tests/Keystore/KeystoreKeyTests.swift @@ -119,4 +119,25 @@ class KeystoreKeyTests: XCTestCase { let data = keystore.decryptPrivateKey(password: password) XCTAssertEqual(data?.hexString, "4357b2f9a6150ba969bc52f01c98cce5313595fe49f2d08303759c73e5c7a46c") } + + struct KdfParams: Decodable { + let dklen: Int + let n: Int + } + + struct EncryptionParameters: Decodable { + let kdf: String + let kdfparams: KdfParams + } + + func testEncryptionParameters() { + let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! + let key = StoredKey.load(path: url.path)! + + let paramsData = key.encryptionParameters!.data(using: .utf8)! + let params = try! JSONDecoder().decode(EncryptionParameters.self, from: paramsData) + + XCTAssertEqual(params.kdf, "scrypt"); + XCTAssertEqual(params.kdfparams.n, 262144); + } } 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 98bc17c9901..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 @@ -27,6 +25,9 @@ class MnemonicTests: XCTestCase { func testIsWordValid() { XCTAssertTrue(Mnemonic.isValidWord(word: "credit")) + + XCTAssertFalse(Mnemonic.isValidWord(word: "di")) + XCTAssertFalse(Mnemonic.isValidWord(word: "cr")) XCTAssertFalse(Mnemonic.isValidWord(word: "hybridous")) } diff --git a/swift/Tests/PBKDF2Tests.swift b/swift/Tests/PBKDF2Tests.swift new file mode 100644 index 00000000000..cb3645cdf95 --- /dev/null +++ b/swift/Tests/PBKDF2Tests.swift @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class PBKDF2Tests: XCTestCase { + + let password = "password".data(using: .utf8)! + let salt = "salt".data(using: .utf8)! + let salt2 = Data(base64Encoded: "kNHS+Mx//slRsmLF9396HQ==")! + + func testSha256Hmac() { + + let key = PBKDF2.hmacSha256(password: password, salt: salt, iterations: 1, dkLen: 20)! + + XCTAssertEqual(key.hexString, "120fb6cffcf8b32c43e7225256c4f837a86548c9") + + let key2 = PBKDF2.hmacSha256(password: password, salt: salt, iterations: 4096, dkLen: 20)! + + XCTAssertEqual(key2.hexString, "c5e478d59288c841aa530db6845c4c8d962893a0") + + let key3 = PBKDF2.hmacSha256(password: password, salt: salt2, iterations: 100, dkLen: 32)! + XCTAssertEqual(key3.hexString, "9cf33ebd3542c691fac6f61609a8d13355a0adf4d15eed77cc9d13f792b77c3a") + } + + func testSha512Hmac() { + let key = PBKDF2.hmacSha512(password: password, salt: salt, iterations: 1, dkLen: 20)! + + XCTAssertEqual(key.hexString, "867f70cf1ade02cff3752599a3a53dc4af34c7a6") + + let key2 = PBKDF2.hmacSha512(password: password, salt: salt, iterations: 4096, dkLen: 20)! + + XCTAssertEqual(key2.hexString, "d197b1b33db0143e018b12f3d1d1479e6cdebdcc") + + let key3 = PBKDF2.hmacSha512(password: password, salt: salt2, iterations: 100, dkLen: 32)! + XCTAssertEqual(key3.hexString, "6a9a209f35be9212118ea055e11b545451b53b686608a6362d59ddf31a2b3ce0") + } +} 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 51498a08c2c..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 @@ -29,7 +27,7 @@ class UniversalAssetIDTests: XCTestCase { XCTAssertEqual(busd.description, "c714_tBUSD-BD1") } - func testEqutable() { + func testEquatable() { XCTAssertEqual( UniversalAssetID(coin: .ethereum), 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 new file mode 100644 index 00000000000..962bbaf4239 --- /dev/null +++ b/swift/common-xcframework.yml @@ -0,0 +1,214 @@ +name: WalletCoreCommon +options: + bundleIdPrefix: com.trustwallet + createIntermediateGroups: true + platform: [iOS, macOS] + deploymentTarget: + iOS: 12.0 + macOS: 10.14 +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++20 + GCC_WARN_64_TO_32_BIT_CONVERSION: NO + +targets: + WalletCoreCommon: + type: framework.static + platform: [iOS, macOS] + deploymentTarget: + iOS: 12.0 + macOS: 10.14 + groups: + - ${platform} + sources: + - path: include + headerVisibility: public + excludes: + - "TrustWalletCore/TW*Proto.h" + - path: wallet-core + headerVisibility: project + excludes: + - ".vscode" + - "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 + INFOPLIST_FILE: 'Info.plist' + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: $(inherited) false + OTHER_CFLAGS: $(inherited) -Wno-comma + + trezor-crypto: + type: library.static + platform: [iOS, macOS] + deploymentTarget: + iOS: 12.0 + macOS: 10.14 + sources: + - trezor-crypto/crypto/bignum.c + - trezor-crypto/crypto/ecdsa.c + - trezor-crypto/crypto/curves.c + - trezor-crypto/crypto/secp256k1.c + - trezor-crypto/crypto/rand.c + - trezor-crypto/crypto/hmac.c + - trezor-crypto/crypto/bip32.c + - trezor-crypto/crypto/bip39.c + - trezor-crypto/crypto/pbkdf2.c + - trezor-crypto/crypto/base58.c + - trezor-crypto/crypto/base32.c + - trezor-crypto/crypto/address.c + - trezor-crypto/crypto/script.c + - trezor-crypto/crypto/ripemd160.c + - trezor-crypto/crypto/sha2.c + - trezor-crypto/crypto/sha3.c + - trezor-crypto/crypto/hasher.c + - trezor-crypto/crypto/aes/aescrypt.c + - trezor-crypto/crypto/aes/aeskey.c + - trezor-crypto/crypto/aes/aestab.c + - trezor-crypto/crypto/aes/aes_modes.c + - trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c + - trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c + - trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c + - trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c + - trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c + - trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c + - trezor-crypto/crypto/ed25519-donna/ed25519.c + - trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c + - trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c + - trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c + - trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c + - trezor-crypto/crypto/monero/base58.c + - trezor-crypto/crypto/monero/serialize.c + - trezor-crypto/crypto/monero/range_proof.c + - trezor-crypto/crypto/blake256.c + - trezor-crypto/crypto/blake2b.c + - trezor-crypto/crypto/blake2s.c + - trezor-crypto/crypto/chacha_drbg.c + - trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c + - trezor-crypto/crypto/chacha20poly1305/chacha_merged.c + - trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c + - trezor-crypto/crypto/chacha20poly1305/rfc7539.c + - trezor-crypto/crypto/rc4.c + - trezor-crypto/crypto/nano.c + - trezor-crypto/crypto/nem.c + - trezor-crypto/crypto/cash_addr.c + - trezor-crypto/crypto/memzero.c + - trezor-crypto/crypto/scrypt.c + - trezor-crypto/crypto/nist256p1.c + - trezor-crypto/crypto/groestl.c + - trezor-crypto/crypto/hmac_drbg.c + - trezor-crypto/crypto/rfc6979.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 + - trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c + - trezor-crypto/crypto/sodium/keypair.c + + protobuf: + type: library.static + platform: [iOS, macOS] + deploymentTarget: + iOS: 12.0 + macOS: 10.14 + sources: + - protobuf/google/protobuf/any.cc + - protobuf/google/protobuf/any.pb.cc + - protobuf/google/protobuf/any_lite.cc + - protobuf/google/protobuf/api.pb.cc + - protobuf/google/protobuf/arena.cc + - protobuf/google/protobuf/arenastring.cc + - protobuf/google/protobuf/arenaz_sampler.cc + - protobuf/google/protobuf/compiler/importer.cc + - protobuf/google/protobuf/compiler/parser.cc + - protobuf/google/protobuf/descriptor.cc + - protobuf/google/protobuf/descriptor.pb.cc + - protobuf/google/protobuf/descriptor_database.cc + - protobuf/google/protobuf/duration.pb.cc + - protobuf/google/protobuf/dynamic_message.cc + - protobuf/google/protobuf/empty.pb.cc + - protobuf/google/protobuf/extension_set.cc + - protobuf/google/protobuf/extension_set_heavy.cc + - protobuf/google/protobuf/field_mask.pb.cc + - protobuf/google/protobuf/generated_enum_util.cc + - protobuf/google/protobuf/generated_message_bases.cc + - protobuf/google/protobuf/generated_message_reflection.cc + - protobuf/google/protobuf/generated_message_tctable_full.cc + - protobuf/google/protobuf/generated_message_tctable_lite.cc + - protobuf/google/protobuf/generated_message_util.cc + - protobuf/google/protobuf/implicit_weak_message.cc + - protobuf/google/protobuf/inlined_string_field.cc + - protobuf/google/protobuf/io/coded_stream.cc + - protobuf/google/protobuf/io/gzip_stream.cc + - protobuf/google/protobuf/io/io_win32.cc + - protobuf/google/protobuf/io/printer.cc + - protobuf/google/protobuf/io/strtod.cc + - protobuf/google/protobuf/io/tokenizer.cc + - protobuf/google/protobuf/io/zero_copy_stream.cc + - protobuf/google/protobuf/io/zero_copy_stream_impl.cc + - protobuf/google/protobuf/io/zero_copy_stream_impl_lite.cc + - protobuf/google/protobuf/map.cc + - protobuf/google/protobuf/map_field.cc + - protobuf/google/protobuf/message.cc + - protobuf/google/protobuf/message_lite.cc + - protobuf/google/protobuf/parse_context.cc + - protobuf/google/protobuf/reflection_ops.cc + - protobuf/google/protobuf/repeated_field.cc + - protobuf/google/protobuf/repeated_ptr_field.cc + - protobuf/google/protobuf/service.cc + - protobuf/google/protobuf/source_context.pb.cc + - protobuf/google/protobuf/struct.pb.cc + - protobuf/google/protobuf/stubs/bytestream.cc + - protobuf/google/protobuf/stubs/common.cc + - protobuf/google/protobuf/stubs/int128.cc + - protobuf/google/protobuf/stubs/status.cc + - protobuf/google/protobuf/stubs/statusor.cc + - protobuf/google/protobuf/stubs/stringpiece.cc + - protobuf/google/protobuf/stubs/stringprintf.cc + - protobuf/google/protobuf/stubs/structurally_valid.cc + - protobuf/google/protobuf/stubs/strutil.cc + - protobuf/google/protobuf/stubs/substitute.cc + - protobuf/google/protobuf/stubs/time.cc + - protobuf/google/protobuf/text_format.cc + - protobuf/google/protobuf/timestamp.pb.cc + - protobuf/google/protobuf/type.pb.cc + - protobuf/google/protobuf/unknown_field_set.cc + - protobuf/google/protobuf/util/delimited_message_util.cc + - protobuf/google/protobuf/util/field_comparator.cc + - protobuf/google/protobuf/util/field_mask_util.cc + - protobuf/google/protobuf/util/internal/datapiece.cc + - protobuf/google/protobuf/util/internal/default_value_objectwriter.cc + - protobuf/google/protobuf/util/internal/error_listener.cc + - protobuf/google/protobuf/util/internal/field_mask_utility.cc + - protobuf/google/protobuf/util/internal/json_escaping.cc + - protobuf/google/protobuf/util/internal/json_objectwriter.cc + - protobuf/google/protobuf/util/internal/json_stream_parser.cc + - protobuf/google/protobuf/util/internal/object_writer.cc + - protobuf/google/protobuf/util/internal/proto_writer.cc + - protobuf/google/protobuf/util/internal/protostream_objectsource.cc + - protobuf/google/protobuf/util/internal/protostream_objectwriter.cc + - protobuf/google/protobuf/util/internal/type_info.cc + - protobuf/google/protobuf/util/internal/utility.cc + - protobuf/google/protobuf/util/json_util.cc + - protobuf/google/protobuf/util/message_differencer.cc + - protobuf/google/protobuf/util/time_util.cc + - protobuf/google/protobuf/util/type_resolver_util.cc + - protobuf/google/protobuf/wire_format.cc + - protobuf/google/protobuf/wire_format_lite.cc + - protobuf/google/protobuf/wrappers.pb.cc + settings: + OTHER_CFLAGS: $(inherited) -DHAVE_PTHREAD=1 -Wno-comma -Wno-unreachable-code diff --git a/swift/cpp.xcconfig.in b/swift/cpp.xcconfig.in index 36b166aceb6..e96e2db5138 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.14.0/src +SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.20.3/src diff --git a/swift/fastlane/Fastfile b/swift/fastlane/Fastfile new file mode 100644 index 00000000000..b22a40e6cc9 --- /dev/null +++ b/swift/fastlane/Fastfile @@ -0,0 +1,32 @@ +opt_out_usage +default_platform(:ios) + +platform :ios do + desc "create full xcframeworks" + lane :xcframework do + swift_protobuf_xcframework + core_xcframework + end + + desc "create wallet core xcframework" + lane :core_xcframework do + create_xcframework( + workspace: 'TrustWalletCore.xcworkspace', + scheme: 'WalletCore', + destinations: ['iOS'], + xcframework_output_directory: 'build', + enable_bitcode: false + ) + end + + desc "create swift protobuf xcframework" + lane :swift_protobuf_xcframework do + create_xcframework( + workspace: 'TrustWalletCore.xcworkspace', + scheme: 'WalletCoreSwiftProtobuf', + destinations: ['iOS'], + xcframework_output_directory: 'build', + enable_bitcode: false + ) + end +end diff --git a/swift/fastlane/Pluginfile b/swift/fastlane/Pluginfile new file mode 100644 index 00000000000..35e331872e0 --- /dev/null +++ b/swift/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-create_xcframework' diff --git a/swift/project.yml b/swift/project.yml index aba5d97e052..9d68acab6cd 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -5,9 +5,10 @@ options: 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 - CLANG_CXX_LANGUAGE_STANDARD: c++17 + 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++20 SWIFT_VERSION: 5.1 + IPHONEOS_DEPLOYMENT_TARGET: 13.0 configs: release: SWIFT_OPTIMIZATION_LEVEL: -Owholemodule @@ -16,7 +17,6 @@ targets: WalletCore: type: framework platform: iOS - deploymentTarget: 12.0 sources: - path: include headerVisibility: public @@ -25,11 +25,12 @@ targets: - path: wallet-core headerVisibility: project excludes: - - ".vscode" - - "proto/*.proto" - - "Tron/Protobuf/*.proto" - - "Zilliqa/Protobuf/*.proto" - - "TON/*.md" + - ".vscode" + - "proto/*.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 @@ -44,14 +46,23 @@ targets: settings: SKIP_INSTALL: false SUPPORTS_MACCATALYST: true + DEBUG_INFORMATION_FORMAT: dwarf-with-dsym BUILD_LIBRARY_FOR_DISTRIBUTION: true INFOPLIST_FILE: 'Info.plist' CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: $(inherited) false - OTHER_CFLAGS: $(inherited) -Wno-comma + OTHER_CFLAGS: $(inherited) -Wno-comma -DNDEBUG postCompileScripts: - - script: ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml + - script: | + export PATH=/opt/homebrew/bin:$PATH; + if which swiftlint >/dev/null; then + swiftlint + else + echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" + fi name: SwiftLint + shell: /bin/bash + basedOnDependencyAnalysis: false WalletCoreTests: type: bundle.unit-test @@ -118,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 @@ -126,6 +138,7 @@ targets: - trezor-crypto/crypto/sodium/keypair.c settings: GCC_WARN_64_TO_32_BIT_CONVERSION: NO + OTHER_CFLAGS: $(inherited) -Wno-comma protobuf: type: library.static @@ -137,6 +150,7 @@ targets: - protobuf/google/protobuf/api.pb.cc - protobuf/google/protobuf/arena.cc - protobuf/google/protobuf/arenastring.cc + - protobuf/google/protobuf/arenaz_sampler.cc - protobuf/google/protobuf/compiler/importer.cc - protobuf/google/protobuf/compiler/parser.cc - protobuf/google/protobuf/descriptor.cc @@ -149,11 +163,13 @@ targets: - protobuf/google/protobuf/extension_set_heavy.cc - protobuf/google/protobuf/field_mask.pb.cc - protobuf/google/protobuf/generated_enum_util.cc + - protobuf/google/protobuf/generated_message_bases.cc - protobuf/google/protobuf/generated_message_reflection.cc - - protobuf/google/protobuf/generated_message_table_driven.cc - - protobuf/google/protobuf/generated_message_table_driven_lite.cc + - protobuf/google/protobuf/generated_message_tctable_full.cc + - protobuf/google/protobuf/generated_message_tctable_lite.cc - protobuf/google/protobuf/generated_message_util.cc - protobuf/google/protobuf/implicit_weak_message.cc + - protobuf/google/protobuf/inlined_string_field.cc - protobuf/google/protobuf/io/coded_stream.cc - protobuf/google/protobuf/io/gzip_stream.cc - protobuf/google/protobuf/io/io_win32.cc @@ -170,6 +186,7 @@ targets: - protobuf/google/protobuf/parse_context.cc - protobuf/google/protobuf/reflection_ops.cc - protobuf/google/protobuf/repeated_field.cc + - protobuf/google/protobuf/repeated_ptr_field.cc - protobuf/google/protobuf/service.cc - protobuf/google/protobuf/source_context.pb.cc - protobuf/google/protobuf/struct.pb.cc @@ -203,7 +220,6 @@ targets: - protobuf/google/protobuf/util/internal/protostream_objectsource.cc - protobuf/google/protobuf/util/internal/protostream_objectwriter.cc - protobuf/google/protobuf/util/internal/type_info.cc - - protobuf/google/protobuf/util/internal/type_info_test_helper.cc - protobuf/google/protobuf/util/internal/utility.cc - protobuf/google/protobuf/util/json_util.cc - protobuf/google/protobuf/util/message_differencer.cc diff --git a/swift/protobuf b/swift/protobuf index 3d4e7cf4303..6e8a73eadea 120000 --- a/swift/protobuf +++ b/swift/protobuf @@ -1 +1 @@ -../build/local/src/protobuf/protobuf-3.14.0/src \ No newline at end of file +../build/local/src/protobuf/protobuf-3.20.3/src \ No newline at end of file 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 03ef85f487b..00000000000 --- a/tests/Algorand/SignerTests.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 "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 = Transaction( - /* 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.serialize(signature); - - ASSERT_EQ(hex(serialized), "89a3616d74cd034fa3666565ce000775e3a2667633a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c763da3726376c420a089aa6922e3b998fadff6cd4808ddf9e021e4944e389ea3d5c638786689197ea3736e64c42074b000b6368551a6066d713e2866002e8dab34b69ede09a72e85a39bbb1f7928a474797065a3706179"); - ASSERT_EQ(hex(signature), "de73363dbdeda0682adca06f6268a16a6ec47253c94d5692dc1c49a84a05847812cf66d7c4cf07c7e2f50f143ec365d405e30b35117b264a994626054d2af604"); - ASSERT_EQ(hex(result), "82a3736967c440de73363dbdeda0682adca06f6268a16a6ec47253c94d5692dc1c49a84a05847812cf66d7c4cf07c7e2f50f143ec365d405e30b35117b264a994626054d2af604a374786e89a3616d74cd034fa3666565ce000775e3a2667633a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c763da3726376c420a089aa6922e3b998fadff6cd4808ddf9e021e4944e389ea3d5c638786689197ea3736e64c42074b000b6368551a6066d713e2866002e8dab34b69ede09a72e85a39bbb1f7928a474797065a3706179"); -} diff --git a/tests/Algorand/TWAnySignerTests.cpp b/tests/Algorand/TWAnySignerTests.cpp deleted file mode 100644 index 82ec598f932..00000000000 --- a/tests/Algorand/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 "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_transaction_pay(); - transaction.set_to_address("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4"); - transaction.set_fee(263000ull); - transaction.set_amount(1000000000000ull); - transaction.set_first_round(1937767ull); - transaction.set_last_round(1938767ull); - 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"); -} 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/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 8cb091000c9..00000000000 --- a/tests/Binance/TWAnySignerTests.cpp +++ /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. - -#include "HexCoding.h" -#include "Binance/Address.h" -#include "proto/Binance.pb.h" -#include - -#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-Nile"); - 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(); - - // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 - auto fromKeyhash = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); - // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 - auto toKeyhash = parse_hex("bffe47abfaede50419c577f1074fee6dd1535cd1"); - - { - 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()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); -} - -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()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); - } - }; - - // 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 aaa9c3f0b9e..00000000000 --- a/tests/Binance/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(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)); - - 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"); -} diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp deleted file mode 100644 index ff7e7637c4c..00000000000 --- a/tests/BinanceSmartChain/SignerTests.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "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; - -class SignerExposed : public Signer { -public: - SignerExposed(boost::multiprecision::uint256_t chainID) : Signer(chainID) {} - using Signer::hash; -}; - -TEST(BinanceSmartChain, SignNativeTransfer) { - // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e - - auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto transaction = Transaction( - /* 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")); - auto signer = SignerExposed(97); - signer.sign(privateKey, transaction); - - auto encoded = RLP::encode(transaction); - 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 8cca95a5cf4..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, "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/BitcoinScriptTests.cpp b/tests/Bitcoin/BitcoinScriptTests.cpp deleted file mode 100644 index 4942cc948ff..00000000000 --- a/tests/Bitcoin/BitcoinScriptTests.cpp +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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/Script.h" -#include "Bitcoin/TransactionSigner.h" -#include "../interface/TWTestUtilities.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Bitcoin; - -const Script PayToScriptHash(parse_hex("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87")); -const Script PayToWitnessScriptHash(parse_hex("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); -const Script PayToWitnessPublicKeyHash(parse_hex("0014" "79091972186c449eb1ded22b78e40d009bdf0089")); -const Script PayToPublicKeySecp256k1(parse_hex("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac")); -const Script PayToPublicKeySecp256k1Extended(parse_hex("41" "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "66b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91ac")); -const Script PayToPublicKeyHash(parse_hex("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac")); - -TEST(BitcoinScript, PayToPublicKey) { - Data res; - EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToPublicKey(res), true); - EXPECT_EQ(hex(res), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToPublicKey(res), true); - EXPECT_EQ(hex(res), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - - EXPECT_EQ(PayToScriptHash.matchPayToPublicKey(res), false); - EXPECT_EQ(PayToWitnessScriptHash.matchPayToPublicKey(res), false); - EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToPublicKey(res), false); - EXPECT_EQ(PayToPublicKeyHash.matchPayToPublicKey(res), false); -} - -TEST(BitcoinScript, PayToPublicKeyHash) { - Data res; - EXPECT_EQ(PayToPublicKeyHash.matchPayToPublicKeyHash(res), true); - EXPECT_EQ(hex(res), "79091972186c449eb1ded22b78e40d009bdf0089"); - EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToPublicKey(res), true); - EXPECT_EQ(hex(res), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToPublicKey(res), true); - EXPECT_EQ(hex(res), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - - EXPECT_EQ(PayToScriptHash.matchPayToPublicKeyHash(res), false); - EXPECT_EQ(PayToWitnessScriptHash.matchPayToPublicKeyHash(res), false); - EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToPublicKeyHash(res), false); -} - -TEST(BitcoinScript, PayToScriptHash) { - EXPECT_EQ(PayToScriptHash.isPayToScriptHash(), true); - EXPECT_EQ(PayToScriptHash.bytes.size(), 23); - - EXPECT_EQ(PayToWitnessScriptHash.isPayToScriptHash(), false); - EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToScriptHash(), false); - EXPECT_EQ(PayToPublicKeySecp256k1.isPayToScriptHash(), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToScriptHash(), false); - EXPECT_EQ(PayToPublicKeyHash.isPayToScriptHash(), false); - - Data res; - EXPECT_EQ(PayToScriptHash.matchPayToScriptHash(res), true); - EXPECT_EQ(hex(res), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); - - EXPECT_EQ(PayToWitnessScriptHash.matchPayToScriptHash(res), false); - EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToScriptHash(res), false); - EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToScriptHash(res), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToScriptHash(res), false); - EXPECT_EQ(PayToPublicKeyHash.matchPayToScriptHash(res), false); -} - -TEST(BitcoinScript, PayToWitnessScriptHash) { - EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessScriptHash(), true); - EXPECT_EQ(PayToWitnessScriptHash.bytes.size(), 34); - - EXPECT_EQ(PayToScriptHash.isPayToWitnessScriptHash(), false); - EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessScriptHash(), false); - EXPECT_EQ(PayToPublicKeySecp256k1.isPayToWitnessScriptHash(), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToWitnessScriptHash(), false); - EXPECT_EQ(PayToPublicKeyHash.isPayToWitnessScriptHash(), false); - - Data res; - EXPECT_EQ(PayToWitnessScriptHash.matchPayToWitnessScriptHash(res), true); - EXPECT_EQ(hex(res), "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); - - EXPECT_EQ(PayToScriptHash.matchPayToWitnessScriptHash(res), false); - EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToWitnessScriptHash(res), false); - EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToWitnessScriptHash(res), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToWitnessScriptHash(res), false); - EXPECT_EQ(PayToPublicKeyHash.matchPayToWitnessScriptHash(res), false); -} - -TEST(BitcoinScript, PayToWitnessPublicKeyHash) { - EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessPublicKeyHash(), true); - EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22); - - EXPECT_EQ(PayToScriptHash.isPayToWitnessPublicKeyHash(), false); - EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessPublicKeyHash(), false); - EXPECT_EQ(PayToPublicKeySecp256k1.isPayToWitnessPublicKeyHash(), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToWitnessPublicKeyHash(), false); - EXPECT_EQ(PayToPublicKeyHash.isPayToWitnessPublicKeyHash(), false); - - Data res; - EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToWitnessPublicKeyHash(res), true); - EXPECT_EQ(hex(res), "79091972186c449eb1ded22b78e40d009bdf0089"); - - EXPECT_EQ(PayToScriptHash.matchPayToWitnessPublicKeyHash(res), false); - EXPECT_EQ(PayToWitnessScriptHash.matchPayToWitnessPublicKeyHash(res), false); - EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToWitnessPublicKeyHash(res), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToWitnessPublicKeyHash(res), false); - EXPECT_EQ(PayToPublicKeyHash.matchPayToWitnessPublicKeyHash(res), false); -} - -TEST(BitcoinScript, WitnessProgram) { - EXPECT_EQ(PayToWitnessScriptHash.isWitnessProgram(), true); - EXPECT_EQ(PayToWitnessPublicKeyHash.isWitnessProgram(), true); - - EXPECT_EQ(PayToScriptHash.isWitnessProgram(), false); - EXPECT_EQ(PayToPublicKeySecp256k1.isWitnessProgram(), false); - EXPECT_EQ(PayToPublicKeySecp256k1Extended.isWitnessProgram(), false); - EXPECT_EQ(PayToPublicKeyHash.isWitnessProgram(), false); -} - -TEST(BitcoinScript, EncodeNumber) { - EXPECT_EQ(Script::encodeNumber(0), OP_0); - EXPECT_EQ(Script::encodeNumber(1), OP_1); - EXPECT_EQ(Script::encodeNumber(3), OP_3); - EXPECT_EQ(Script::encodeNumber(9), OP_9); -} - -TEST(BitcoinScript, DecodeNumber) { - EXPECT_EQ(Script::decodeNumber(OP_0), 0); - EXPECT_EQ(Script::decodeNumber(OP_1), 1); - EXPECT_EQ(Script::decodeNumber(OP_3), 3); - EXPECT_EQ(Script::decodeNumber(OP_9), 9); -} - -TEST(BitcoinScript, GetScriptOp) { - { - size_t index = 5; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("")).getScriptOp(index, opcode, operand), false); - } - { - 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(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(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(opcode, 0x4c); - EXPECT_EQ(hex(operand), "0102030405"); - } - { // OP_PUSHDATA1 too short - size_t index = 0; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("4c")).getScriptOp(index, opcode, operand), false); - } - { // OP_PUSHDATA1 too short - size_t index = 0; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("4c" "05" "010203")).getScriptOp(index, opcode, operand), false); - } - { // 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(opcode, 0x4d); - EXPECT_EQ(hex(operand), "0102030405"); - } - { // OP_PUSHDATA2 too short - size_t index = 0; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("4d" "05")).getScriptOp(index, opcode, operand), false); - } - { // OP_PUSHDATA2 too short - size_t index = 0; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("4d" "0500" "010203")).getScriptOp(index, opcode, operand), false); - } - { // 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(opcode, 0x4e); - EXPECT_EQ(hex(operand), "0102030405"); - } - { // OP_PUSHDATA4 too short - size_t index = 0; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("4e" "0500")).getScriptOp(index, opcode, operand), false); - } - { // OP_PUSHDATA4 too short - size_t index = 0; uint8_t opcode; Data operand; - EXPECT_EQ(Script(parse_hex("4e" "05000000" "010203")).getScriptOp(index, opcode, operand), false); - } -} - -TEST(BitcoinScript, MatchMultiSig) { - std::vector keys; - int required; - EXPECT_EQ(Script(parse_hex("")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("20")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("00ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("4fae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("20ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514cae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514c05ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("51ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("51" "05" "0102030405" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "00ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "52ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51aeae")).matchMultisig(keys, required), false); - - // 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); - EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - - EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("52" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); - - // 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); - EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - - // OP_PUSHDATA1 - EXPECT_EQ(Script(parse_hex("514cae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514c" "05" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514c" "05" "0102030405" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514c" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); - - // 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); - EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - - // OP_PUSHDATA2 - EXPECT_EQ(Script(parse_hex("514dae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514d" "0500" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514d" "0500" "0102030405" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514d" "2100" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); - - // 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); - EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - - // OP_PUSHDATA4 - EXPECT_EQ(Script(parse_hex("514eae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514e" "0500" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514e" "05000000" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514e" "05000000" "0102030405" "ae")).matchMultisig(keys, required), false); - EXPECT_EQ(Script(parse_hex("514e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); - - // 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); - EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - - // valid three keys, mixed - EXPECT_EQ(Script(parse_hex("53" - "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" - "4d" "2100" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" - "4e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" - "53" "ae")).matchMultisig(keys, required), true); - EXPECT_EQ(required, 3); - ASSERT_EQ(keys.size(), 3); - EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); -} - -TEST(BitcoinTransactionSigner, PushAllEmpty) { - { - std::vector input = {}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), ""); - } - { - std::vector input = {parse_hex("")}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), "00"); - } - { - std::vector input = {parse_hex("09")}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), "59" "09"); - } - { - std::vector input = {parse_hex("00010203040506070809")}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), "0a" "00010203040506070809"); - } - { - std::vector input = {parse_hex("0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809")}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), "4c50" "0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809"); - } - { - // 2-byte len - Data in1 = Data(256 + 10); - Data expected = parse_hex("4d" "0a01"); - TW::append(expected, in1); - std::vector input = {in1}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), hex(expected)); - } - { - // 4-byte len - Data in1 = Data(65536 + 256 + 10); - Data expected = parse_hex("4e" "0a010100"); - TW::append(expected, in1); - std::vector input = {in1}; - Data res = TransactionSigner::pushAll(input); - EXPECT_EQ(hex(res), hex(expected)); - } -} \ No newline at end of file diff --git a/tests/Bitcoin/FeeCalculatorTests.cpp b/tests/Bitcoin/FeeCalculatorTests.cpp deleted file mode 100644 index e5524a31eaa..00000000000 --- a/tests/Bitcoin/FeeCalculatorTests.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 "Bitcoin/FeeCalculator.h" - -#include - -using namespace TW; -using namespace TW::Bitcoin; - -TEST(BitcoinFeeCalculator, BitcoinCalculate) { - 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) { - 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) { - 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/SegwitAddressTests.cpp b/tests/Bitcoin/SegwitAddressTests.cpp deleted file mode 100644 index a919cd66878..00000000000 --- a/tests/Bitcoin/SegwitAddressTests.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 "Bech32.h" -#include "Bitcoin/SegwitAddress.h" -#include "HexCoding.h" - -#include -#include -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - -static const std::string valid_checksum[] = { - "A12UEL5L", - "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", - "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", - "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", - "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", -}; - -static const std::string invalid_checksum[] = { - " 1nwldj5", - "\x7f""1axkwrx", - "an124characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx0123456789012345678901234567890123456789", - "pzry9x0s0muk", - "1pzry9x0s0muk", - "x1b4n0q5v", - "li1dgmt3", - "de1lg7wt\xff", -}; - -struct valid_address_data { - std::string address; - size_t scriptPubKeyLen; - uint8_t scriptPubKey[42]; -}; - -struct invalid_address_data { - std::string hrp; - int version; - size_t program_length; -}; - -static const struct valid_address_data valid_address[] = { - { - "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", - 22, { - 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, - 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 - } - }, - { - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", - 34, { - 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, - 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78, - 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, - 0x62 - } - }, - { - "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", - 34, { - 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, - 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, - 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, - 0x33 - } - } -}; - -static const std::vector invalid_address = { - "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", - "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", - "bc1rw5uspcuh", - "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", - "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", - "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", - "bc1gmk9yu", // empty data, no version - "bc1q9zpgru", // 1 byte data (only version byte) - "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", // version = 1 - "BC1SW50QA3JX3S", // version = 16 - "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", // version = 2 -}; - -static const invalid_address_data invalid_address_enc[] = { - {"BC", 0, 20}, - {"bc", 0, 21}, - {"bc", 17, 32}, - {"bc", 1, 1}, - {"bc", 16, 41}, -}; - -static std::vector segwit_scriptpubkey(int witver, const std::vector& witprog) { - std::vector ret; - ret.push_back(witver ? (0x80 | witver) : 0); - ret.push_back(witprog.size()); - ret.insert(ret.end(), witprog.begin(), witprog.end()); - return ret; -} - -bool case_insensitive_equal(const std::string& s1, const std::string& s2) { - size_t i = 0; - if (s1.size() != s2.size()) return false; - while (i < s1.size() && i < s2.size()) { - char c1 = s1[i]; - char c2 = s2[i]; - if (c1 >= 'A' && c1 <= 'Z') c1 = (c1 - 'A') + 'a'; - if (c2 >= 'A' && c2 <= 'Z') c2 = (c2 - 'A') + 'a'; - if (c1 != c2) return false; - ++i; - } - return true; -} - -TEST(SegwitAddress, ValidChecksum) { - for (auto i = 0; i < sizeof(valid_checksum) / sizeof(valid_checksum[0]); ++i) { - auto dec = Bech32::decode(valid_checksum[i]); - ASSERT_FALSE(dec.first.empty()); - - auto recode = Bech32::encode(dec.first, dec.second); - ASSERT_FALSE(recode.empty()); - - ASSERT_TRUE(case_insensitive_equal(recode, valid_checksum[i])); - } -} - -TEST(SegwitAddress, InvalidChecksum) { - for (auto i = 0; i < sizeof(invalid_checksum) / sizeof(invalid_checksum[0]); ++i) { - auto dec = Bech32::decode(invalid_checksum[i]); - EXPECT_TRUE(dec.first.empty() && dec.second.empty()); - } -} - -TEST(SegwitAddress, ValidAddress) { - for (auto i = 0; i < sizeof(valid_address) / sizeof(valid_address[0]); ++i) { - auto dec = SegwitAddress::decode(valid_address[i].address); - ASSERT_TRUE(std::get<2>(dec)) << "Valid address could not be decoded " << valid_address[i].address; - ASSERT_EQ(std::get<1>(dec).length(), 2); // hrp - - std::vector spk = segwit_scriptpubkey(std::get<0>(dec).witnessVersion, std::get<0>(dec).witnessProgram); - ASSERT_TRUE(spk.size() == valid_address[i].scriptPubKeyLen && std::memcmp(&spk[0], valid_address[i].scriptPubKey, spk.size()) == 0); - - std::string recode = std::get<0>(dec).string(); - ASSERT_FALSE(recode.empty()); - - ASSERT_TRUE(case_insensitive_equal(valid_address[i].address, recode)); - } -} - -TEST(SegwitAddress, InvalidAddress) { - for (auto i = 0; 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) { - auto address = SegwitAddress(invalid_address_enc[i].hrp, invalid_address_enc[i].version, std::vector(invalid_address_enc[i].program_length, 0)); - std::string code = address.string(); - EXPECT_TRUE(code.empty()); - } -} - -TEST(SegwitAddress, LegacyAddress) { - auto result = SegwitAddress::decode("TLWEciM1CjP5fJqM2r9wymAidkkYtTU5k3"); - EXPECT_FALSE(std::get<2>(result)); -} - -TEST(SegwitAddress, fromRaw) { - { - auto addr = SegwitAddress::fromRaw("bc", parse_hex("000e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16")); - EXPECT_TRUE(addr.second); - EXPECT_EQ(addr.first.string(), "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); - } - { - // empty data - auto addr = SegwitAddress::fromRaw("bc", Data()); - EXPECT_FALSE(addr.second); - } -} diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp deleted file mode 100644 index fb4bc9724c9..00000000000 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ /dev/null @@ -1,1308 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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/OutPoint.h" -#include "Bitcoin/Script.h" -#include "Bitcoin/Transaction.h" -#include "Bitcoin/TransactionBuilder.h" -#include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.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 - -using namespace TW; -using namespace TW::Bitcoin; - - -Proto::SigningInput buildInputP2PKH(bool omitKey = false) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - Proto::SigningInput input; - input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); - input.set_amount(335'790'000); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - - 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.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - } - - 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.add_private_key(utxoKey1.bytes.data(), utxoKey1.bytes.size()); - } - - auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash0); - Data scriptHash; - utxo0Script.matchPayToPublicKeyHash(scriptHash); - assert(hex(scriptHash) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); - - auto utxo0 = input.add_utxo(); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.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("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - 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); - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - 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 - ); -} - -Proto::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 - Proto::SigningInput input; - input.set_hash_type(hashType); - input.set_amount(amount); - input.set_use_max_amount(useMaxAmount); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); - auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); - input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); - assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - input.add_private_key(utxoKey1.bytes.data(), utxoKey1.bytes.size()); - - 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")); - 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(utxo0Amount); - 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("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo1Script.data(), utxo1Script.size()); - utxo1->set_amount(utxo1Amount); - 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); - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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, 0); - - 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 - ); -} - -Proto::SigningInput buildInputP2WSH(uint32_t hashType, bool omitScript = false, bool omitKeys = false) { - Proto::SigningInput input; - input.set_hash_type(hashType); - input.set_amount(1000); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - - if (!omitKeys) { - auto utxoKey0 = parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); - } - - if (!omitScript) { - auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); - auto scriptHash = "593128f9f90e38b706c18623151e37d2da05c229"; - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHash] = scriptString; - } - - auto utxo0 = input.add_utxo(); - auto p2wsh = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); - utxo0->set_script(p2wsh.bytes.data(), p2wsh.bytes.size()); - utxo0->set_amount(1226); - auto hash0 = parse_hex("0001000000000000000000000000000000000000000000000000000000000000"); - 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); - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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((uint32_t)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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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((uint32_t)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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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((uint32_t)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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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((uint32_t)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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); -} - -TEST(BitcoinSigning, SignP2WSH_NegativeMissingKeys) { - const auto input = buildInputP2WSH((uint32_t)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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); -} - -TEST(BitcoinSigning, SignP2WSH_NegativePlanWithError) { - // Setup input - auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll); - auto plan = Bitcoin::TransactionPlan(); - input.mutable_plan()->set_error(Common::Proto::Error_missing_input_utxos); - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, SignP2WSH_NegativeNoUTXOs) { - // Setup input - auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll); - input.clear_utxo(); - input.mutable_plan()->clear_utxos(); - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { - // Setup input - auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll); - input.mutable_plan()->clear_utxos(); - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - 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 - ); -} - -Proto::SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = false, bool invalidOutputScript = false, bool invalidRedeemScript = false) { - // Setup input - Proto::SigningInput input; - input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); - input.set_amount(200'000'000); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - - 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.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - } - - if (!omitScript && !invalidRedeemScript) { - auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - assert(hex(scriptHash) == "4733f37cf4db86fbc2efed2500b4f4e49f312023"); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash)] = scriptString; - } else if (invalidRedeemScript) { - auto redeemScript = parse_hex("FAFBFCFDFE"); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript)); - auto scriptString = std::string(redeemScript.begin(), redeemScript.end()); - (*input.mutable_scripts())[hex(scriptHash)] = scriptString; - } - - auto utxo0 = input.add_utxo(); - auto utxo0Script = Script(parse_hex("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387")); - if (invalidOutputScript) { - utxo0Script = Script(parse_hex("FFFEFDFCFB")); - } - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(1'000'000'000); - auto hash0 = DATA("db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477"); - utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); - utxo0->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); -} - -TEST(BitcoinSigning, EncodeP2SH_P2WSH) { - auto unsignedTx = Transaction(1, 0); - - 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) { - auto emptyScript = Script(); - auto unsignedTx = Transaction(1, 0); - - auto outpoint0 = OutPoint(parse_hex("36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e"), 1); - unsignedTx.inputs.emplace_back(outpoint0, emptyScript, 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); - - // Setup signing input - auto input = Proto::SigningInput(); - input.set_amount(900000000); - - auto key0 = parse_hex("730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"); - input.add_private_key(key0.data(), key0.size()); - auto key1 = parse_hex("11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"); - input.add_private_key(key1.data(), key1.size()); - auto key2 = parse_hex("77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"); - input.add_private_key(key2.data(), key2.size()); - auto key3 = parse_hex("14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"); - input.add_private_key(key3.data(), key3.size()); - auto key4 = parse_hex("fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"); - input.add_private_key(key4.data(), key4.size()); - auto key5 = parse_hex("428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"); - input.add_private_key(key5.data(), key5.size()); - - auto redeemScript = Script::buildPayToWitnessScriptHash(parse_hex("a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54")); - 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 witnessScript = Script(parse_hex("" - "56" - "210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3" - "2103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b" - "21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a" - "21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4" - "2103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16" - "2102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b" - "56ae" - )); - auto witnessScriptHash = Hash::ripemd(Hash::sha256(witnessScript.bytes)); - auto witnessScriptString = std::string(witnessScript.bytes.begin(), witnessScript.bytes.end()); - (*input.mutable_scripts())[hex(witnessScriptHash)] = witnessScriptString; - - auto utxo0Script = Script(parse_hex("a9149993a429037b5d912407a71c252019287b8d27a587")); - auto utxo = input.add_utxo(); - utxo->mutable_out_point()->set_hash(outpoint0.hash.data(), outpoint0.hash.size()); - utxo->mutable_out_point()->set_index(outpoint0.index); - utxo->mutable_out_point()->set_sequence(UINT32_MAX); - utxo->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo->set_amount(987654321); - - // Sign - auto signer = TransactionSigner(std::move(input)); - signer.transaction = unsignedTx; - signer.plan.utxos = {*utxo}; - auto result = signer.sign(); - 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; - signer.encodeTx(signedTx, 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 - 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 scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - Data scriptHash; - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHashHex] = scriptString; - - { - // 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 signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - // 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 - Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(335'790'000); - input.set_byte_fee(1); - input.set_to_address("THIS-IS-NOT-A-BITCOIN-ADDRESS"); - input.set_change_address("THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"); - - 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 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - Data scriptHash; - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = 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); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 174)); - } - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, Plan_10input_MaxAmount) { - auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; - auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; - - Proto::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); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[std::string(keyHash.begin(), keyHash.end())] = scriptString; - - auto utxo = input.add_utxo(); - utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); - utxo->set_amount(1'000'000 + i * 10'000); - auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); - std::reverse(hash.begin(), hash.end()); - utxo->mutable_out_point()->set_hash(hash.data(), hash.size()); - utxo->mutable_out_point()->set_index(0); - utxo->mutable_out_point()->set_sequence(UINT32_MAX); - } - - input.set_coin_type(TWCoinTypeBitcoin); - input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); - input.set_use_max_amount(true); - input.set_amount(2'000'000); - input.set_byte_fee(1); - input.set_to_address("bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"); - input.set_change_address(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 = parse_hex(ownPrivateKey); - input.add_private_key(privKey.data(), privKey.size()); - *input.mutable_plan() = plan.proto(); - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 - Proto::SigningInput input; - input.set_coin_type(coin); - input.set_hash_type(hashTypeForCoin(coin)); - input.set_amount(3'899'774); - input.set_use_max_amount(true); - input.set_byte_fee(1); - input.set_to_address("ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"); - input.set_change_address(ownAddress); - - auto privKey = parse_hex(ownPrivateKey); - input.add_private_key(privKey.data(), privKey.size()); - - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); - Data keyHash0; - EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); - EXPECT_EQ(hex(keyHash0), "5c74be45eb45a3459050667529022d9df8a1ecff"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[std::string(keyHash0.begin(), keyHash0.end())] = scriptString; - - auto utxo0 = input.add_utxo(); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(3'900'000); - auto hash0 = parse_hex("7051cd18189401a844abf0f9c67e791315c4c154393870453f8ad98a818efdb5"); - std::reverse(hash0.begin(), hash0.end()); - utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); - utxo0->mutable_out_point()->set_index(9); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX - 1); - - // set plan, to match real tx - input.mutable_plan()->set_available_amount(3'900'000); - input.mutable_plan()->set_amount(3'899'774); - input.mutable_plan()->set_fee(226); - input.mutable_plan()->set_change(0); - input.mutable_plan()->add_utxos(); - *input.mutable_plan()->mutable_utxos(0) = input.utxo(0); - EXPECT_TRUE(verifyPlan(input.plan(), {3'900'000}, 3'899'774, 226)); - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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 - Proto::SigningInput input; - input.set_coin_type(coin); - input.set_hash_type(hashTypeForCoin(coin)); - input.set_amount(1'200'000); - input.set_use_max_amount(false); - input.set_byte_fee(1); - input.set_to_address("ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"); - input.set_change_address(ownAddress); - - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); - Data keyHash0; - EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); - EXPECT_EQ(hex(keyHash0), "7b59c096c20fd9a273e240846b23276c69d35815"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[std::string(keyHash0.begin(), keyHash0.end())] = scriptString; - - auto utxo0 = input.add_utxo(); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(3'899'774); - auto hash0 = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); - std::reverse(hash0.begin(), hash0.end()); - 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); - - // 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 = parse_hex(ownPrivateKey); - input.add_private_key(privKey.data(), privKey.size()); - *input.mutable_plan() = plan.proto(); - - // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signer.encodeTx(signedTx, 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, 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, 0); - - 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.push_back(TransactionOutput(toAmount0, lockingScript0)); - auto lockingScript1 = Script::lockScriptForAddress(toAddress1, coin); - unsignedTx.outputs.push_back(TransactionOutput(toAmount1, lockingScript1)); - // change - auto lockingScript2 = Script::lockScriptForAddress(ownAddress, coin); - unsignedTx.outputs.push_back(TransactionOutput(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); // buildPayToWitnessProgram() - 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 - ); -} 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 cf426d39ff2..00000000000 --- a/tests/Bitcoin/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 -#include - -#include - -const char *address1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; -const char *address2 = "bc1qr583w2swedy2acd7rung055k8t3n7udp7vyzyg"; - -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())); -} - -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/TransactionPlanTests.cpp b/tests/Bitcoin/TransactionPlanTests.cpp deleted file mode 100644 index 913e96b47f7..00000000000 --- a/tests/Bitcoin/TransactionPlanTests.cpp +++ /dev/null @@ -1,571 +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 "TxComparisonHelper.h" -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" -#include "Bitcoin/TransactionPlan.h" -#include "Bitcoin/TransactionBuilder.h" -#include "Bitcoin/FeeCalculator.h" -#include "proto/Bitcoin.pb.h" -#include - -#include - -using namespace TW; -using namespace TW::Bitcoin; - - -TEST(TransactionPlan, OneTypical) { - auto utxos = buildTestUTXOs({100'000}); - auto byteFee = 1; - auto sigingInput = buildSigningInput(50'000, byteFee, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 147)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174); -} - -TEST(TransactionPlan, OneInsufficient) { - auto utxos = buildTestUTXOs({100'000}); - auto sigingInput = buildSigningInput(200'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // Max is returned - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 99'887, 113)); -} - -TEST(TransactionPlan, OneInsufficientEqual) { - auto utxos = buildTestUTXOs({100'000}); - auto sigingInput = buildSigningInput(100'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // Max is returned - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 99'887, 113)); -} - -TEST(TransactionPlan, OneInsufficientLower100) { - // requested is only slightly lower than avail, not enough for fee, cannot be satisfied - auto utxos = buildTestUTXOs({100'000}); - auto sigingInput = buildSigningInput(100'000 - 100, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); -} - -TEST(TransactionPlan, OneInsufficientLower170) { - // 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); - - EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); -} - -TEST(TransactionPlan, OneInsufficientLower300) { - auto utxos = buildTestUTXOs({100'000}); - auto sigingInput = buildSigningInput(100'000 - 300, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 300, 147)); -} - -TEST(TransactionPlan, OneMoreRequested) { - auto utxos = buildTestUTXOs({100'000}); - auto byteFee = 1; - auto sigingInput = buildSigningInput(150'000, byteFee, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // Max is returned - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 99'887, 113)); -} - -TEST(TransactionPlan, OneFitsExactly) { - auto utxos = buildTestUTXOs({100'000}); - auto byteFee = 1; - auto expectedFee = 147; - auto sigingInput = buildSigningInput(100'000 - 174, byteFee, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 174, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174); -} - -TEST(TransactionPlan, OneFitsExactlyHighFee) { - auto utxos = buildTestUTXOs({100'000}); - auto byteFee = 10; - auto expectedFee = 1470; - auto sigingInput = buildSigningInput(100'000 - 1740, byteFee, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 1740, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 1740); -} - -TEST(TransactionPlan, TwoFirstEnough) { - auto utxos = buildTestUTXOs({20'000, 80'000}); - auto sigingInput = buildSigningInput(15'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {20'000}, 15'000, 147)); -} - -TEST(TransactionPlan, TwoSecondEnough) { - auto utxos = buildTestUTXOs({20'000, 80'000}); - auto sigingInput = buildSigningInput(70'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {80'000}, 70'000, 147)); -} - -TEST(TransactionPlan, TwoBoth) { - auto utxos = buildTestUTXOs({20'000, 80'000}); - auto sigingInput = buildSigningInput(90'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {20'000, 80'000}, 90'000, 215)); -} - -TEST(TransactionPlan, TwoFirstEnoughButSecond) { - auto utxos = buildTestUTXOs({20'000, 22'000}); - auto sigingInput = buildSigningInput(18'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {22'000}, 18'000, 147)); -} - -TEST(TransactionPlan, ThreeNoDust) { - auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); - auto sigingInput = buildSigningInput(100'000 - 174 - 10, 1, utxos); - - // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 174 - 10, 215)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); - EXPECT_EQ(feeCalculator.calculate(2, 2, 1), 275); - - const auto dustLimit = 102; - // Now 100'000 fits with no dust - sigingInput = buildSigningInput(100'000 - 174 - dustLimit, 1, utxos); - txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 174 - dustLimit, 147)); - - // One more and we are over dust limit - sigingInput = buildSigningInput(100'000 - 174 - dustLimit + 1, 1, utxos); - txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 174 - dustLimit + 1, 215)); -} - -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 sigingInput = buildSigningInput(300'000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {100'000, 125'000, 150'000}, 300'000, 283)); -} - -TEST(TransactionPlan, NonMaxAmount) { - auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); - auto sigingInput = buildSigningInput(10000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {50000}, 10000, 147)); -} - -TEST(TransactionPlan, UnspentsInsufficient) { - auto utxos = buildTestUTXOs({4000, 4000, 4000}); - auto sigingInput = buildSigningInput(15000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // Max is returned - EXPECT_TRUE(verifyPlan(txPlan, {4000, 4000, 4000}, 11751, 249)); -} - -TEST(TransactionPlan, SelectionSuboptimal_ExtraSmallUtxo) { - // Solution found 4-in-2-out {500, 600, 800, 1000} avail 2900 txamount 1570 fee 702 change 628 - // Better solution: 3-in-2-out {600, 800, 1000} avail 2400 txamount 1570 fee 566 change 264 - // 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 1570 fee 838 change 892 - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1'000}); - auto byteFee = 2; - auto sigingInput = buildSigningInput(1'570, byteFee, utxos); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 702; - EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1'000}, 1'570, expectedFee)); - auto change = 2'900 - 1'570 - expectedFee; - auto firstUtxo = txPlan.utxos[0].amount(); - EXPECT_TRUE(change - 204 < txPlan.utxos[0].amount()); - EXPECT_EQ(change, 628); - 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. - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1'000}); - auto byteFee = 2; - auto sigingInput = buildSigningInput(1'775, byteFee, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {400, 500, 600, 800, 1000}, 1775, 838)); -} - -TEST(TransactionPlan, Inputs5_33Req19NoDustFee2) { - auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); - auto byteFee = 2; - auto sigingInput = buildSigningInput(19'000, byteFee, utxos); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 283*byteFee; - EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 204); -} - -TEST(TransactionPlan, Inputs5_33Req19Dust1Fee5) { - auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); - auto byteFee = 5; - auto sigingInput = buildSigningInput(19'000, byteFee, utxos); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 283*byteFee; - EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 510); -} - -TEST(TransactionPlan, Inputs5_33Req19Dust1Fee9) { - auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); - auto byteFee = 9; - auto sigingInput = buildSigningInput(19'000, byteFee, utxos); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 283*byteFee; - EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 918); -} - -TEST(TransactionPlan, Inputs5_33Req19Fee20) { - auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); - auto byteFee = 20; - auto sigingInput = buildSigningInput(19'000, byteFee, utxos); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); -} - -TEST(TransactionPlan, Inputs5_33Req13Fee20) { - auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); - auto byteFee = 20; - auto sigingInput = buildSigningInput(13'000, byteFee, utxos); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 283*byteFee; - EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 13'000, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 2040); -} - -TEST(TransactionPlan, NoUTXOs) { - auto utxos = buildTestUTXOs({}); - auto sigingInput = buildSigningInput(15000, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_missing_input_utxos)); -} - -TEST(TransactionPlan, CustomCase) { - auto utxos = buildTestUTXOs({794121, 2289357}); - auto byteFee = 61; - auto sigingInput = buildSigningInput(2287189, byteFee, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {794121, 2289357}, 2287189, 13115)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(2, 2, byteFee), 16775); -} - -TEST(TransactionPlan, Target0) { - auto utxos = buildTestUTXOs({2000, 3000}); - auto sigingInput = buildSigningInput(0, 1, utxos); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_zero_amount_requested)); -} - -TEST(TransactionPlan, MaxAmount) { - auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); - ASSERT_EQ(sumUTXOs(utxos), 39200); - auto byteFee = 40; - auto sigingInput = buildSigningInput(39200, byteFee, utxos, true); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4080); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 7240; - EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - expectedFee, expectedFee)); -} - -TEST(TransactionPlan, MaxAmountOne) { - auto utxos = buildTestUTXOs({10189534}); - auto sigingInput = buildSigningInput(100, 1, utxos, true); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 113; - EXPECT_TRUE(verifyPlan(txPlan, {10189534}, 10189534 - expectedFee, expectedFee)); -} - -TEST(TransactionPlan, AmountEqualsMaxButNotUseMax) { - // amount is set to max, but UseMax is not set --> Max is returned - auto utxos = buildTestUTXOs({10189534}); - auto sigingInput = buildSigningInput(10189534, 1, utxos, false); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {10189534}, 10189421, 113)); -} - -TEST(TransactionPlan, MaxAmountRequestedIsLower) { - auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); - ASSERT_EQ(sumUTXOs(utxos), 39200); - auto byteFee = 40; - auto sigingInput = buildSigningInput(10, byteFee, utxos, true); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4080); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 7240; - EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - expectedFee, expectedFee)); -} - -TEST(TransactionPlan, MaxAmountRequestedZero) { - auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); - ASSERT_EQ(sumUTXOs(utxos), 39200); - auto byteFee = 40; - auto sigingInput = buildSigningInput(0, byteFee, utxos, true); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4080); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 7240; - EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - expectedFee, expectedFee)); -} - -TEST(TransactionPlan, MaxAmountNoDustFee2) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - auto byteFee = 2; - auto sigingInput = buildSigningInput(100, byteFee, utxos, true); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 770; - EXPECT_TRUE(verifyPlan(txPlan, {400, 500, 600, 800, 1000}, 3'300 - expectedFee, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 204); - EXPECT_EQ(feeCalculator.calculate(5, 1, byteFee), 1096); -} - -TEST(TransactionPlan, MaxAmountDust1Fee4) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - auto byteFee = 4; - auto sigingInput = buildSigningInput(100, byteFee, utxos, true); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 1268; - EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1000}, 2'900 - expectedFee, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 408); - EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1784); -} - -TEST(TransactionPlan, MaxAmountDust2Fee5) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - auto byteFee = 5; - auto sigingInput = buildSigningInput(100, byteFee, utxos, true); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - auto expectedFee = 1245; - EXPECT_TRUE(verifyPlan(txPlan, {600, 800, 1000}, 2'400 - expectedFee, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 510); - EXPECT_EQ(feeCalculator.calculate(3, 1, byteFee), 1725); -} - -TEST(TransactionPlan, MaxAmountDustAllFee10) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - auto byteFee = 10; - auto sigingInput = buildSigningInput(100, byteFee, utxos, true); - - // UTXOs smaller than singleInputFee are not included - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 1020); -} - -TEST(TransactionPlan, One_MaxAmount_FeeMoreThanAvailable) { - auto utxos = buildTestUTXOs({170}); - auto byteFee = 1; - auto expectedFee = 113; - auto sigingInput = buildSigningInput(300, byteFee, utxos, true); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // Fee is reduced to availableAmount - EXPECT_TRUE(verifyPlan(txPlan, {170}, 170 - expectedFee, expectedFee)); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 143); -} - -TEST(TransactionPlan, MaxAmountDoge) { - auto utxos = buildTestUTXOs({Amount(100000000), Amount(2000000000), Amount(200000000)}); - ASSERT_EQ(sumUTXOs(utxos), Amount(2300000000)); - auto sigingInput = buildSigningInput(Amount(2300000000), 100, utxos, true, TWCoinTypeDogecoin); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {100000000, 2000000000, 200000000}, 2299951200, 48800)); -} - -TEST(TransactionPlan, AmountDecred) { - auto utxos = buildTestUTXOs({Amount(39900000)}); - auto sigingInput = buildSigningInput(Amount(10000000), 10, utxos, false, TWCoinTypeDecred); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - EXPECT_TRUE(verifyPlan(txPlan, {39900000}, 10000000, 2540)); -} - -TEST(TransactionPlan, LotsofUtxosNonmax) { - const auto n = 1000; - const auto byteFee = 10; - std::vector values; - uint64_t valueSum = 0; - for (int i = 0; i < n; ++i) { - const auto val = (i + 1) * 100; - values.push_back(val); - valueSum += val; - } - const auto requestedAmount = valueSum / 2 + 123; - - auto utxos = buildTestUTXOs(values); - auto sigingInput = buildSigningInput(requestedAmount, byteFee, utxos, false, TWCoinTypeBitcoin); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // expected result: 298 utxos, with amounts 70300,70400,70500,...,100000. - std::vector subset; - uint64_t subsetSum = 0; - for (int i = n - 298; i < n; ++i) { - const auto val = (i + 1) * 100; - subset.push_back(val); - subsetSum += val; - } - EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 203'450)); -} - -TEST(TransactionPlan, LotsofUtxosMax) { - const auto n = 1000; - const auto byteFee = 10; - std::vector values; - uint64_t valueSum = 0; - for (int i = 0; i < n; ++i) { - const auto val = (i + 1) * 100; - values.push_back(val); - valueSum += val; - } - - // Use Ravencoin, because of faster non-segwit estimation, and one original issues was with this coin. - auto utxos = buildTestUTXOs(values); - auto sigingInput = buildSigningInput(valueSum, byteFee, utxos, true, TWCoinTypeRavencoin); - - auto txPlan = TransactionBuilder::plan(sigingInput); - - // a few smallest UTXOs are filtered out - const auto dustLimit = byteFee * 148; - std::vector filteredValues; - uint64_t filteredValueSum = 0; - for (int i = 0; i < n; ++i) { - const auto val = (i + 1) * 100; - if (val > dustLimit) { - filteredValues.push_back(val); - filteredValueSum += val; - } - } - EXPECT_EQ(valueSum, 50'050'000); - EXPECT_EQ(dustLimit, 1480); - EXPECT_EQ(filteredValues.size(), 986); - EXPECT_EQ(filteredValueSum, 50'039'500); - EXPECT_TRUE(verifyPlan(txPlan, filteredValues, 48'579'780, 1'459'720)); -} diff --git a/tests/Bitcoin/TxComparisonHelper.cpp b/tests/Bitcoin/TxComparisonHelper.cpp deleted file mode 100644 index 0dbf5d76b3a..00000000000 --- a/tests/Bitcoin/TxComparisonHelper.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 "TxComparisonHelper.h" - -#include - -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" -#include "proto/Bitcoin.pb.h" -#include "Data.h" -#include "PrivateKey.h" -#include "HexCoding.h" -#include "BinaryCoding.h" - -#include -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - -auto emptyTxOutPoint = OutPoint(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"), 0); - -Proto::UnspentTransaction buildTestUTXO(int64_t amount) { - Proto::UnspentTransaction utxo; - utxo.set_amount(amount); - const auto& outPoint = emptyTxOutPoint; - utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); - utxo.mutable_out_point()->set_index(outPoint.index); - utxo.mutable_out_point()->set_sequence(UINT32_MAX); - auto utxo1Script = parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo.set_script(utxo1Script.data(), utxo1Script.size()); - return utxo; -} - -std::vector buildTestUTXOs(const std::vector& amounts) { - std::vector utxos; - for (auto it = amounts.begin(); it != amounts.end(); it++) { - utxos.push_back(buildTestUTXO(*it)); - } - return utxos; -} - -Proto::SigningInput buildSigningInput(Amount amount, int byteFee, const std::vector& utxos, bool useMaxAmount, enum TWCoinType coin) { - Proto::SigningInput input; - input.set_amount(amount); - input.set_byte_fee(byteFee); - input.set_use_max_amount(useMaxAmount); - input.set_coin_type(coin); - - auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); - assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - input.add_private_key(utxoKey.bytes.data(), utxoKey.bytes.size()); - - *input.mutable_utxo() = { utxos.begin(), utxos.end() }; - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - return input; -} - -int64_t sumUTXOs(const std::vector& utxos) { - int64_t s = 0u; - for (auto& utxo: utxos) { - s += utxo.amount(); - } - return s; -} - -bool verifySelectedUTXOs(const std::vector& selected, const std::vector& expectedAmounts) { - bool ret = true; - if (selected.size() != expectedAmounts.size()) { - ret = false; - std::cerr << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; - } - for (auto i = 0; i < selected.size() && i < expectedAmounts.size(); ++i) { - if (expectedAmounts[i] != selected[i].amount()) { - ret = false; - std::cerr << "Wrong UTXOs amount, pos " << i << " amount " << selected[i].amount() << " expected " << expectedAmounts[i] << std::endl; - } - } - return ret; -} - -bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmounts, int64_t outputAmount, int64_t fee, Common::Proto::SigningError error) { - bool ret = true; - if (!verifySelectedUTXOs(plan.utxos, utxoAmounts)) { - ret = false; - } - if (plan.amount != outputAmount) { - ret = false; - std::cerr << "Mismatch in amount, act " << plan.amount << ", exp " << outputAmount << std::endl; - } - if (plan.fee != fee) { - ret = false; - 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]; - } - if (plan.availableAmount != sumExpectedUTXOs) { - ret = false; - std::cerr << "Mismatch in availableAmount, act " << plan.availableAmount << ", exp " << sumExpectedUTXOs << std::endl; - } - int64_t expectedChange = sumExpectedUTXOs - outputAmount - fee; - if (plan.change != expectedChange) { - ret = false; - std::cerr << "Mismatch in change, act " << plan.change << ", exp " << expectedChange << std::endl; - } - if (plan.error != Common::Proto::OK) { - if (error != Common::Proto::OK) { - if (plan.error != error) { - ret = false; - std::cerr << "Unexpected error, act " << std::to_string(plan.error) << ", exp " << std::to_string(plan.error) << std::endl; - } - } else { - ret = false; - std::cerr << "Unexpected error " << std::to_string(plan.error) << std::endl; - } - } else { - if (error != Common::Proto::OK) { - ret = false; - std::cerr << "Missing expected error " << std::to_string(plan.error) << std::endl; - } - } - return ret; -} - -bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2) { - return s1.virtualBytes == s2.virtualBytes && s1.segwit == s2.segwit && s1.nonSegwit == s2.nonSegwit; -} - -EncodedTxSize getEncodedTxSize(const Transaction& tx) { - EncodedTxSize size; - { // full segwit size - Data data; - tx.encode(data, Transaction::SegwitFormatMode::Segwit); - size.segwit = data.size(); - } - { // non-segwit - Data data; - tx.encode(data, Transaction::SegwitFormatMode::NonSegwit); - size.nonSegwit = data.size(); - } - int64_t witnessSize = 0; - { // double check witness part: witness plus 2 bytes is the difference between segwit and non-segwit size - Data data; - tx.encodeWitness(data); - witnessSize = data.size(); - assert(size.segwit - size.nonSegwit == 2 + witnessSize); - } - // compute virtual size: 3/4 of (smaller) non-segwit + 1/4 of segwit size - 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); - assert(size.virtualBytes == vSize2); - return size; -} - -bool validateEstimatedSize(const Transaction& tx, int smallerTolerance, int biggerTolerance) { - if (tx.previousEstimatedVirtualSize == 0) { - // no estimated size, do nothing - return true; - } - bool ret = true; - auto estSize = tx.previousEstimatedVirtualSize; - uint64_t vsize = getEncodedTxSize(tx).virtualBytes; - int64_t diff = estSize - vsize; - if (diff < smallerTolerance) { - ret = false; - std::cerr << "Estimated size too small! " << std::to_string(estSize) << " vs. " << std::to_string(vsize) << std::endl; - } - if (diff > biggerTolerance) { - ret = false; - std::cerr << "Estimated size too big! " << std::to_string(estSize) << " vs. " << std::to_string(vsize) << std::endl; - } - return ret; -} - -void prettyPrintScript(const Script& script) { - Data data; - encodeVarInt(script.bytes.size(), data); - std::cout << " \"" << hex(data) << "\""; - std::cout << " \"" << hex(script.bytes) << "\""; -} - -void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { - Data data; - encode32LE(tx.version, data); - std::cout << " \"" << hex(data) << "\" // version\n"; - - if (useWitnessFormat) { - std::cout << " \"0001\" // marker & flag\n"; - } - - // txins - data.clear(); - encodeVarInt(tx.inputs.size(), data); - std::cout << " \"" << hex(data) << "\" // inputs\n"; - for (auto& input: tx.inputs) { - auto& outpoint = reinterpret_cast(input.previousOutput); - std::cout << " \"" << hex(outpoint.hash) << "\""; - data.clear(); - encode32LE(outpoint.index, data); - std::cout << " \"" << hex(data) << "\""; - prettyPrintScript(input.script); - data.clear(); - encode32LE(input.sequence, data); - std::cout << " \"" << hex(data) << "\"\n"; - } - - // txouts - data.clear(); - encodeVarInt(tx.outputs.size(), data); - std::cout << " \"" << hex(data) << "\" // outputs\n"; - for (auto& output: tx.outputs) { - data.clear(); - encode64LE(output.value, data); - std::cout << " \"" << hex(data) << "\""; - prettyPrintScript(output.script); - std::cout << "\n"; - } - - if (useWitnessFormat) { - std::cout << " // witness\n"; - for (auto& input: tx.inputs) { - data.clear(); - encodeVarInt(input.scriptWitness.size(), data); - std::cout << " \"" << hex(data) << "\"\n"; - for (auto& item: input.scriptWitness) { - data.clear(); - encodeVarInt(item.size(), data); - std::cout << " \"" << hex(data) << "\""; - std::cout << " \"" << hex(item) << "\"\n"; - } - } - } - - data.clear(); - encode32LE(tx.lockTime, data); // nLockTime - std::cout << " \"" << hex(data) << "\" // nLockTime\n"; - std::cout << "\n"; -} diff --git a/tests/Bitcoin/TxComparisonHelper.h b/tests/Bitcoin/TxComparisonHelper.h deleted file mode 100644 index 61ca78ec53a..00000000000 --- a/tests/Bitcoin/TxComparisonHelper.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 "Bitcoin/Amount.h" -#include "Bitcoin/Transaction.h" -#include "Bitcoin/TransactionPlan.h" -#include "proto/Bitcoin.pb.h" -#include - -#include -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - -/// Build a dummy UTXO with the given amount -Proto::UnspentTransaction buildTestUTXO(int64_t amount); - -/// Build a set of dummy UTXO with the given amounts -std::vector buildTestUTXOs(const std::vector& amounts); - -Proto::SigningInput buildSigningInput(Amount amount, int byteFee, const std::vector& utxos, - bool useMaxAmount = false, enum TWCoinType coin = TWCoinTypeBitcoin); - -/// Compare a set of selected UTXOs to the expected set of amounts. -/// Returns false on mismatch, and error is printed (stderr). -bool verifySelectedUTXOs(const std::vector& 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 std::vector& 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/Bitcoin/UnspentSelectorTests.cpp b/tests/Bitcoin/UnspentSelectorTests.cpp deleted file mode 100644 index 4971249ca3b..00000000000 --- a/tests/Bitcoin/UnspentSelectorTests.cpp +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "TxComparisonHelper.h" -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" -#include "Bitcoin/UnspentSelector.h" -#include "proto/Bitcoin.pb.h" - -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - -TEST(BitcoinUnspentSelector, SelectUnspents1) { - auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 11000, 12000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 5000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {11000})); -} - -TEST(BitcoinUnspentSelector, SelectUnspents2) { - auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 10000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {50000})); -} - -TEST(BitcoinUnspentSelector, SelectUnspents3) { - auto utxos = buildTestUTXOs({4000, 2000, 5000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 6000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {4000, 5000})); -} - -TEST(BitcoinUnspentSelector, SelectUnspents4) { - auto utxos = buildTestUTXOs({40000, 30000, 30000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 50000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {30000, 40000})); -} - -TEST(BitcoinUnspentSelector, SelectUnspents5) { - auto utxos = buildTestUTXOs({1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 28000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {6000, 7000, 8000, 9000})); -} - -TEST(BitcoinUnspentSelector, SelectUnspentsInsufficient) { - auto utxos = buildTestUTXOs({4000, 4000, 4000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 15000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectCustomCase) { - auto utxos = buildTestUTXOs({794121, 2289357}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 2287189, 61); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {794121, 2289357})); -} - -TEST(BitcoinUnspentSelector, SelectNegativeNoUTXOs) { - auto utxos = buildTestUTXOs({}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 100000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectNegativeTarget0) { - auto utxos = buildTestUTXOs({100'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 0, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectOneTypical) { - auto utxos = buildTestUTXOs({100'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 50'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); -} - -TEST(BitcoinUnspentSelector, SelectOneInsufficient) { - auto utxos = buildTestUTXOs({100'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 200'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectOneInsufficientEqual) { - auto utxos = buildTestUTXOs({100'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 100'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectOneInsufficientHigher) { - auto utxos = buildTestUTXOs({100'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 99'900, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectOneFitsExactly) { - auto utxos = buildTestUTXOs({100'000}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto expectedFee = 174; - auto selected = selector.select(utxos, 100'000 - expectedFee, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); - - 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 - selected = selector.select(utxos, 100'000 - expectedFee + 1, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectOneFitsExactlyHighfee) { - auto utxos = buildTestUTXOs({100'000}); - - const auto byteFee = 10; - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto expectedFee = 1740; - auto selected = selector.select(utxos, 100'000 - expectedFee, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); - - 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 - selected = selector.select(utxos, 100'000 - expectedFee + 1, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectThreeNoDust) { - auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto selected = selector.select(utxos, 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; - // Now 100'000 fits with no dust - selected = selector.select(utxos, 100'000 - 174 - dustLimit, 1); - EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); - - // One more and we are over dust limit - selected = selector.select(utxos, 100'000 - 174 - dustLimit + 1, 1); - EXPECT_TRUE(verifySelectedUTXOs(selected, {75'000, 100'000})); -} - -TEST(BitcoinUnspentSelector, SelectTwoFirstEnough) { - auto utxos = buildTestUTXOs({20'000, 80'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 15'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {20'000})); -} - -TEST(BitcoinUnspentSelector, SelectTwoSecondEnough) { - auto utxos = buildTestUTXOs({20'000, 80'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 70'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {80'000})); -} - -TEST(BitcoinUnspentSelector, SelectTwoBoth) { - auto utxos = buildTestUTXOs({20'000, 80'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 90'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {20'000, 80'000})); -} - -TEST(BitcoinUnspentSelector, SelectTwoFirstEnoughButSecond) { - auto utxos = buildTestUTXOs({20'000, 22'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 18'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {22'000})); -} - -TEST(BitcoinUnspentSelector, SelectTenThree) { - auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); - - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 300'000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); -} - -TEST(BitcoinUnspentSelector, 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 = UnspentSelector(feeCalculator); - const auto dustLimit = 102; - auto selected = selector.select(utxos, 375'000 - 376 - dustLimit, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); - - ASSERT_EQ(feeCalculator.calculate(3, 2, 1), 376); - - // one more, and it's too much - selected = selector.select(utxos, 375'000 - 376 - dustLimit + 1, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {7'000, 100'000, 125'000, 150'000})); -} - -TEST(BitcoinUnspentSelector, SelectMaxAmountOne) { - auto utxos = buildTestUTXOs({10189534}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto selected = selector.selectMaxAmount(utxos, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); - - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); -} - -TEST(BitcoinUnspentSelector, SelectAllAvail) { - auto utxos = buildTestUTXOs({10189534}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto selected = selector.select(utxos, 10189534 - 226, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); - - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); -} - -TEST(BitcoinUnspentSelector, SelectMaxAmount5of5) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto byteFee = 1; - auto selected = selector.selectMaxAmount(utxos, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {400, 500, 600, 800, 1000})); - - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 102); - EXPECT_EQ(feeCalculator.calculate(5, 1, byteFee), 548); -} - -TEST(BitcoinUnspentSelector, SelectMaxAmount4of5) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto byteFee = 4; - auto selected = selector.selectMaxAmount(utxos, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {500, 600, 800, 1000})); - - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 408); - EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1784); -} - -TEST(BitcoinUnspentSelector, SelectMaxAmount1of5) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto byteFee = 8; - auto selected = selector.selectMaxAmount(utxos, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {1000})); - - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 816); - EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 1144); -} - -TEST(BitcoinUnspentSelector, SelectMaxAmountNone) { - auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto byteFee = 10; - auto selected = selector.selectMaxAmount(utxos, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); - - EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 1020); -} - -TEST(BitcoinUnspentSelector, SelectMaxAmountNoUTXOs) { - auto utxos = buildTestUTXOs({}); - - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = UnspentSelector(feeCalculator); - auto selected = selector.selectMaxAmount(utxos, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} - -TEST(BitcoinUnspentSelector, SelectZcashUnspents) { - auto utxos = buildTestUTXOs({100000, 2592, 73774}); - - auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); - auto selected = selector.select(utxos, 10000, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {73774})); -} - -TEST(BitcoinUnspentSelector, SelectGroestlUnspents) { - auto utxos = buildTestUTXOs({499971976}); - - auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); - auto selected = selector.select(utxos, 499951976, 1, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {499971976})); -} - -TEST(BitcoinUnspentSelector, SelectZcashMaxAmount) { - auto utxos = buildTestUTXOs({100000, 2592, 73774}); - - auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); - auto selected = selector.selectMaxAmount(utxos, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {100000, 2592, 73774})); -} - -TEST(BitcoinUnspentSelector, SelectZcashMaxUnspents2) { - auto utxos = buildTestUTXOs({100000, 2592, 73774}); - - auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); - auto selected = selector.select(utxos, 176366 - 6, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); -} diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/BitcoinCash/TWBitcoinCashTests.cpp deleted file mode 100644 index 30536e06cea..00000000000 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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/SigHashType.h" -#include "HexCoding.h" -#include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace TW; -using namespace TW::Bitcoin; - -TEST(BitcoinCash, Address) { - EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); -} - -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); -} - -TEST(BitcoinCash, LegacyToCashAddr) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); - auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), 0)); - auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); - assertStringsEqual(addressString, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); - - auto cashAddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeBitcoinCash)); - auto cashAddressString = WRAPS(TWAnyAddressDescription(cashAddress.get())); - assertStringsEqual(cashAddressString, "bitcoincash:qruxj7zq6yzpdx8dld0e9hfvt7u47zrw9gfr5hy0vh"); -} - -TEST(BitcoinCash, LockScript) { - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoinCash)); - auto data = WRAPD(TWAnyAddressData(address.get())); - auto rawData = WRAPD(TWDataCreateWithSize(0)); - TWDataAppendByte(rawData.get(), 0x00); - TWDataAppendData(rawData.get(), data.get()); - - auto legacyAddress = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(rawData.get())); - auto legacyString = WRAPS(TWBitcoinAddressDescription(legacyAddress.get())); - assertStringsEqual(legacyString, "1AwDXywmyhASpCCFWkqhySgZf8KiswFoGh"); - - auto keyHash = WRAPD(TWDataCreateWithBytes(legacyAddress.get()->impl.bytes.data() + 1, 20)); - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(keyHash.get())); - auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); - assertHexEqual(scriptData, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); - - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l").get(), TWCoinTypeBitcoinCash)); - auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); - assertHexEqual(scriptData2, "a914b9604b7820876bc510009b8247316c4b801aff8a87"); -} - -TEST(BitcoinCash, 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 xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeBitcoinCash, TWHDVersionXPRV)); - auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeBitcoinCash, TWHDVersionXPUB)); - - assertStringsEqual(xprv, "xprv9yEvwSfPanK5gLYVnYvNyF2CEWJx1RsktQtKDeT6jnCnqASBiPCvFYHFSApXv39bZbF6hRaha1kWQBVhN1xjo7NHuhAn5uUfzy79TBuGiHh"); - assertStringsEqual(xpub, "xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw"); -} - -TEST(BitcoinCash, DeriveFromXPub) { - auto xpub = STRING("xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw"); - auto pubKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get())); - auto pubKey9 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/9").get())); - - auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2.get(), TWCoinTypeBitcoinCash)); - auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); - - auto address9 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey9.get(), TWCoinTypeBitcoinCash)); - auto address9String = WRAPS(TWAnyAddressDescription(address9.get())); - - assertStringsEqual(address2String, "bitcoincash:qq4cm0hcc4trsj98v425f4ackdq7h92rsy6zzstrgy"); - assertStringsEqual(address9String, "bitcoincash:qqyqupaugd7mycyr87j899u02exc6t2tcg9frrqnve"); -} - -TEST(BitcoinCash, SignTransaction) { - const int64_t amount = 600; - - // Transaction on Bitcoin Cash Mainnet - // https://blockchair.com/bitcoin-cash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 - - auto input = Proto::SigningInput(); - input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinCash)); - input.set_amount(amount); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - - auto hash0 = DATA("e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05"); - auto utxo0 = input.add_utxo(); - utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); - utxo0->mutable_out_point()->set_index(2); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - utxo0->set_amount(5151); - auto script0 = parse_hex("76a914aff1e0789e5fe316b729577665aa0a04d5b0f8c788ac"); - utxo0->set_script(script0.data(), script0.size()); - - auto utxoKey0 = DATA("7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384"); - input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); - - // Sign - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeBitcoinCash); - - 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); - ASSERT_EQ(hex(output.encoded()), - "01000000" - "01" - "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" "02000000" "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" "ffffffff" - "02" - "5802000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "00000000"); -} 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/TWAddressTests.cpp b/tests/BitcoinGold/TWAddressTests.cpp deleted file mode 100644 index 77c192461bb..00000000000 --- a/tests/BitcoinGold/TWAddressTests.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 "Bitcoin/Address.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "HexCoding.h" -#include "Coin.h" -#include -#include - -using namespace TW; - -const char *PRIVATE_KEY = "CA6D1402199530A5D610A01A53505B6A344CF61B0CCB2902D5AEFBEA63C274BB"; -const char *ADDRESS = "GSGUyooxtCUVBonYV8AANp7FvKy3WTvpMR"; -const char *FAKEADDRESS = "GSGUyooxtCUVBonYV9AANp7FvKy3WTvpMR"; - -TEST(TWBitcoinGoldAddress, Valid) { - ASSERT_TRUE(Bitcoin::Address::isValid(std::string(ADDRESS))); - ASSERT_FALSE(Bitcoin::Address::isValid(std::string(FAKEADDRESS))); -} - -TEST(TWBitcoinGoldAddress, PubkeyToAddress) { - const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY)); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - - /// construct with public key - auto address = Bitcoin::Address(PublicKey(publicKey), p2pkhPrefix(TWCoinTypeBitcoinGold)); - ASSERT_EQ(address.string(), ADDRESS); -} 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 c624b0a90de..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, 0, "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/CMakeLists.txt b/tests/CMakeLists.txt index cfde8ca9737..87ec8f86384 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + enable_testing() # Prevent overriding the parent project's compiler/linker @@ -6,7 +10,7 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Add googletest directly to our build. This defines # the gtest and gtest_main targets. -add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.10.0 +add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-1.16.0 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) @@ -15,15 +19,19 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1. ##include_directories(${Protobuf_INCLUDE_DIRS}) # Test executable -file(GLOB_RECURSE test_sources *.cpp **/*.cpp) +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 e08e4a46cc9..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://explorer2.callisto.network/tx/t123"); - assertStringsEqual(accUrl, "https://explorer2.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 d798fb66f90..00000000000 --- a/tests/Cardano/AddressTests.cpp +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 - -#include - -using namespace TW; -using namespace TW::Cardano; -using namespace std; - - -TEST(CardanoAddress, Validation) { - // valid V3 address - ASSERT_TRUE(AddressV3::isValid("addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe")); - ASSERT_TRUE(AddressV3::isValid("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j")); - ASSERT_TRUE(AddressV3::isValid("ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); - ASSERT_TRUE(AddressV3::isValid("ca1qsqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jq2f29vkz6t30xqcnyve5x5mrwwpe8ganc0f78aqyzsjrg3z5v36gguhxny")); - - // valid V2 address - ASSERT_TRUE(AddressV3::isValid("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); - ASSERT_TRUE(AddressV3::isValid("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); - - // valid V1 address - ASSERT_TRUE(AddressV3::isValid("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ")); - ASSERT_TRUE(AddressV3::isValid("DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di")); - - // invalid checksum V3 - ASSERT_FALSE(AddressV3::isValid("PREFIX1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); - // invalid checksum V2 - ASSERT_FALSE(AddressV3::isValid("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm")); - // random - ASSERT_FALSE(AddressV3::isValid("hasoiusaodiuhsaijnnsajnsaiussai")); - // empty - ASSERT_FALSE(AddressV3::isValid("")); -} - -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("ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s"); - EXPECT_EQ(address.string("ca"), "ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s"); - EXPECT_EQ(address.string("ca"), "ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s"); - EXPECT_EQ(AddressV3::Discrim_Production, address.discrimination); - EXPECT_EQ(AddressV3::Kind_Single, address.kind); - EXPECT_EQ("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", hex(address.key1)); - EXPECT_EQ("", hex(address.groupKey)); - } - { - // group addr - auto address = AddressV3("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ(address.string(), "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ(AddressV3::Discrim_Test, address.discrimination); - EXPECT_EQ(AddressV3::Kind_Group, address.kind); - EXPECT_EQ("4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295", hex(address.key1)); - EXPECT_EQ("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", hex(address.groupKey)); - } -} - -TEST(CardanoAddress, MnemonicToAddressV2) { - { - // Test from cardano-crypto.js; Test wallet - auto mnemonic = "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh"; - auto wallet = HDWallet(mnemonic, ""); - - PrivateKey masterPrivKey = wallet.getMasterKey(TWCurve::TWCurveED25519Extended); - PrivateKey masterPrivKeyExt = wallet.getMasterKeyExtension(TWCurve::TWCurveED25519Extended); - ASSERT_EQ("a018cd746e128a0be0782b228c275473205445c33b9000a33dd5668b430b5744", hex(masterPrivKey.bytes)); - ASSERT_EQ("26877cfe435fddda02409b839b7386f3738f10a30b95a225f4b720ee71d2505b", hex(masterPrivKeyExt.bytes)); - - { - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", 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("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", 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("addr1sjkw630aatyg273m9cpgezvs2unf6xrtw0z7udhguh7ednkhf9p0jduldrg5qsnaz99e3sl4f8t56w0hs0zhql9lacr63mx693ppjw2r5nwehs", 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("addr1sng939f9el5mnsj4l30kk2f02ea63rwhny5pa69masam4xtsmp5naq6lks0p7pzkn35z7juyd7hhk3zc8p9dc736pu4nzhyy6fusxapa9v5h5c", 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("addr1ssf3e4w2g8gpqlewnt0a4t9kwvdwhxyaaqu7tmru20zgakwf2mdu3jamu779gr3085lykk7r0q8t6lf6p2vfj7u9ma2s7a748vn0se2gxym6da", 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("addr1sn8hmvmhxw6926mgz3fn5qp205wmu42adg8uehnce3nfr4umecm3mfqmxy4jyl5xewag3kq52vulqpgt386atv3v5upz9j0rl4cc42m707gewk", 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("addr1sn32yvavqtnzpqggxf9aa0yypng80gr3anfpwppz8dhztx4cevzp5v6nf40c6d8v6z70fcd76634sdlyfpfpk6d3ya84czk83jlze676vmpf37", 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, FromPublicKeyInternalV3) { - // tests from chain-lib - { - auto address = AddressV3::createSingle(AddressV3::Discrim_Production, parse_hex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")); - auto bech = address.string("ca"); - EXPECT_EQ("ca1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s", bech); - EXPECT_EQ("amaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsa", address.stringBase32()); - } - { - auto address = AddressV3::createGroup(AddressV3::Discrim_Production, - parse_hex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), - parse_hex("292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748") - ); - auto bech = address.string("ca"); - EXPECT_EQ("ca1qsqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jq2f29vkz6t30xqcnyve5x5mrwwpe8ganc0f78aqyzsjrg3z5v36gguhxny", bech); - EXPECT_EQ("aqaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsakjkfmwc2lrpgaytemzugu3doobzhi5typj6h5aecqsdircumr2i", address.stringBase32()); - } - { - auto address = AddressV3::createGroup(AddressV3::Discrim_Test, - parse_hex("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), - parse_hex("292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748") - ); - auto bech = address.string("ca"); - EXPECT_EQ("ca1ssqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jq2f29vkz6t30xqcnyve5x5mrwwpe8ganc0f78aqyzsjrg3z5v36gjdetkp", bech); - EXPECT_EQ("qqaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsakjkfmwc2lrpgaytemzugu3doobzhi5typj6h5aecqsdircumr2i", address.stringBase32()); - } - { - auto address = AddressV3::createAccount(AddressV3::Discrim_Test, parse_hex("292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748")); - auto bech = address.string("ca"); - EXPECT_EQ("ca1s55j52ev95hz7vp3xgengdfkxuurjw3m8s7nu06qg9pyx3z9ger5samu4rv", bech); - EXPECT_EQ("quusukzmfuxc6mbrgiztinjwg44dsor3hq6t4p2aifbegrcfizduq", address.stringBase32()); - } - // end of chain-lib - { - auto address = AddressV3::createGroup(AddressV3::Discrim_Test, - parse_hex("4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295"), - parse_hex("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2") - ); - auto bech = address.string("addr"); - EXPECT_EQ("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j", bech); - } -} - -TEST(CardanoAddress, FromPublicKeyV2) { - { - // caradano-crypto.js test - auto publicKey = PublicKey(parse_hex("e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); - } - { - // Adalite test account addr0 - auto publicKey = PublicKey(parse_hex("57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); - } - { - // Adalite test account addr1 - auto publicKey = PublicKey(parse_hex("25af99056d600f7956312406bdd1cd791975bb1ae91c9d034fc65f326195fcdb247ee97ec351c0820dd12de4ca500232f73a35fe6f86778745bcd57f34d1048d"), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV"); - } - { - // Play1 addr0 - auto publicKey = PublicKey(parse_hex("7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"), 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") - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); - } - { - // mnemonic Play1, addr0 - auto privateKey = PrivateKey( - parse_hex("a089c9423100960440ccd5b7adbd202d1ab1993a7bb30fc88b287d94016df247"), - parse_hex("da86a87f08fb15de1431a6c0ccd5ebf51c3bee81f7eaf714801bbbe4d903154a"), - parse_hex("e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2") - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"); - 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") - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); - } -} - -TEST(CardanoAddress, PrivateKeyExtended) { - // check extended key lengths, private key 3x32 bytes, public key 64 bytes - auto privateKeyExt = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") - ); - auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(64, 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, DataV3) { - // group addr - auto address = AddressV3("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ("4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295", hex(address.key1)); - EXPECT_EQ("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", hex(address.groupKey)); - Data data = address.data(); - EXPECT_EQ( - "0104" - "20" "4dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295" - "20" "6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", - hex(data) - ); -} - -TEST(CardanoAddress, FromDataV3) { - Data data = parse_hex("0104204dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295206d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2"); - auto address = AddressV3(data); - EXPECT_EQ(address.string(), "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); - EXPECT_EQ("6d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2", hex(address.groupKey)); -} - -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("addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe"); - 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/TWCardanoAddressTests.cpp b/tests/Cardano/TWCardanoAddressTests.cpp deleted file mode 100644 index f920bffeb33..00000000000 --- a/tests/Cardano/TWCardanoAddressTests.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 -#include -#include -#include -#include "../interface/TWTestUtilities.h" -#include "PrivateKey.h" - -#include - -TEST(TWCardano, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4").get())); - ASSERT_NE(nullptr, privateKey.get()); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Extended(privateKey.get())); - ASSERT_NE(nullptr, publicKey.get()); - ASSERT_EQ(64, publicKey.get()->impl.bytes.size()); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeCardano)); - auto addressString = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressString, "addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668"); - - auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668").get(), TWCoinTypeCardano)); - ASSERT_NE(nullptr, address2.get()); - auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); - assertStringsEqual(address2String, "addr1s3tl64970vuthz2j0qkz7kd2ya5j3fxuhdnv333vu38e6c37e4dq80ek4raf7hs3adag2tzpuxz7895a2x8xde5f8jqa8lrjyuqfj5k50pm668"); - - ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); -} - -TEST(TWCardano, SigningNotImplemented) { - // not implemented, returns empty data - auto result = WRAPD(TWAnySignerSign(WRAPD(TWDataCreateWithSize(0)).get(), TWCoinType::TWCoinTypeCardano)); - EXPECT_EQ(TWDataSize(result.get()), 0); -} diff --git a/tests/Cardano/TWCoinTypeTests.cpp b/tests/Cardano/TWCoinTypeTests.cpp deleted file mode 100644 index bf21fe8c6e0..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://shelleyexplorer.cardano.org/tx/b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3"); - assertStringsEqual(accUrl, "https://shelleyexplorer.cardano.org/address/DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC"); - assertStringsEqual(id, "cardano"); - assertStringsEqual(name, "Cardano"); -} diff --git a/tests/CborTests.cpp b/tests/CborTests.cpp deleted file mode 100644 index 2c07cc1b1dc..00000000000 --- a/tests/CborTests.cpp +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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, 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/CoinAddressDerivationTests.cpp b/tests/CoinAddressDerivationTests.cpp deleted file mode 100644 index fee1a91a496..00000000000 --- a/tests/CoinAddressDerivationTests.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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); - - 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(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(TWCoinTypeZcoin, 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(TWCoinTypeTON, privateKey), "EQDww_SOMx3CT30jzaDPDuWkGaHjo8zQULH75spBGszlINDK"); - 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), "addr1sn0sqpku8yfj2dazh7czyspcd5flzkzu6x9wqt0lsn4wfvds9sg3s3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxdnpy03"); - 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"); -} - -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); -} - -TEST(Coin, SupportedCoins) { - const auto coinTypes = TW::getCoinTypes(); - for (auto c: coinTypes) { - const auto similarTypes = TW::getSimilarCoinTypes(c); - // For all coins, supported coins should include this coin as well - EXPECT_TRUE(std::find(similarTypes.begin(), similarTypes.end(), c) != similarTypes.end()); - } -} - -} // namespace TW diff --git a/tests/Cosmos/AddressTests.cpp b/tests/Cosmos/AddressTests.cpp deleted file mode 100644 index 06a2f7e5998..00000000000 --- a/tests/Cosmos/AddressTests.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 "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "Cosmos/Address.h" - -#include -#include - -namespace TW::Cosmos { - -TEST(Address, Valid) { - ASSERT_TRUE(Address::isValid("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2")); -} - -TEST(Address, Invalid) { - ASSERT_FALSE(Address::isValid("bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2")); -} - -TEST(Address, 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"); -} - -TEST(Address, Cosmos_Valid) { - ASSERT_TRUE(Address::isValid("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); - ASSERT_TRUE(Address::isValid("cosmospub1addwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq")); - ASSERT_TRUE(Address::isValid("cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08")); - ASSERT_TRUE(Address::isValid("cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa")); -} - -TEST(Address, Cosmos_Invalid) { - ASSERT_FALSE(Address::isValid("cosmos1xsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); - ASSERT_FALSE(Address::isValid("cosmospub1xddwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq")); - ASSERT_FALSE(Address::isValid("cosmosvaloper1xxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08")); - ASSERT_FALSE(Address::isValid("cosmosvalconspub1xcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa")); -} -} // namespace TW::Cosmos diff --git a/tests/Cosmos/SignerTests.cpp b/tests/Cosmos/SignerTests.cpp deleted file mode 100644 index 1e91ceedac0..00000000000 --- a/tests/Cosmos/SignerTests.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CosmosSigner, SignTx) { - auto input = Proto::SigningInput(); - 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); - - ASSERT_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); - - ASSERT_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()); - ASSERT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); - - /* - the sample tx on testnet - https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json - */ -} - -TEST(CosmosSigner, SignTxWithMode) { - auto input = Proto::SigningInput(); - 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); - ASSERT_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()); - } - input.set_mode(Proto::BroadcastMode::SYNC); - { - auto output = Signer::sign(input); - ASSERT_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()); - } -} diff --git a/tests/Cosmos/StakingTests.cpp b/tests/Cosmos/StakingTests.cpp deleted file mode 100644 index 332c6b29077..00000000000 --- a/tests/Cosmos/StakingTests.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CosmosStaking, Staking) { - auto input = Proto::SigningInput(); - 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); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgDelegate\",\"value\":{\"amount\":{\"amount\":\"10\",\"denom\":\"muon\"},\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); -} - -TEST(CosmosStaking, Unstaking) { - auto input = Proto::SigningInput(); - 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); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgUndelegate\",\"value\":{\"amount\":{\"amount\":\"10\",\"denom\":\"muon\"},\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"j4WpUVohGIHa6/s0bCvuyjq1wtQGqbOtQCz92qPQjisTN44Tz++Ozx1lAP6F0M4+eTA03XerqQ8hZCeAfL/3nw==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); -} - -TEST(CosmosStaking, Restaking) { - auto input = Proto::SigningInput(); - 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); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgBeginRedelegate\",\"value\":{\"amount\":{\"amount\":\"10\",\"denom\":\"muon\"},\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_dst_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_src_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"5k03Yb0loovvzagMCg4gjQJP2woriZVRcOZaXF1FSros6B1X4B8MEm3lpZwrWBJMEJVgyYA9ZaF6FLVI3WxQ2w==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); -} - -TEST(CosmosStaking, Withdraw) { - auto input = Proto::SigningInput(); - 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); - - ASSERT_EQ( output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgWithdrawDelegationReward\",\"value\":{\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"validator_address\":\"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"VG8NZzVvavlM+1qyK5dOSZwzEj8sLCkvTw5kh44Oco9GQxBf13FVC+s/I3HwiICqo4+o8jNMEDp3nx2C0tuY1g==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); -} - -TEST(CosmosStaking, WithdrawAllRaw) { - auto input = Proto::SigningInput(); - 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_raw_json_message(); - message.set_type("cosmos-sdk/MsgWithdrawDelegationRewardsAll"); - message.set_value("{\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}"); - 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); - - ASSERT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"1018\",\"denom\":\"muon\"}],\"gas\":\"101721\"},\"memo\":\"\",\"msg\":[{\"type\":\"cosmos-sdk/MsgWithdrawDelegationRewardsAll\",\"value\":{\"delegator_address\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"ImvsgnfbjebxzeBCUPeOcMoOJWMV3IhWM1apV20WiS4K11iA50fe0uXr4Xf/RTxUDXTm56cne/OjOr77BG99Aw==\"}]}}"); - ASSERT_EQ(hex(output.signature()), "226bec8277db8de6f1cde04250f78e70ca0e256315dc88563356a9576d16892e0ad75880e747ded2e5ebe177ff453c540d74e6e7a7277bf3a33abefb046f7d03"); -} diff --git a/tests/Cosmos/TWAnySignerTests.cpp b/tests/Cosmos/TWAnySignerTests.cpp deleted file mode 100644 index 26a69e3e71c..00000000000 --- a/tests/Cosmos/TWAnySignerTests.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 "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("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - Proto::SigningInput input; - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - input.set_private_key(privateKey.data(), privateKey.size()); - - 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); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCosmos); - - ASSERT_EQ(output.json(), 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=="}]}})"); -} - -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 747ba90f2fe..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("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCosmos, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - 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://www.mintscan.io/txs/t123"); - assertStringsEqual(accUrl, "https://www.mintscan.io/account/a12"); - assertStringsEqual(id, "cosmos"); - assertStringsEqual(name, "Cosmos"); -} 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 d25f0bb7bec..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 1fb338d04f3..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(TWBlockchainBitcoin, 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/DerivationPathTests.cpp b/tests/DerivationPathTests.cpp deleted file mode 100644 index 4b2269ad864..00000000000 --- a/tests/DerivationPathTests.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 "DerivationPath.h" - -#include - -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)); -} - -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.purpose(), 44); - ASSERT_EQ(path.coin(), 60); - ASSERT_EQ(path.account(), 0); - ASSERT_EQ(path.change(), 0); - ASSERT_EQ(path.address(), 0); -} - -TEST(DerivationPath, InitInvalid) { - ASSERT_THROW(DerivationPath("a/b/c"), std::invalid_argument); - ASSERT_THROW(DerivationPath("m/44'/60''/"), std::invalid_argument); -} - -TEST(DerivationPath, IndexOutOfBounds) { - DerivationPath path; - - EXPECT_EQ(path.indices.size(), 0); - - 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); - - ASSERT_NO_THROW(path.setPurpose(TWPurposeBIP44)); - ASSERT_NO_THROW(path.setCoin(TWCoinTypeBitcoin)); - ASSERT_NO_THROW(path.setAccount(0)); - ASSERT_NO_THROW(path.setChange(0)); - ASSERT_NO_THROW(path.setAddress(0)); -} - -TEST(DerivationPath, String) { - const auto path = DerivationPath("m/44'/60'/0'/0/0"); - ASSERT_EQ(path.string(), "m/44'/60'/0'/0/0"); -} - -TEST(DerivationPath, Equal) { - const auto path1 = DerivationPath("m/44'/60'/0'/0/0"); - const auto path2 = DerivationPath("44'/60'/0'/0/0"); - ASSERT_EQ(path1, path2); -} - -} // namespace 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/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 33a08d0fb18..00000000000 --- a/tests/EOS/SignatureTests.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 "EOS/Transaction.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::EOS; - -TEST(EOSSignature, Serialization) { - Data buf; - Signature *sig = nullptr; - ASSERT_NO_THROW(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; - ASSERT_NO_THROW(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 8d2df015518..00000000000 --- a/tests/EOS/TransactionTests.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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] - ); - } -} \ No newline at end of file diff --git a/tests/Elrond/AddressTests.cpp b/tests/Elrond/AddressTests.cpp deleted file mode 100644 index 3dd1fb6f166..00000000000 --- a/tests/Elrond/AddressTests.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 -#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)); - ASSERT_TRUE(Address::isValid(CAROL_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_TRUE(Address::decode(CAROL_BECH32, carol)); - - ASSERT_EQ(ALICE_PUBKEY_HEX, hex(alice.getKeyHash())); - ASSERT_EQ(BOB_PUBKEY_HEX, hex(bob.getKeyHash())); - ASSERT_EQ(CAROL_PUBKEY_HEX, hex(carol.getKeyHash())); -} - -TEST(ElrondAddress, FromData) { - const auto alice = Address(parse_hex(ALICE_PUBKEY_HEX)); - const auto bob = Address(parse_hex(BOB_PUBKEY_HEX)); - const auto carol = Address(parse_hex(CAROL_PUBKEY_HEX)); - - ASSERT_EQ(ALICE_BECH32, alice.string()); - ASSERT_EQ(BOB_BECH32, bob.string()); - ASSERT_EQ(CAROL_BECH32, carol.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()); - - auto carolKey = PrivateKey(parse_hex(CAROL_SEED_HEX)); - auto carol = Address(carolKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(CAROL_BECH32, carol.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()); - - auto carol = PublicKey(parse_hex(CAROL_PUBKEY_HEX), TWPublicKeyTypeED25519); - ASSERT_EQ(CAROL_BECH32, Address(carol).string()); -} diff --git a/tests/Elrond/SerializationTests.cpp b/tests/Elrond/SerializationTests.cpp deleted file mode 100644 index 35110688955..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) { - Proto::TransactionMessage message; - message.set_nonce(42); - message.set_value("43"); - message.set_sender("alice"); - message.set_receiver("bob"); - message.set_data("foo"); - message.set_chain_id("1"); - message.set_version(1); - - string jsonString = serializeTransaction(message); - 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) { - Proto::TransactionMessage message; - message.set_nonce(15); - message.set_value("100"); - message.set_sender(ALICE_BECH32); - message.set_receiver(BOB_BECH32); - message.set_gas_price(1000000000); - message.set_gas_limit(50000); - message.set_data("foo"); - message.set_chain_id("1"); - message.set_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(message); - ASSERT_EQ(expected, actual); -} - -TEST(ElrondSerialization, SignableStringWithoutData) { - Proto::TransactionMessage message; - message.set_nonce(42); - message.set_value("43"); - message.set_sender("feed"); - message.set_receiver("abba"); - message.set_chain_id("1"); - message.set_version(1); - - string jsonString = serializeTransaction(message); - 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 39a019caf65..00000000000 --- a/tests/Elrond/SignerTests.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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, 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_transaction()->set_nonce(0); - input.mutable_transaction()->set_value("0"); - input.mutable_transaction()->set_sender(ALICE_BECH32); - input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(1000000000); - input.mutable_transaction()->set_gas_limit(50000); - input.mutable_transaction()->set_data("foo"); - input.mutable_transaction()->set_chain_id("1"); - input.mutable_transaction()->set_version(1); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"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(ElrondSigner, SignJSON) { - // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - - auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"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_transaction()->set_nonce(0); - input.mutable_transaction()->set_value("0"); - input.mutable_transaction()->set_sender(ALICE_BECH32); - input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(1000000000); - input.mutable_transaction()->set_gas_limit(50000); - input.mutable_transaction()->set_data(""); - input.mutable_transaction()->set_chain_id("1"); - input.mutable_transaction()->set_version(1); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "3079d37bfbdbe66fbb4c4b186144f9d9ad5b4b08fbcd6083be0688cf1171123109dfdefdbabf91425c757ca109b6db6d674cb9aeebb19a1a51333565abb53109"; - 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"({"transaction" : {"value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - - auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "3079d37bfbdbe66fbb4c4b186144f9d9ad5b4b08fbcd6083be0688cf1171123109dfdefdbabf91425c757ca109b6db6d674cb9aeebb19a1a51333565abb53109"; - 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); -} diff --git a/tests/Elrond/TWAnySignerTests.cpp b/tests/Elrond/TWAnySignerTests.cpp deleted file mode 100644 index 0124614bf06..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_transaction()->set_nonce(0); - input.mutable_transaction()->set_value("0"); - input.mutable_transaction()->set_sender(ALICE_BECH32); - input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(1000000000); - input.mutable_transaction()->set_gas_limit(50000); - input.mutable_transaction()->set_data("foo"); - input.mutable_transaction()->set_chain_id("1"); - input.mutable_transaction()->set_version(1); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeElrond); - - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"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"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":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 = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"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 55060002abd..00000000000 --- a/tests/Elrond/TestAccounts.h +++ /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. - -#pragma once - -const auto ALICE_BECH32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"; -const auto ALICE_SEED_HEX = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"; -const auto ALICE_PUBKEY_HEX = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"; -const auto BOB_BECH32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"; -const auto BOB_SEED_HEX = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e"; -const auto BOB_PUBKEY_HEX = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36"; -const auto CAROL_BECH32 = "erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0"; -const auto CAROL_SEED_HEX = "a926316cc3daf8ff25ba3e417797e6dfd51f62ae735ab07148234732f7314052"; -const auto CAROL_PUBKEY_HEX = "2cf945faf0162ceede93fe3addda83fe2ebe2041f8a70b2521767044638fa29f"; diff --git a/tests/Ethereum/AbiTests.cpp b/tests/Ethereum/AbiTests.cpp deleted file mode 100644 index 4ff72539c80..00000000000 --- a/tests/Ethereum/AbiTests.cpp +++ /dev/null @@ -1,1224 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 - -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: needs a child parameter to obtain type - auto paramArray = ParamArray(); - EXPECT_EQ("empty[]", paramArray.getType()); - paramArray.addParam(std::make_shared()); - EXPECT_EQ("bool[]", paramArray.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, 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()); -} - -///// Direct encode & decode - -TEST(EthereumAbi, EncodeVectorByte10) { - auto p = ParamByteArrayFix(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}); - EXPECT_EQ("bytes10", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ("3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); -} - -TEST(EthereumAbi, EncodeVectorByte) { - auto p = ParamByteArray(std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}); - 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, 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[]") { - expected = "[0]"; - } else if (t.substr(0, 3) == "int" || t.substr(0, 4) == "uint") { - expected = "0"; - } - if (expected.length() > 0) { - EXPECT_EQ(expected, ParamFactory::getValue(p, t)); - } - } -} - -TEST(EthereumAbi, MaskForBits) { - EXPECT_EQ(0x000000ffffff, ParamUIntN::maskForBits(24)); - EXPECT_EQ(0x00ffffffffff, ParamUIntN::maskForBits(40)); -} 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 a8264ae19d9..00000000000 --- a/tests/Ethereum/ContractCallTests.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 "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); - 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); -} diff --git a/tests/Ethereum/RLPTests.cpp b/tests/Ethereum/RLPTests.cpp deleted file mode 100644 index 55c1c752ea3..00000000000 --- a/tests/Ethereum/RLPTests.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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; - -TEST(RLP, Strings) { - EXPECT_EQ(hex(RLP::encode("")), "80"); - EXPECT_EQ(hex(RLP::encode("dog")), "83646f67"); -} - -TEST(RLP, Integers) { - 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(256)), "820100"); - EXPECT_EQ(hex(RLP::encode(1024)), "820400"); - 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, uint256_t) { - 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, PutInt) { - EXPECT_EQ(hex(RLP::putint(0)), "00"); - EXPECT_EQ(hex(RLP::putint(1)), "01"); - EXPECT_EQ(hex(RLP::putint(0x21)), "21"); - EXPECT_EQ(hex(RLP::putint(0x4321)), "4321"); - EXPECT_EQ(hex(RLP::putint(0x654321)), "654321"); - EXPECT_EQ(hex(RLP::putint(0x87654321)), "87654321"); - EXPECT_EQ(hex(RLP::putint(0xa987654321)), "a987654321"); - EXPECT_EQ(hex(RLP::putint(0xcba987654321)), "cba987654321"); - EXPECT_EQ(hex(RLP::putint(0xedcba987654321)), "edcba987654321"); - EXPECT_EQ(hex(RLP::putint(0x21edcba987654321)), "21edcba987654321"); - EXPECT_EQ(hex(RLP::putint(0xffffffffffffffff)), "ffffffffffffffff"); -} - -TEST(RLP, Lists) { - 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{"cat", "dog"})), "c88363617483646f67"); - const auto encoded = RLP::encodeList(std::vector(1024)); - const auto prefix = std::string("f90400"); - ASSERT_TRUE(std::equal(prefix.begin(), prefix.end(), hex(encoded).begin())); -} - -TEST(RLP, Invalid) { - ASSERT_TRUE(RLP::encode(-1).empty()); - ASSERT_TRUE(RLP::encodeList(std::vector{0, -1}).empty()); -} - -TEST(RLP, Decode) { - { - // empty string - auto decoded = RLP::decode(parse_hex("0x80")).decoded[0]; - ASSERT_EQ(std::string(decoded.begin(), decoded.end()), ""); - } - - { - // short string - auto decoded = RLP::decode(parse_hex("0x83636174")).decoded[0]; - ASSERT_EQ(std::string(decoded.begin(), decoded.end()), "cat"); - } - - { - // long string - auto encoded = parse_hex("0xb87674686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e67"); - auto decoded = RLP::decode(encoded).decoded[0]; - ASSERT_EQ(std::string(decoded.begin(), decoded.end()), "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"); - } - - { - // empty list - auto decoded = RLP::decode(parse_hex("0xc0")).decoded; - ASSERT_EQ(decoded.size(), 0); - } - - { - // short list - auto encoded = parse_hex("0xc88363617483646f67"); - auto decoded = RLP::decode(encoded).decoded; - ASSERT_EQ(std::string(decoded[0].begin(), decoded[0].end()), "cat"); - ASSERT_EQ(std::string(decoded[1].begin(), decoded[1].end()), "dog"); - } -} - -TEST(RLP, DecodeList) { - { - // long list, raw ether transfer tx - auto rawTx = parse_hex("0xf86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"); - auto decoded = RLP::decode(rawTx); - - auto expected = std::vector{ - "0xa9", // nonce - "0x051f4d5ce9", // gas price - "0x5208", // gas limit - "0x515778891c99e3d2e7ae489980cb7c77b37b5e76", // to - "0x1b48eb57e000", // amount - "0x", // data - "0x25", // v - "0xad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475", // r - "0x0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65", // s - }; - ASSERT_EQ(decoded.decoded.size(), expected.size()); - for (int i = 0; i < expected.size(); i++) { - EXPECT_EQ(hexEncoded(decoded.decoded[i]), expected[i]); - } - } - - { - // long list, raw token transfer tx - auto rawTx = parse_hex("0xf8aa81d485077359400082db9194dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf0801ca02843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6aca05d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a"); - auto decoded = RLP::decode(rawTx); - - auto expected = std::vector{ - "0xd4", - "0x0773594000", - "0xdb91", - "0xdac17f958d2ee523a2206206994597c13d831ec7", - "0x", - "0xa9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080", - "0x1c", - "0x2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac", - "0x5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a", - }; - ASSERT_EQ(decoded.decoded.size(), expected.size()); - for (int i = 0; i < expected.size(); i++) { - EXPECT_EQ(hexEncoded(decoded.decoded[i]), expected[i]); - } - } -} - -TEST(RLP, DecodeLength) { - EXPECT_EQ(hex(store(RLP::decodeLength(parse_hex("00")))), "00"); - EXPECT_EQ(hex(store(RLP::decodeLength(parse_hex("01")))), "01"); - EXPECT_EQ(hex(store(RLP::decodeLength(parse_hex("fc")))), "fc"); - EXPECT_EQ(hex(store(RLP::decodeLength(parse_hex("fd0102")))), "0201"); - EXPECT_EQ(hex(store(RLP::decodeLength(parse_hex("fe01020304")))), "04030201"); - EXPECT_EQ(hex(store(RLP::decodeLength(parse_hex("ff0102030405060708")))), "0807060504030201"); - EXPECT_THROW(RLP::decodeLength(parse_hex("fd01")), std::invalid_argument); - EXPECT_THROW(RLP::decodeLength(parse_hex("fe010203")), std::invalid_argument); -} - -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); -} diff --git a/tests/Ethereum/SignerTests.cpp b/tests/Ethereum/SignerTests.cpp deleted file mode 100644 index e77bed75d9c..00000000000 --- a/tests/Ethereum/SignerTests.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 "Ethereum/Address.h" -#include "Ethereum/RLP.h" -#include "Ethereum/Signer.h" -#include "HexCoding.h" - -#include - -namespace TW::Ethereum { - -using boost::multiprecision::uint256_t; - -class SignerExposed : public Signer { -public: - SignerExposed(boost::multiprecision::uint256_t chainID) : Signer(chainID) {} - using Signer::hash; -}; - -TEST(EthereumSigner, Hash) { - auto address = parse_hex("0x3535353535353535353535353535353535353535"); - auto transaction = Transaction( - /* nonce: */ 9, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ address, - /* amount: */ 1000000000000000000 - ); - auto signer = SignerExposed(1); - auto hash = signer.hash(transaction); - - ASSERT_EQ(hex(hash), "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"); -} - -TEST(EthereumSigner, Sign) { - auto address = parse_hex("0x3535353535353535353535353535353535353535"); - auto transaction = Transaction( - /* nonce: */ 9, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ address, - /* amount: */ 1000000000000000000 - ); - - auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); - auto signer = SignerExposed(1); - signer.sign(key, transaction); - - ASSERT_EQ(transaction.v, 37); - ASSERT_EQ(transaction.r, uint256_t("18515461264373351373200002665853028612451056578545711640558177340181847433846")); - ASSERT_EQ(transaction.s, uint256_t("46948507304638947509940763649030358759909902576025900602547168820602576006531")); -} - -TEST(EthereumSigner, SignERC20Transfer) { - auto transaction = Transaction::buildERC20Transfer( - /* nonce: */ 0, - /* gasPrice: */ 42000000000, // 0x09c7652400 - /* gasLimit: */ 78009, // 130B9 - /* tokenContract: */ parse_hex("0x6b175474e89094c44da98b954eedeac495271d0f"), // DAI - /* toAddress: */ parse_hex("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), - /* amount: */ 2000000000000000000 - ); - - auto key = PrivateKey(parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")); - auto signer = SignerExposed(1); - signer.sign(key, transaction); - - ASSERT_EQ(transaction.v, 37); - ASSERT_EQ(hex(TW::store(transaction.r)), "724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98ce"); - ASSERT_EQ(hex(TW::store(transaction.s)), "032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"); -} - -} // namespace TW::Ethereum diff --git a/tests/Ethereum/TWAnySignerTests.cpp b/tests/Ethereum/TWAnySignerTests.cpp deleted file mode 100644 index 61cfedb8b77..00000000000 --- a/tests/Ethereum/TWAnySignerTests.cpp +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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()); - 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()); - 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()); - 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()); - 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()); - 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()); - 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()); - 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); -} diff --git a/tests/Ethereum/TWCoinTypeTests.cpp b/tests/Ethereum/TWCoinTypeTests.cpp deleted file mode 100644 index 1bdfedd0e25..00000000000 --- a/tests/Ethereum/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(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)); - - 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"); -} diff --git a/tests/Ethereum/TWEthereumAbiTests.cpp b/tests/Ethereum/TWEthereumAbiTests.cpp deleted file mode 100644 index 5f212e0464d..00000000000 --- a/tests/Ethereum/TWEthereumAbiTests.cpp +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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); -} - -} // 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 6774a3c294e..00000000000 --- a/tests/EthereumClassic/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(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)); - - 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"); -} 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/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/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 8b621892521..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(TWBlockchainBitcoin, 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/Groestlcoin/TWGroestlcoinSigningTests.cpp deleted file mode 100644 index 7e81ec429b1..00000000000 --- a/tests/Groestlcoin/TWGroestlcoinSigningTests.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 "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" -#include "Hash.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" -#include "../Bitcoin/TxComparisonHelper.h" - -#include -#include -#include -#include -#include - -#include - -using namespace TW; -using namespace TW::Bitcoin; - -TEST(GroestlcoinSigning, SignP2WPKH) { - Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(2500); - input.set_byte_fee(1); - input.set_to_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); - input.set_change_address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); - - auto utxoKey0 = parse_hex("dc334e7347f2f9f72fce789b11832bdf78adf0158bc6617e6d2d2a530a0d4bc6"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxo0 = input.add_utxo(); - auto utxo0Script = Script(parse_hex("00147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce")); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(4774); - auto hash0 = parse_hex("9568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f"); - utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); - utxo0->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - - Proto::TransactionPlan plan; - { - // try plan first - ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); - EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); - } - - // 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(2048); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeGroestlcoin); - - // https://blockbook.groestlcoin.org/tx/40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64 - EXPECT_EQ(output.transaction().outputs_size(), 2); - EXPECT_EQ(output.transaction().outputs(0).value(), 2500); - EXPECT_EQ(output.transaction().outputs(1).value(), 2048); - ASSERT_EQ(hex(output.encoded()), "010000000001019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f0100000000ffffffff02c40900000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700080000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88ac024830450221009bbd0228dcb7343828633ded99d216555d587b74db40c4a46f560187eca222dd022032364cf6dbf9c0213076beb6b4a20935d4e9c827a551c3f6f8cbb22d8b464467012102e9c9b9b76e982ad8fa9a7f48470eafbeeba9bf6d287579318c517db5157d936e00000000"); -} - -TEST(GroestlcoinSigning, SignP2PKH) { - 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"); - - auto utxoKey0 = parse_hex("3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxo0 = input.add_utxo(); - auto utxo0Script = 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); - - Proto::TransactionPlan plan; - { - // try plan first - ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); - 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); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeGroestlcoin); - - // https://blockbook.groestlcoin.org/tx/74a0dd12bc178cfcc1e0982a2a5b2c01a50e41abbb63beb031bcd21b3e28eac0 - EXPECT_EQ(output.transaction().outputs_size(), 2); - EXPECT_EQ(output.transaction().outputs(0).value(), 2500); - EXPECT_EQ(output.transaction().outputs(1).value(), 2274); - ASSERT_EQ(hex(output.encoded()), "01000000019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f000000006a47304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19012103b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91ffffffff02c4090000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09cee20800000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700000000"); -} - -TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { - // TX outputs - Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(5'000); - input.set_byte_fee(1); - input.set_to_address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); - input.set_change_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); - - // TX input - 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"); - 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"); - 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 = Script(parse_hex("a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e487")); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(10'000); - auto hash0 = DATA("fdae0772d7d1d33804a6b1ca0e391668b116bb7a70028427d3d82232189ce863"); // UTXO hash backwards - 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); - - Proto::TransactionPlan plan; - { - // try plan first - ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); - EXPECT_TRUE(verifyPlan(plan, {10'000}, 5000, 167)); - } - - // 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(4774); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeGroestlcoin); - - // https://blockbook.groestlcoin.org/tx/8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895 - EXPECT_EQ(output.transaction().outputs_size(), 2); - EXPECT_EQ(output.transaction().outputs(0).value(), 5000); - EXPECT_EQ(output.transaction().outputs(1).value(), 4774); - ASSERT_EQ(hex(output.encoded()), "01000000000101fdae0772d7d1d33804a6b1ca0e391668b116bb7a70028427d3d82232189ce86300000000171600142fc7d70acef142d1f7b5ef2f20b1a9b759797674ffffffff0288130000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88aca6120000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce024730440220614df57babf74029afaa6dda202afa47d3555cca7a0f20a22e466aeb7029e7d002207974b4c16f346811aff6720d09b9c58d0c4e01e8d258c3d203cc3c1ad228c61a012102fb6ad115761ea928f1367befb2bee79c0b3497314b45e0b734cd150f0601706c00000000"); -} - -TEST(GroestlcoinSigning, PlanP2WPKH) { - Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(2500); - input.set_byte_fee(1); - input.set_to_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); - input.set_change_address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); - - auto utxoKey0 = parse_hex("dc334e7347f2f9f72fce789b11832bdf78adf0158bc6617e6d2d2a530a0d4bc6"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxo0 = input.add_utxo(); - auto utxo0Script = Script(parse_hex("00147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce")); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(4774); - auto hash0 = parse_hex("9568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f"); - utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); - utxo0->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - - Proto::TransactionPlan plan; - ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); - - EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); - EXPECT_EQ(plan.branch_id(), ""); -} diff --git a/tests/HDWalletTests.cpp b/tests/HDWalletTests.cpp deleted file mode 100644 index 39f93c8b7b5..00000000000 --- a/tests/HDWalletTests.cpp +++ /dev/null @@ -1,116 +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 "HDWallet.h" -#include "Bitcoin/Address.h" -#include "Bitcoin/CashAddress.h" -#include "Bitcoin/SegwitAddress.h" -#include "HexCoding.h" -#include "PublicKey.h" -#include "Hash.h" -#include "Base58.h" -#include "Coin.h" - -#include - -namespace TW { - -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::CashAddress(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, 0, "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"); -} - -} // namespace 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/TWAnySignerTests.cpp b/tests/Harmony/TWAnySignerTests.cpp deleted file mode 100644 index 63da2642258..00000000000 --- a/tests/Harmony/TWAnySignerTests.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 "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); -} 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/HashTests.cpp b/tests/HashTests.cpp deleted file mode 100644 index 5c92799f5d7..00000000000 --- a/tests/HashTests.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 "Hash.h" -#include "HexCoding.h" - -#include - -using namespace std; -using namespace TW; - -const string brownFox = "The quick brown fox jumps over the lazy dog"; -const string brownFoxDot = "The quick brown fox jumps over the lazy dog."; - -TEST(HashTests, Blake2b) { - auto content = string("Hello world"); - auto hashed = Hash::blake2b(content, 64); - auto result = hex(hashed); - - ASSERT_EQ(result, string("6ff843ba685842aa82031d3f53c48b66326df7639a63d128974c5c14f31a0f33343a8c65551134ed1ae0f2b0dd2bb495dc81039e3eeb0aa1bb0388bbeac29183")); -} - -TEST(HashTests, Blake2bPersonal) { - auto personal_string = string("MyApp Files Hash"); - auto personal_data = Data(personal_string.begin(), personal_string.end()); - auto content = string("the same content"); - auto hashed = Hash::blake2b(content, 32, personal_data); - auto result = hex(hashed); - - ASSERT_EQ(result, string("20d9cd024d4fb086aae819a1432dd2466de12947831b75c5a30cf2676095d3b4")); -} - -TEST(HashTests, Sha512_256) { - auto tests = { - make_tuple(string(""), string("c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a")), - make_tuple(brownFox, string("dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d")), - make_tuple(brownFoxDot, string("1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3")), - }; - for (auto &test : tests) { - auto hashed = Hash::sha512_256(get<0>(test)); - ASSERT_EQ(hex(hashed), get<1>(test)); - } -} - -TEST(HashTests, Sha1) { - auto tests = { - make_tuple(string(""), string("da39a3ee5e6b4b0d3255bfef95601890afd80709")), - make_tuple(brownFox, string("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")), - make_tuple(brownFoxDot, string("408d94384216f890ff7a0c3528e8bed1e0b01621")), - }; - for (auto &test: tests) { - const auto hash = Hash::sha1(TW::data(get<0>(test))); - EXPECT_EQ(hex(hash), get<1>(test)); - } -} - -TEST(HashTests, hmac256) { - const Data key = parse_hex("531cbfcf12a168faff61af28bf437377397b4bf435ee732cf4ac95761a651f14"); - const Data data = parse_hex("f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224d"); - const auto expectedHmac = "a7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab"; - const Data hmac = Hash::hmac256(key, data); - EXPECT_EQ(hex(hmac), expectedHmac); -} - -// More tests in TWHashTests diff --git a/tests/HexCodingTests.cpp b/tests/HexCodingTests.cpp deleted file mode 100644 index 3c06029227e..00000000000 --- a/tests/HexCodingTests.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 "HexCoding.h" -#include "Data.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 invalid = "28fa6ae00"; - const auto bytes = parse_hex(invalid); - - ASSERT_TRUE(bytes.empty()); -} - -} 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 bdffe32e09b..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 1e2ed1b261b..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("kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7")); - 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://kava.mintscan.io/txs/2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A"); - assertStringsEqual(accUrl, "https://kava.mintscan.io/account/kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7"); - assertStringsEqual(id, "kava"); - assertStringsEqual(name, "Kava"); -} diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp deleted file mode 100644 index 5b621f07f2e..00000000000 --- a/tests/Keystore/StoredKeyTests.cpp +++ /dev/null @@ -1,374 +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 -#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); - 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).mnemonic, 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_"); - } catch (std::invalid_argument&) { - // expedcted exception OK - return; - } - FAIL() << "Missing excpected excpetion"; -} - -TEST(StoredKey, CreateWithMnemonicRandom) { - const auto key = StoredKey::createWithMnemonicRandom("name", password); - 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(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); - 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); - 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); - EXPECT_EQ(key.accounts.size(), 0); - - { - const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); - key.addAccount("bc1qaucw06s3agez8tyyk4zj9kt0q2934e3mcewdpf", coinTypeBc, derivationPath, "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); - EXPECT_EQ(key.accounts.size(), 1); - } - { - const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); - key.addAccount("bnb1utrnnjym7ustgw7pgyvtmnxay4qmt3ahh276nu", coinTypeBnb, derivationPath, ""); - key.addAccount("0x23b02dC8f67eD6cF8DCa47935791954286ffe7c9", coinTypeBsc, derivationPath, ""); - EXPECT_EQ(key.accounts.size(), 3); - } - { - const auto derivationPath = DerivationPath("m/60'/0'/0'/0/0"); - key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeEth, derivationPath, ""); - key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeBscLegacy, 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, FixAddress) { - { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); - 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.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.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.id, "3198bc9c-6672-5ab3-d995-4942343ae5b6"); - - const auto& payload = key.payload; - ASSERT_TRUE(payload.kdfParams.which() == 1); - EXPECT_EQ(boost::get(payload.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(payload.kdfParams).iterations, 262144); - EXPECT_EQ(hex(boost::get(payload.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.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.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.id, "e13b209c-3b2f-4327-bab0-3bef2e51630d"); - EXPECT_EQ(key.name, "Test Account"); - - const auto header = key.payload; - - EXPECT_EQ(header.cipher, "aes-128-ctr"); - EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); - EXPECT_EQ(hex(header.mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); - EXPECT_EQ(hex(header.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); - - ASSERT_TRUE(header.kdfParams.which() == 0); - EXPECT_EQ(boost::get(header.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(header.kdfParams).n, 262144); - EXPECT_EQ(boost::get(header.kdfParams).p, 8); - EXPECT_EQ(boost::get(header.kdfParams).r, 1); - EXPECT_EQ(hex(boost::get(header.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); - const auto wallet = key.wallet(password); - - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); - - EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - 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, MissingAddress) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/missing-address.json"); - key.fixAddresses(password); - - EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0x04De84ec355BAe81b51cD53Fdc8AA30A61872C95"); - EXPECT_EQ(key.account(coinTypeBc, nullptr)->address, "bc1qe938ncm8fhdqg27xmxd7lq02jz9xh0x48r22lc"); -} - -TEST(StoredKey, EtherWalletAddressNo0x) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/ethereum-wallet-address-no-0x.json"); - key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); - EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); -} - -} // 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/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 a4f3c72b3fa..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 "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 5e4a0584079..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 929af6a5bc1..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(TWBlockchainPolkadot, 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/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/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 c0f2837f0ba..00000000000 --- a/tests/NEAR/SerializationTests.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 "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"); -} - -} diff --git a/tests/NEAR/SignerTests.cpp b/tests/NEAR/SignerTests.cpp deleted file mode 100644 index 69570a621b4..00000000000 --- a/tests/NEAR/SignerTests.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "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"); -} - -} diff --git a/tests/NEAR/TWAnySignerTests.cpp b/tests/NEAR/TWAnySignerTests.cpp deleted file mode 100644 index 49326b7cb4a..00000000000 --- a/tests/NEAR/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/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"); -} - -} // 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 1a7ac8ab7af..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, "NANO"); - 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/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 3a4028f6938..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 c5c5d41f8bb..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 ab703446d39..00000000000 --- a/tests/Oasis/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(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/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/ScaleCodecTests.cpp b/tests/Polkadot/ScaleCodecTests.cpp deleted file mode 100644 index efdd07515c8..00000000000 --- a/tests/Polkadot/ScaleCodecTests.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 "HexCoding.h" -#include "Polkadot/ScaleCodec.h" -#include "Kusama/Address.h" - -#include - -using namespace TW; -using namespace TW::Polkadot; - - -TEST(PolkadotCodec, EncodeCompact) { - ASSERT_EQ(hex(encodeCompact(0)), "00"); - ASSERT_EQ(hex(encodeCompact(18)), "48"); - ASSERT_EQ(hex(encodeCompact(63)), "fc"); - ASSERT_EQ(hex(encodeCompact(64)), "0101"); - - ASSERT_EQ(hex(encodeCompact(12345)), "e5c0"); - ASSERT_EQ(hex(encodeCompact(16383)), "fdff"); - ASSERT_EQ(hex(encodeCompact(16384)), "02000100"); - - ASSERT_EQ(hex(encodeCompact(1073741823)), "feffffff"); - ASSERT_EQ(hex(encodeCompact(1073741824)), "0300000040"); - - ASSERT_EQ(hex(encodeCompact(4294967295)), "03ffffffff"); - ASSERT_EQ(hex(encodeCompact(4294967296)), "070000000001"); - - ASSERT_EQ(hex(encodeCompact(1099511627776)), "0b000000000001"); - ASSERT_EQ(hex(encodeCompact(281474976710656)), "0f00000000000001"); - - ASSERT_EQ(hex(encodeCompact(72057594037927935)), "0fffffffffffffff"); - ASSERT_EQ(hex(encodeCompact(72057594037927936)), "130000000000000001"); - - ASSERT_EQ(hex(encodeCompact(18446744073709551615u)), "13ffffffffffffffff"); -} - -TEST(PolkadotCodec, EncodeBool) { - ASSERT_EQ(hex(encodeBool(true)), "01"); - ASSERT_EQ(hex(encodeBool(false)), "00"); -} - -TEST(PolkadotCodec, EncodeLengthPrefix) { - auto encoded = parse_hex("84ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); - encodeLengthPrefix(encoded); - - ASSERT_EQ(hex(encoded), "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -TEST(PolkadotCodec, encodeAccountId) { - auto address = Kusama::Address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); - auto encoded = encodeAccountId(address.keyBytes(), true); - auto encoded2 = encodeAccountId(address.keyBytes(), false); - - ASSERT_EQ(hex(encoded), "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"); - ASSERT_EQ(hex(encoded2), "008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"); -} - -TEST(PolkadotCodec, EncodeVectorAccountIds) { - auto addresses = std::vector{ - Kusama::Address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"), - Kusama::Address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY") - }; - auto encoded = encodeAccountIds(addresses, false); - ASSERT_EQ(hex(encoded), "08008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); -} - -TEST(PolkadotCodec, EncodeEra) { - auto era1 = encodeEra(429119, 8); - auto era2 = encodeEra(428861, 4); - ASSERT_EQ(hex(era1), "7200"); - ASSERT_EQ(hex(era2), "1100"); -} diff --git a/tests/Polkadot/SignerTests.cpp b/tests/Polkadot/SignerTests.cpp deleted file mode 100644 index 9014e93d016..00000000000 --- a/tests/Polkadot/SignerTests.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "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 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_c7a016) { - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - auto blockHash = parse_hex("0x3a886617f4bbd4fe2bbe7369acae4163ed0b19ffbf061083abc5e0836ad58f77"); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(6); - input.set_spec_version(27); - 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(3856651); - era->set_period(64); - - auto stakingCall = input.mutable_staking_call(); - auto bondnom = stakingCall->mutable_bond_and_nominate(); - auto value = store(uint256_t(2000000000)); // 0.2 - bondnom->set_controller(addressThrow2); // myself - bondnom->set_value(value.data(), value.size()); - bondnom->set_reward_destination(Proto::RewardDestination::STASH); - bondnom->add_nominators(controller1); - bondnom->add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/0xc7a016f961dbf35d58feea22694e7d79ac77175a8cc40cb017bb5e87d56142ce - ASSERT_EQ(hex(output.encoded()), "5103849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783007d549324f270eb5932b898ce5fc166c3f30942c96668f52d6cc86c7b61a8d65680cd0a979f1e0a43ef9418e6571edab6d9c391a1696abdf56db2af348862d50eb50018001a000807009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783030094357701070508aee72821ca00e62304e4f0d858122a65b87c8df4f0eae224ae064b951d39f610127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a"); -} - -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"); -} - -} // 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/PrivateKeyTests.cpp b/tests/PrivateKeyTests.cpp deleted file mode 100644 index f774f7d8eb6..00000000000 --- a/tests/PrivateKeyTests.cpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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) { - try { - auto privateKey = PrivateKey(data, ext, chainCode); - 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") - ); - EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); - } - { - string res = TestInvalidExtended( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("deadbeed"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") - ); - EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); - } - { - string res = TestInvalidExtended( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("deadbeed") - ); - 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()); - - // Extended keys: private key is 3x32 bytes, public key is 64 bytes - auto privateKeyExt = PrivateKey(parse_hex( - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - )); - EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExt.bytes)); - EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExt.extensionBytes)); - EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExt.chainCodeBytes)); - auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Extended); - EXPECT_EQ(64, publicKeyExt.bytes.size()); - - // Try other constructor for extended key - auto privateKeyExtOne = PrivateKey(parse_hex( - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - )); - EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExtOne.bytes)); - EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExtOne.extensionBytes)); - EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExtOne.chainCodeBytes)); -} - -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" - )); - 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 7f056f3c8f4..00000000000 --- a/tests/PublicKeyTests.cpp +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = PrivateKey(key); - - 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, VerifyEd25519Extended) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = PrivateKey(key); - - 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"); -} - -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 cbfc4f28823..00000000000 --- a/tests/Ripple/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/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"); -} 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/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 7385d4af074..00000000000 --- a/tests/Solana/ProgramTests.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 "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"); - ASSERT_EQ(StakeProgram::addressFromValidatorSeed(user, validator, 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 0b65dbaf298..00000000000 --- a/tests/Solana/SignerTests.cpp +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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(header, accountKeys, recentBlockhash, instructions); - - 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, SignDelegateStake) { - 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(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( - "2GXRrZMMWTaY8ycwFTLFojAVZ1EepFqnVGW7b5bBuuKPiVrpaPXMAwyYsSmYc2okCa1MuJjNguu1emSJRtZxVdwt"); - expectedSignatures.push_back(expectedSignature); - ASSERT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7" - "uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQP" - "ouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy" - "7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xy" - "ATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGv" - "wfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWu" - "kgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgzU285SkncqNSgxkpZVhmenTXpuZv74" - "rXzariX8P4sprRgKUoj4b7Nu72Pya1zr7k45isMwgxtLnnnTK5k7mrZRDw3jBSBuukJBja93zaidm8HCQdwQsBt5CN" - "SgSXug1R2t6Sdm5tjJrsd1gyRv7udFbHCdbVEeatzULNSSGdwjwwJDy1DTC12ddBNHd8k5ic5TDwrWdfCxbDRoFYw8" - "49YNNUuyNAPz1jDCkLG9af6KFFLxfuR9pnF8jSyTcQAq95YiiD9sC3mAUoe8AkYfy929XzTEatP1vasMvo"; - ASSERT_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(signer, TokenInstruction::CreateTokenAccount, 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) { - 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(signer, TokenInstruction::CreateTokenAccount, 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) { - 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(signer, TokenInstruction::TokenTransfer, 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 d15fefe463d..00000000000 --- a/tests/Solana/TWAnySignerTests.cpp +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "PrivateKey.h" -#include "../interface/TWTestUtilities.h" -#include - -#include - -using namespace TW; -using namespace TW::Solana; - -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); - - auto expectedString = - "3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZU" - "jikfDzbWBG2aFtG3gHT1QfLzyFKHM4HQtMQMNXqay1NAeiiYZjNhx9UvMX4uAQZ4Q6rx6m2AYfQ" - "7aoMUrejq298q1wBFdtS9XVB5QTiStnzC7zs97FUEK2T4XapjF1519EyFBViTfHpGpnf5bfizDz" - "sW9kYUtRDW1UC2LgHr7npgq5W9TBmHf9hSmRgM9XXucjXLqubNWE7HUMhbKjuBqkirRM"; - ASSERT_EQ(output.encoded(), expectedString); -} - -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, SignDelegateStakeTransaction) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_stake_transaction(); - message.set_validator_pubkey("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - 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 = - "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7" - "uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQP" - "ouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy" - "7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xy" - "ATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGv" - "wfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWu" - "kgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgzU285SkncqNSgxkpZVhmenTXpuZv74" - "rXzariX8P4sprRgKUoj4b7Nu72Pya1zr7k45isMwgxtLnnnTK5k7mrZRDw3jBSBuukJBja93zaidm8HCQdwQsBt5CN" - "SgSXug1R2t6Sdm5tjJrsd1gyRv7udFbHCdbVEeatzULNSSGdwjwwJDy1DTC12ddBNHd8k5ic5TDwrWdfCxbDRoFYw8" - "49YNNUuyNAPz1jDCkLG9af6KFFLxfuR9pnF8jSyTcQAq95YiiD9sC3mAUoe8AkYfy929XzTEatP1vasMvo"; - 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_validator_pubkey("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - 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) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_withdraw_transaction(); - message.set_validator_pubkey("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - 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 = - "7Y1Wg1yHNs8MgWFiFSfcsRtqdMwZg8oGeQnTABYDfyDnof4VSFw63s3PuSxvUCJqqHKgYNVb8UTNcNiYHY8kng4NqT" - "cVV5SA1KAWRzKHVGUxNWioAEXXVot5iJ1XbUWuuZUZBtsraaBjNyfmgWEDje3ESdGhiVL7vadU1uHeBuUKwM3nqB6y" - "oeggeNyzmT34hs9utyehTFg48MAfrKEFKxaby7YZD6JbXFS1SyG1kxKWnCpoPgX3efwDwukmyDwxrKdABt9eTwmaiX" - "KbTnK1hzBTatNfnJ9ePuWkhWFrjyDrGdx5S5KpybxET2vV9CSpExcD51BA6NPemTpjbhLYnJEzHWBGfYqfxu7p3257" - "NHhpQQrSU56adk4dAQFjEYP"; - 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) { - 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) { - 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) { - 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) { - 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) { - 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); -} diff --git a/tests/Solana/TWCoinTypeTests.cpp b/tests/Solana/TWCoinTypeTests.cpp deleted file mode 100644 index a59d8b308c9..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("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSolana, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - 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://explorer.solana.com/transactions/t123"); - assertStringsEqual(accUrl, "https://explorer.solana.com/accounts/a12"); - 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 52152f932dd..00000000000 --- a/tests/Solana/TransactionTests.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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, StakeSerializeTransaction) { - 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(signer, stakeAddress, voteAddress, 42, recentBlockhash); - auto transaction = Transaction(message); - Signature signature( - "2GXRrZMMWTaY8ycwFTLFojAVZ1EepFqnVGW7b5bBuuKPiVrpaPXMAwyYsSmYc2okCa1MuJjNguu1emSJRtZxVdwt"); - transaction.signatures.clear(); - transaction.signatures.push_back(signature); - - auto expectedString = - "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7" - "uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQP" - "ouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy" - "7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xy" - "ATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGv" - "wfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWu" - "kgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgzU285SkncqNSgxkpZVhmenTXpuZv74" - "rXzariX8P4sprRgKUoj4b7Nu72Pya1zr7k45isMwgxtLnnnTK5k7mrZRDw3jBSBuukJBja93zaidm8HCQdwQsBt5CN" - "SgSXug1R2t6Sdm5tjJrsd1gyRv7udFbHCdbVEeatzULNSSGdwjwwJDy1DTC12ddBNHd8k5ic5TDwrWdfCxbDRoFYw8" - "49YNNUuyNAPz1jDCkLG9af6KFFLxfuR9pnF8jSyTcQAq95YiiD9sC3mAUoe8AkYfy929XzTEatP1vasMvo"; - 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(signer, TokenInstruction::CreateTokenAccount, 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) { - 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(signer, TokenInstruction::TokenTransfer, 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 bbe59a37f43..00000000000 --- a/tests/Stellar/TWAnySignerTests.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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"); -} 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/TON/AddressTests.cpp b/tests/TON/AddressTests.cpp deleted file mode 100644 index e835d1bde33..00000000000 --- a/tests/TON/AddressTests.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "TON/Address.h" -#include "TON/Contract.h" -#include "HexCoding.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::TON; - -static string TestGiverHex = "8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d"; -static string TestGiverRaw = "0:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d"; -static string TestGiverUser = "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZT"; // base64url -static string TestGiverUserRegular = "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZT"; // regular base64 - -TEST(TONAddress, WorkchainValid) -{ - ASSERT_TRUE(Workchain::isValid(0)); - ASSERT_TRUE(Workchain::isValid(-1)); - ASSERT_FALSE(Workchain::isValid(1)); - ASSERT_FALSE(Workchain::isValid(10)); -} - -TEST(TONAddress, AddressValidUser) -{ - ASSERT_TRUE(Address::isValid(TestGiverUser)); - // wrong length - ASSERT_FALSE(Address::isValid("EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZ")); // shorter - ASSERT_FALSE(Address::isValid("EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZTz")); // longer - ASSERT_FALSE(Address::isValid("E")); - ASSERT_FALSE(Address::isValid("")); - // Wrong CRC: same value, but with invalid CRC (0) - ASSERT_FALSE(Address::isValid("EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODd0T")); -} - -TEST(TONAddress, AddressValidRaw) -{ - ASSERT_TRUE(Address::isValid(TestGiverRaw)); - ASSERT_TRUE(Address::isValid("0:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - // no colon - ASSERT_FALSE(Address::isValid("0 8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - // invalid workchainID - ASSERT_FALSE(Address::isValid("9:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - ASSERT_FALSE(Address::isValid("A:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - ASSERT_FALSE(Address::isValid(":8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - // valid, but not supported chainID - ASSERT_FALSE(Address::isValid("-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - // invalid address part - ASSERT_FALSE(Address::isValid("0:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0dd")); // longer - ASSERT_FALSE(Address::isValid("0:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0")); // shorter - ASSERT_FALSE(Address::isValid("0:")); - ASSERT_FALSE(Address::isValid("0:0")); - ASSERT_FALSE(Address::isValid("")); -} - -TEST(TONAddress, AddressFromUser) -{ - { - auto addr = Address(TestGiverUser); - // verify fields - ASSERT_TRUE(addr.isBounceable); - ASSERT_FALSE(addr.isTestOnly); - // convert back to string, check - auto addr2strUser = addr.string(); - EXPECT_EQ(TestGiverUser, addr2strUser); - auto addr2strRaw = addr.stringRaw(); - EXPECT_EQ(TestGiverRaw, addr2strRaw); - } - - { - // Base64Url format - auto addr2 = Address(TestGiverUserRegular); - // convert back to string, will be standard again - auto addr22strUser = addr2.string(); - EXPECT_EQ(TestGiverUser, addr22strUser); - } -} - -TEST(TONAddress, AddressFromRaw) -{ - auto addr = Address(TestGiverRaw); - // convert back to string, check - auto addr2strUser = addr.string(); - EXPECT_EQ(TestGiverUser, addr2strUser); - auto addr2strRaw = addr.stringRaw(); - EXPECT_EQ(TestGiverRaw, addr2strRaw); -} - -TEST(TONAddress, AddressToString) -{ - auto addr = Address(TestGiverUser); - - auto strUser = addr.string(); - EXPECT_EQ(TestGiverUser, strUser); - - auto strUserRaw = addr.stringRaw(); - EXPECT_EQ(TestGiverRaw, strUserRaw); -} - -TEST(TONAddress, createStateInit) { - const auto publicKey = PublicKey(parse_hex("F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"), TWPublicKeyTypeED25519); - Cell stateInit = Contract::createStateInit(publicKey); - EXPECT_EQ(2, stateInit.cellCount()); - if (stateInit.cellCount() >= 2) { - // first cell contains smart contract code - EXPECT_EQ(81, stateInit.getCells()[0]->getSlice().size()); - // second cell contains public key - EXPECT_EQ("00000000f61cf0bc8e891ad7636e0cd35229d579323aa2da827eb85d8071407464dc2fa3", hex(stateInit.getCells()[1]->getSlice().data())); - } - auto hash = stateInit.hash(); - EXPECT_EQ("240090ab66459bf6e61e3dfd43f7b9c1f1e7d4bd81d3b2a4ac7323cc1a970753", hex(hash)); -} - -TEST(TONAddress, AddressFromPublicKey) -{ - // Sample taken from TON HOWTO - const auto publicKey = PublicKey(parse_hex("F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - - EXPECT_EQ("0:240090ab66459bf6e61e3dfd43f7b9c1f1e7d4bd81d3b2a4ac7323cc1a970753", address.stringRaw()); - EXPECT_EQ("EQAkAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHUx3Y", address.string()); - // to match options 7 (non-bounceable, test only, base64url) - address.isBounceable = false; - address.isTestOnly = true; - EXPECT_EQ("0QAkAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHU_uX", address.string()); - - // MasterChain - address.workchainId = Workchain::MasterChainId; - address.isBounceable = true; - address.isTestOnly = false; - EXPECT_EQ("-1:240090ab66459bf6e61e3dfd43f7b9c1f1e7d4bd81d3b2a4ac7323cc1a970753", address.stringRaw()); - EXPECT_EQ("Ef8kAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHU-KQ", address.string()); - // to match options 7 (non-bounceable, test only, base64url) - address.isBounceable = false; - address.isTestOnly = true; - EXPECT_EQ("0f8kAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHUwTf", address.string()); -} diff --git a/tests/TON/CellTests.cpp b/tests/TON/CellTests.cpp deleted file mode 100644 index ff53fa33d52..00000000000 --- a/tests/TON/CellTests.cpp +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "TON/Cell.h" -#include "HexCoding.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::TON; - -TEST(TONCell, SliceHash) -{ - { - Slice s = Slice::createFromHex("123456"); - EXPECT_EQ("bf7cbe09d71a1bcc373ab9a764917f730a6ed951ffa1a7399b7abd8f8fd73cb4", hex(s.hash())); - } - { - Slice s = Slice::createFromHex("00000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"); - EXPECT_EQ("3f8e0f65857d989321be21d1b55a01c47b5d5da6b463292841302d4b8a2a39e5", hex(s.hash())); - } - { - Slice s = Slice::createFromBitsStr("31", 5); - EXPECT_EQ(1, s.size()); - EXPECT_EQ(5, s.sizeBits()); - EXPECT_EQ("34", s.asBytesStr()); // unused bits: 100 - EXPECT_EQ("4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a", hex(s.hash())); - } -} - -TEST(TONCell, SliceAppendBits) { - Slice s; - EXPECT_EQ(0, s.sizeBits()); - EXPECT_EQ("", s.asBytesStr()); - // byte boundary, append 2 full bytes (8 bits) - s.appendBits(parse_hex("abcd"), 16); - EXPECT_EQ(2 * 8, s.sizeBits()); - EXPECT_EQ("abcd", s.asBytesStr()); - // byte boundary, append 5 bits 01100 (plus closing 1) - s.appendBits(parse_hex("60"), 5); - EXPECT_EQ(2 * 8 + 5, s.sizeBits()); - EXPECT_EQ("abcd64", s.asBytesStr()); - // append 2 full bytes - s.appendBits(parse_hex("1234"), 16); - EXPECT_EQ(4 * 8 + 5, s.sizeBits()); - EXPECT_EQ("abcd6091a4", s.asBytesStr()); - // append 5 bits 00110 (plus closing 1) - s.appendBits(parse_hex("30"), 5); - EXPECT_EQ(5 * 8 + 2, s.sizeBits()); - EXPECT_EQ("abcd6091a1a0", s.asBytesStr()); - // append nothing - s.appendBits(parse_hex(""), 0); - EXPECT_EQ(5 * 8 + 2, s.sizeBits()); - EXPECT_EQ("abcd6091a1a0", s.asBytesStr()); - // append 1 bit 0 - s.appendBits(parse_hex("00"), 1); - EXPECT_EQ(5 * 8 + 3, s.sizeBits()); - EXPECT_EQ("abcd6091a190", s.asBytesStr()); - // append 4 bits 0100 - s.appendBits(parse_hex("40"), 4); - EXPECT_EQ(5 * 8 + 7, s.sizeBits()); - EXPECT_EQ("abcd6091a189", s.asBytesStr()); - // append 2 bits 01 - s.appendBits(parse_hex("40"), 2); - EXPECT_EQ(6 * 8 + 1, s.sizeBits()); - EXPECT_EQ("abcd6091a188c0", s.asBytesStr()); - // append 7 bits 0110100 - s.appendBits(parse_hex("68"), 7); - EXPECT_EQ(7 * 8, s.sizeBits()); - EXPECT_EQ("abcd6091a188b4", s.asBytesStr()); -} - -TEST(TONCell, CellSimple) -{ - { - Cell cell; - cell.setSliceBytesStr("123456"); - EXPECT_EQ("123456", cell.getSlice().asBytesStr()); - EXPECT_EQ("09a6e1fb711077014e7cae82826707cace55a493501a16144cfd83fca0c8e6d6", hex(cell.hash())); - EXPECT_EQ(5, cell.serializedOwnSize()); - } - { - Cell cell; - cell.setSliceBytesStr("FEDCBA"); - EXPECT_EQ("53a96fa8e030c2c8be0f32cba5a929ca89b5650b699c3c305150a9b8d9669176", hex(cell.hash())); - EXPECT_EQ(5, cell.serializedOwnSize()); - } - { - Cell cell; - cell.setSliceBytesStr("00000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"); - EXPECT_EQ(32 + 4, cell.getSlice().size()); - EXPECT_EQ("3a2e770059fea3557f95c7a269eb51f1675339fed6b0be624ad8b9e649791e1f", hex(cell.hash())); - EXPECT_EQ(38, cell.serializedOwnSize()); - } - { - Cell cell; - cell.setSliceBytesStr("FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54"); - EXPECT_EQ(66, cell.getSlice().size()); - EXPECT_EQ("a0cfc2c48aee16a271f2cfc0b7382d81756cecb1017d077faaab3bb602f6868c", hex(cell.hash())); - EXPECT_EQ(68, cell.serializedOwnSize()); - } - { - Cell cell; - cell.setSliceBytesStr("FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54"); - EXPECT_EQ(70, cell.getSlice().size()); - EXPECT_EQ("78ad9f4d84126ddade8c3fa1d3d5fa3304b60c5600ea9f0296419cbf73cda9c4", hex(cell.hash())); - EXPECT_EQ(72, cell.serializedOwnSize()); - } - { - // Hash of empty cell - Cell cell; - EXPECT_EQ("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7", hex(cell.hash())); - EXPECT_EQ(2, cell.serializedOwnSize()); - } -} - -TEST(TONCell, CellBits) -{ - { - // Hash of cell with bits - Cell cell; - cell.setSliceBitsStr("31", 5); // 5 bits are 00110, the last 3 are not used - EXPECT_EQ("34", cell.getSlice().asBytesStr()); // the 5 bits padded with 100 - EXPECT_EQ(1, cell.getSlice().size()); - EXPECT_EQ(5, cell.getSlice().sizeBits()); - EXPECT_EQ("ea23ba20e2f88a07af38948bfaef741741f5a464df43e87067c901e537d1c44f", hex(cell.hash())); - EXPECT_EQ(3, cell.serializedOwnSize()); - } -} - -TEST(TONCell, CellWithChild) -{ - { - // cell with one child - auto c1 = std::make_shared(); - c1->setSliceBytesStr("123456"); - EXPECT_EQ("09a6e1fb711077014e7cae82826707cace55a493501a16144cfd83fca0c8e6d6", hex(c1->hash())); - Cell c; - EXPECT_EQ("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7", hex(c.hash())); // empty cell - c.addCell(c1); - EXPECT_EQ("993e6d53d70fb05f052ce45ac751e24abd7d43e358b297694cfc19bbc796141c", hex(c.hash())); - EXPECT_EQ(23, c.serializedSize(Cell::SerializationMode::WithCRC32C)); - Data ser; - c.serialize(ser, Cell::SerializationMode::WithCRC32C); - EXPECT_EQ("b5ee9c724101020100080001000100061234567ac0b173", hex(ser)); - } - { - // cell with two children - auto c1 = std::make_shared(); - c1->setSliceBytesStr("123456"); - EXPECT_EQ("09a6e1fb711077014e7cae82826707cace55a493501a16144cfd83fca0c8e6d6", hex(c1->hash())); - auto c2 = std::make_shared(); - c2->setSliceBytesStr("FEDCBA"); - EXPECT_EQ("53a96fa8e030c2c8be0f32cba5a929ca89b5650b699c3c305150a9b8d9669176", hex(c2->hash())); - Cell c; - EXPECT_EQ("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7", hex(c.hash())); // empty cell - c.addCell(c1); - EXPECT_EQ("993e6d53d70fb05f052ce45ac751e24abd7d43e358b297694cfc19bbc796141c", hex(c.hash())); - c.addCell(c2); - EXPECT_EQ(2, c.cellCount()); - EXPECT_EQ("3959cba26c91a21d80b6953dd646ce8b8fb3caa507ea866ee98ff92b0230d0b9", hex(c.hash())); - EXPECT_EQ(29, c.serializedSize(Cell::SerializationMode::WithCRC32C)); - Data ser; - c.serialize(ser, Cell::SerializationMode::WithCRC32C); - EXPECT_EQ("b5ee9c7241010301000e000200010200061234560006fedcba50ea05ee", hex(ser)); - } - { - // cell with slice value and one child - auto c1 = std::make_shared(); - c1->setSliceBytesStr("FEDCBA"); - EXPECT_EQ("53a96fa8e030c2c8be0f32cba5a929ca89b5650b699c3c305150a9b8d9669176", hex(c1->hash())); - Cell c; - EXPECT_EQ("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7", hex(c.hash())); // empty cell - c.setSliceBytesStr("123456"); - EXPECT_EQ("09a6e1fb711077014e7cae82826707cace55a493501a16144cfd83fca0c8e6d6", hex(c.hash())); - c.addCell(c1); - EXPECT_EQ("45d4770b7e9816f062cb8e3f5c58e8ed16443b2387a1c6f59b777dfb005822fa", hex(c.hash())); - EXPECT_EQ(26, c.serializedSize(Cell::SerializationMode::WithCRC32C)); - Data ser; - c.serialize(ser, Cell::SerializationMode::WithCRC32C); - EXPECT_EQ("b5ee9c7241010201000b000106123456010006fedcba7dc78a01", hex(ser)); - } -} - -TEST(TONCell, CellError) -{ - { - Cell cell; - ASSERT_ANY_THROW({ - cell.setSliceBytesStr(""); // empty data - }); - } - { - Cell cell; - ASSERT_ANY_THROW({ - cell.setSliceBitsStr("31", 80); // too few bytes for 80 bits - }); - } -} - -TEST(TONCell, CellErrorTooManyCells) -{ - { - Cell cell; - auto child = std::make_shared(); - for (int i = 0; i < Cell::max_cells; ++i) - cell.addCell(child); - ASSERT_ANY_THROW({ - // (N+1)th add fails - cell.addCell(child); - }); - } -} - -TEST(TONCell, CellStateInit1) -{ - { - // StateInit, with bits slice value and two children - Cell c; - c.setSliceBitsStr("30", 5); - auto ccode = std::make_shared(); - ccode->setSliceBytesStr("FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54"); - auto cdata = std::make_shared(); - cdata->setSliceBytesStr("00000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"); - c.addCell(ccode); - c.addCell(cdata); - EXPECT_EQ("60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0", hex(c.hash())); - EXPECT_EQ(130, c.serializedSize(Cell::SerializationMode::WithCRC32C)); - Data ser; - c.serialize(ser, Cell::SerializationMode::WithCRC32C); - EXPECT_EQ("b5ee9c72410103010073000201340102008cff0020dda4f260810200d71820d70b1fed44d0d7091fd709ffd15112baf2a122f901541044f910f2a2f80001d7091f3120d74a97d70907d402fb00ded1a4c8cb1fcbffc9ed54004800000000f61cf0bc8e891ad7636e0cd35229d579323aa2da827eb85d8071407464dc2fa3984101af", - hex(ser)); - } -} - -TEST(TONCell, CellStateInit2) -{ - { - // StateInit, with bits slice value and two children - Cell c; - c.setSliceBitsStr("30", 5); - auto ccode = std::make_shared(); - ccode->setSliceBytesStr("FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54"); - auto cdata = std::make_shared(); - cdata->setSliceBytesStr("0000000037F14C50F6435B11B9326E1218524F7F072D0A5EA8221CCA71682E7D6ED64213"); - c.addCell(ccode); - c.addCell(cdata); - EXPECT_EQ("307c9efea683c14f572a9032086ffb8cf8168c5d49094cbffbcbf0bdc3037990", hex(c.hash())); - EXPECT_EQ(141, c.serializedSize(Cell::SerializationMode::WithCRC32C)); - Data ser; - c.serialize(ser, Cell::SerializationMode::WithCRC32C); - EXPECT_EQ("b5ee9c7241010301007e00020134010200a2ff0020dd2082014c97ba9730ed44d0d70b1fe0a4f260810200d71820d70b1fed44d0d31fd3ffd15112baf2a122f901541044f910f2a2f80001d31f3120d74a96d307d402fb00ded1a4c8cb1fcbffc9ed5400480000000037f14c50f6435b11b9326e1218524f7f072d0a5ea8221cca71682e7d6ed6421381c553bd", - hex(ser)); - } -} \ No newline at end of file diff --git a/tests/TON/SignerTests.cpp b/tests/TON/SignerTests.cpp deleted file mode 100644 index 18fd63ba920..00000000000 --- a/tests/TON/SignerTests.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 "TON/Signer.h" -#include "TON/Address.h" -#include "HexCoding.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::TON; - -TEST(TONSigner, extMsg1) -{ - const auto privkey = PrivateKey(parse_hex("c17bedd2e048e132f3d4d2a690499046fa376ef8a8ac9a488c8470045c05cdd7")); - const auto pubkey = privkey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_EQ(hex(parse_hex("37f14c50f6435b11b9326e1218524f7f072d0a5ea8221cca71682e7d6ed64213")), hex(pubkey.bytes)); - - Address addr = Address(pubkey); - EXPECT_EQ("EQAwfJ7-poPBT1cqkDIIb_uM-BaMXUkJTL_7y_C9wwN5kEVJ", addr.string()); - - Data extMsg = Signer::buildInitMessage(privkey); - EXPECT_EQ(244, extMsg.size()); - EXPECT_EQ( - "b5ee9c724101030100e50002cf880060f93dfd4d07829eae55206410dff719f02d18ba9212997ff797e17b8606f3201189e486b7a3c126932745507bce5491900755fe9fdde2162d979e07023dd00eaa4396f1cce78f77f409bfcadbc58e3cae723aa4d6134810ebbf1c900c99813bc0a000000010010200a2ff0020dd2082014c97ba9730ed44d0d70b1fe0a4f260810200d71820d70b1fed44d0d31fd3ffd15112baf2a122f901541044f910f2a2f80001d31f3120d74a96d307d402fb00ded1a4c8cb1fcbffc9ed5400480000000037f14c50f6435b11b9326e1218524f7f072d0a5ea8221cca71682e7d6ed64213c59ea7fb", - hex(extMsg) - ); -} - -TEST(TONSigner, extMsg2) -{ - const auto privkey = PrivateKey(parse_hex("6fd2527cc03e1bd77700722308baa6348aef2a0f5c03bdd392ab013123659914")); - const auto pubkey = privkey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_EQ(hex(parse_hex("2d1e01087d4c3cff53b407e2bf90c435296e4012facb759a904d1baa6f749a14")), hex(pubkey.bytes)); - - Address addr = Address(pubkey); - EXPECT_EQ("EQC7I0GX_8EdSZIFHFcag1hrDdIAYip9lBUGuqXIifbGmYGz", addr.string()); - - Data extMsg = Signer::buildInitMessage(privkey); - EXPECT_EQ(244, extMsg.size()); - EXPECT_EQ( - "b5ee9c724101030100e50002cf88017646832fff823a93240a38ae3506b0d61ba400c454fb282a0d754b9113ed8d321194ce5400aec2a7993af86e4df81d9f99524e9a42a787aff70b717102d295288f95a305c0ff3223aa388dc904ac9383d9cea23f26c374dbd348abf1ec32aa1580c000000010010200a2ff0020dd2082014c97ba9730ed44d0d70b1fe0a4f260810200d71820d70b1fed44d0d31fd3ffd15112baf2a122f901541044f910f2a2f80001d31f3120d74a96d307d402fb00ded1a4c8cb1fcbffc9ed540048000000002d1e01087d4c3cff53b407e2bf90c435296e4012facb759a904d1baa6f749a1439ddf944", - hex(extMsg) - ); -} diff --git a/tests/TON/TWCoinTypeTests.cpp b/tests/TON/TWCoinTypeTests.cpp deleted file mode 100644 index 3c7a1bb916e..00000000000 --- a/tests/TON/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(TWTONCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTON)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTON, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTON, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTON)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTON)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTON), 9); - ASSERT_EQ(TWBlockchainTON, TWCoinTypeBlockchain(TWCoinTypeTON)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTON)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTON)); - assertStringsEqual(symbol, "GRAM"); - assertStringsEqual(txUrl, "https://test.ton.org/testnet/transaction?hash=t123"); - assertStringsEqual(accUrl, "https://test.ton.org/testnet/account?account=a12"); - assertStringsEqual(id, "ton"); - assertStringsEqual(name, "TON"); -} diff --git a/tests/TON/TWTONAddressTests.cpp b/tests/TON/TWTONAddressTests.cpp deleted file mode 100644 index 96281c0cee4..00000000000 --- a/tests/TON/TWTONAddressTests.cpp +++ /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. - -#include -#include -#include -#include -#include -#include - -#include "../interface/TWTestUtilities.h" - -#include - -TEST(TWTONAddress, CreateWithString) { - const char *expect = "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZT"; - auto addrStr = STRING(expect); - - // first call isValid - bool isValid = TWAnyAddressIsValid(addrStr.get(), TWCoinTypeTON); - ASSERT_TRUE(isValid); - - // create address - auto address = TWAnyAddressCreateWithString(addrStr.get(), TWCoinTypeTON); - // convert back to string - auto str2 = WRAPS(TWAnyAddressDescription(address)); - EXPECT_EQ(std::string(TWStringUTF8Bytes(addrStr.get())), std::string(TWStringUTF8Bytes(str2.get()))); - - { - // create a second one, also invoke compare - auto address2 = TWAnyAddressCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes(expect)).get(), TWCoinTypeTON); - ASSERT_TRUE(TWAnyAddressEqual(address, address2)); - - TWAnyAddressDelete(address2); - } - - TWAnyAddressDelete(address); -} - -TEST(TWTONAddress, CreateWithPublicKey) { - auto pkData = DATA("F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3"); - auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeED25519)); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeTON)); - auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - - EXPECT_EQ(std::string("EQAkAJCrZkWb9uYePf1D97nB8efUvYHTsqSscyPMGpcHUx3Y"), TWStringUTF8Bytes(addressStr.get())); -} - -TEST(TWTONAddress, 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(), TWCoinTypeTON, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeTON)).get())); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeTON)); - auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - - ASSERT_EQ(std::string("EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo"), TWStringUTF8Bytes(addressStr.get())); -} - -TEST(TWTON, SigningNotImplemented) { - // not implemented, returns empty data - auto result = WRAPD(TWAnySignerSign(WRAPD(TWDataCreateWithSize(0)).get(), TWCoinType::TWCoinTypeTON)); - EXPECT_EQ(TWDataSize(result.get()), 0); -} diff --git a/tests/Terra/TWCoinTypeTests.cpp b/tests/Terra/TWCoinTypeTests.cpp deleted file mode 100644 index 436d674856c..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("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTerra, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - 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, "LUNA"); - assertStringsEqual(txUrl, "https://terra.stake.id/#/tx/t123"); - assertStringsEqual(accUrl, "https://terra.stake.id/#/address/a12"); - assertStringsEqual(id, "terra"); - assertStringsEqual(name, "Terra"); -} diff --git a/tests/Tezos/AddressTests.cpp b/tests/Tezos/AddressTests.cpp deleted file mode 100644 index 9e2dfd00cab..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 a41a72067c7..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("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTomoChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - 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://scan.tomochain.com/txs/t123"); - assertStringsEqual(accUrl, "https://scan.tomochain.com/address/a12"); - assertStringsEqual(id, "tomochain"); - assertStringsEqual(name, "TomoChain"); -} 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 f34a168cc4f..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 bb1d915b0ea..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 a306d542ed4..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 a5250cce770..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 956f05639d3..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(TWBlockchainBitcoin, 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 678105d24ec..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(std::move(input)).sign(); - 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(std::move(input)).sign(); - 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/Zcoin/TWCoinTypeTests.cpp b/tests/Zcoin/TWCoinTypeTests.cpp deleted file mode 100644 index c11de5e831f..00000000000 --- a/tests/Zcoin/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(TWZcoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZcoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZcoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZcoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZcoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeZcoin)); - ASSERT_EQ(0x7, TWCoinTypeP2shPrefix(TWCoinTypeZcoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeZcoin)); - assertStringsEqual(symbol, "FIRO"); - assertStringsEqual(txUrl, "https://explorer.firo.org/tx/09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111"); - assertStringsEqual(accUrl, "https://explorer.firo.org/address/a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM"); - assertStringsEqual(id, "zcoin"); - assertStringsEqual(name, "Firo"); -} diff --git a/tests/Zcoin/TWZCoinAddressTests.cpp b/tests/Zcoin/TWZCoinAddressTests.cpp deleted file mode 100644 index 1957520ac6e..00000000000 --- a/tests/Zcoin/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(TWCoinTypeZcoin))); - 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, TWCoinTypeZcoin, TWHDVersionXPUB)); - auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeZcoin, TWHDVersionXPRV)); - - assertStringsEqual(xpub, "xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - assertStringsEqual(xprv, "xprv9ybmzbHKp4a6QqJ87tcHZh7nGGgqdrCUZYMh92cKegk6BFNZevum7DZhDuVDqqMdcBT9B4wJSEmwJW9JNdkMcUUjEWKqppxNrJjKFSyKsCr"); -} - -TEST(TWZcoin, DeriveFromXpub) { - auto xpub = STRING("xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - auto pubKey3 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/3").get())); - auto pubKey5 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/5").get())); - - auto address3 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey3.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin))); - auto address3String = WRAPS(TWBitcoinAddressDescription(address3.get())); - - auto address5 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey5.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin))); - auto address5String = WRAPS(TWBitcoinAddressDescription(address5.get())); - - assertStringsEqual(address3String, "aLnztJEbyACnxF9H7SFC8YjUxedwyQsgVm"); - assertStringsEqual(address5String, "aJj2jdMzHyKFJLEFTxhpn379avEqRKFUyw"); -} - -TEST(TWZcoin, LockScripts) { - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeZcoin)); - auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); - assertHexEqual(scriptData2, "76a9142a10f88e30768d2712665c279922b9621ce58bc788ac"); - - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeZcoin)); - auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); - assertHexEqual(scriptData3, "a914f010b17a9189e0f2737d71ae9790359eb5bbc13787"); -} diff --git a/tests/Zelcash/TWCoinTypeTests.cpp b/tests/Zelcash/TWCoinTypeTests.cpp deleted file mode 100644 index 50bfe25002f..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(TWBlockchainBitcoin, 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 4a2712bfbaf..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 db0ec1680ed..00000000000 --- a/tests/Zilliqa/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/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); - - ASSERT_EQ(hex(output.signature()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); -} 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/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..edd69951838 --- /dev/null +++ b/tests/chains/Acala/TWAnySignerTests.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Polkadot.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "PrivateKey.h" +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { + +TEST(TWAnySignerAcala, Sign) { + // Successfully broadcasted: https://acala.subscan.io/extrinsic/0xb2450990defef55f075f41969c8ae7965ddf8446a0aae9510b8bbdbacb4ff344 + const auto coin = TWCoinTypePolkadot; + 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); // New transactions should use a newer specification version, such as 2270. + 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 txInputData = data(input.SerializeAsString()); + 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 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), "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..c5038f13631 --- /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"), TWCurveED25519); + + 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"), TWCurveED25519); + + 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"), TWCurveED25519); + + 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"), TWCurveED25519); + + 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..ed6ba21e40e --- /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"), TWCurveED25519); + 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"), TWCurveED25519); + 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..f3a1b359439 --- /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"), TWCurveED25519); + 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://allo.info/tx/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA + auto key = PrivateKey(parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7"), TWCurveED25519); + 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"), TWCurveED25519); + 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"), TWCurveED25519); + 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"), TWCurveED25519); + 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..b60685fc277 --- /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://allo.info/tx/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..3e74467a395 --- /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://allo.info/tx/CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A"); + assertStringsEqual(accUrl, "https://allo.info/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..5df08888b70 --- /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, TWCurveED25519); + 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); + + /// 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..6c7a952b87f --- /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"), TWCurveED25519); + 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..814e71c6f02 --- /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"), TWCurveED25519); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign); + 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"), TWCurveED25519); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign); + 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..b0ad67d6d8c --- /dev/null +++ b/tests/chains/Aptos/TWAnySignerTests.cpp @@ -0,0 +1,97 @@ +// 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); +} + +TEST(TWAnySignerAptos, TxSignWithABI) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(69); + input.set_max_gas_amount(50000); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(1735902711); + input.set_chain_id(1); + input.set_any_encoded(R"( + { + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + } + )"); + input.set_abi(R"([ + "vector", + "u64", + "bool" + ])"); + 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()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001"); + ASSERT_EQ(hex(output.authenticator().signature()), "13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304"); +} + +} // 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/chains/Bitcoin/BitcoinAddressTests.cpp b/tests/chains/Bitcoin/BitcoinAddressTests.cpp new file mode 100644 index 00000000000..fa8507663df --- /dev/null +++ b/tests/chains/Bitcoin/BitcoinAddressTests.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Address.h" +#include "Bitcoin/Script.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include + +#include +#include + +using namespace TW; + +namespace TW::Bitcoin::tests { + +const char* TestPubKey1 = "039d645d2ce630c2a9a6dbe0cbd0a8fcb7b70241cb8a48424f25593290af2494b9"; +const char* TestP2phkAddr1 = "12dNaXQtN5Asn2YFwT1cvciCrJa525fAe4"; +const char* TestP2phkData1 = "0011d91ce1cc681f95583da3f4a6841c174be950c7"; +const char* TestP2shAddr1 = "3PQ5BD39rDikf7YW6pJ9a9tbS3QhvwvzTG"; +const char* TestP2shData1 = "05ee1e69460b59027d9df0a79ca2c92aa382a25fb7"; + +TEST(BitcoinAddress, P2PKH_CreateFromString) { + const auto address = Address(TestP2phkAddr1); + EXPECT_EQ(address.string(), TestP2phkAddr1); + EXPECT_EQ(hex(address.bytes), TestP2phkData1); +} + +TEST(BitcoinAddress, P2PKH_CreateFromPubkey) { + const auto publicKey = PublicKey(parse_hex(TestPubKey1), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeBitcoin)); + EXPECT_EQ(address.string(), TestP2phkAddr1); + EXPECT_EQ(hex(address.bytes), TestP2phkData1); +} + +TEST(BitcoinAddress, P2PKH_CreateFromData) { + const auto address = Address(parse_hex(TestP2phkData1)); + EXPECT_EQ(address.string(), TestP2phkAddr1); + EXPECT_EQ(hex(address.bytes), TestP2phkData1); +} + +TEST(BitcoinAddress, P2SH_CreateFromString) { + const auto address = Address(TestP2shAddr1); + EXPECT_EQ(address.string(), TestP2shAddr1); + EXPECT_EQ(hex(address.bytes), TestP2shData1); +} + +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"); + + 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); +} + +TEST(BitcoinAddress, P2SH_CreateFromData) { + const auto address = Address(parse_hex(TestP2shData1)); + 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/chains/Bitcoin/BitcoinScriptTests.cpp b/tests/chains/Bitcoin/BitcoinScriptTests.cpp new file mode 100644 index 00000000000..f4a6ed9fbbe --- /dev/null +++ b/tests/chains/Bitcoin/BitcoinScriptTests.cpp @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Script.h" +#include "Bitcoin/SignatureBuilder.h" +#include "TestUtilities.h" +#include "HexCoding.h" + +#include + +using namespace TW; +namespace TW::Bitcoin::tests { + +const Script PayToScriptHash(parse_hex("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87")); +const Script PayToWitnessScriptHash(parse_hex("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); +const Script PayToWitnessPublicKeyHash(parse_hex("0014" "79091972186c449eb1ded22b78e40d009bdf0089")); +const Script PayToPublicKeySecp256k1(parse_hex("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac")); +const Script PayToPublicKeySecp256k1Extended(parse_hex("41" "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "66b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91ac")); +const Script PayToPublicKeyHash(parse_hex("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac")); + +TEST(BitcoinScript, PayToPublicKey) { + const Script script = Script::buildPayToPublicKey(parse_hex("03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432")); + EXPECT_EQ(hex(script.bytes), hex(PayToPublicKeySecp256k1.bytes)); + const Script scriptExtended = Script::buildPayToPublicKey(parse_hex("0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91")); + EXPECT_EQ(hex(scriptExtended.bytes), hex(PayToPublicKeySecp256k1Extended.bytes)); + + Data res; + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + EXPECT_EQ(PayToScriptHash.matchPayToPublicKey(res), false); + EXPECT_EQ(PayToWitnessScriptHash.matchPayToPublicKey(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToPublicKey(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToPublicKey(res), false); +} + +TEST(BitcoinScript, PayToPublicKeyHash) { + const Script script = Script::buildPayToPublicKeyHash(parse_hex("79091972186c449eb1ded22b78e40d009bdf0089")); + EXPECT_EQ(hex(script.bytes), hex(PayToPublicKeyHash.bytes)); + + Data res; + EXPECT_EQ(PayToPublicKeyHash.matchPayToPublicKeyHash(res), true); + EXPECT_EQ(hex(res), "79091972186c449eb1ded22b78e40d009bdf0089"); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + EXPECT_EQ(PayToScriptHash.matchPayToPublicKeyHash(res), false); + EXPECT_EQ(PayToWitnessScriptHash.matchPayToPublicKeyHash(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToPublicKeyHash(res), false); +} + +TEST(BitcoinScript, PayToScriptHash) { + const Script script = Script::buildPayToScriptHash(parse_hex("4733f37cf4db86fbc2efed2500b4f4e49f312023")); + EXPECT_EQ(hex(script.bytes), hex(PayToScriptHash.bytes)); + + EXPECT_EQ(PayToScriptHash.isPayToScriptHash(), true); + EXPECT_EQ(PayToScriptHash.bytes.size(), 23ul); + + EXPECT_EQ(PayToWitnessScriptHash.isPayToScriptHash(), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isPayToScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToScriptHash(), false); + EXPECT_EQ(PayToPublicKeyHash.isPayToScriptHash(), false); + + Data res; + EXPECT_EQ(PayToScriptHash.matchPayToScriptHash(res), true); + EXPECT_EQ(hex(res), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + + EXPECT_EQ(PayToWitnessScriptHash.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToScriptHash(res), false); + 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(), 34ul); + + EXPECT_EQ(PayToScriptHash.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToPublicKeyHash.isPayToWitnessScriptHash(), false); + + Data res; + EXPECT_EQ(PayToWitnessScriptHash.matchPayToWitnessScriptHash(res), true); + EXPECT_EQ(hex(res), "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + + EXPECT_EQ(PayToScriptHash.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToWitnessScriptHash(res), false); +} + +TEST(BitcoinScript, PayToWitnessPublicKeyHash) { + const Script script = Script::buildPayToWitnessPublicKeyHash(parse_hex("79091972186c449eb1ded22b78e40d009bdf0089")); + EXPECT_EQ(hex(script.bytes), hex(PayToWitnessPublicKeyHash.bytes)); + + EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessPublicKeyHash(), true); + EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22ul); + + EXPECT_EQ(PayToScriptHash.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToPublicKeyHash.isPayToWitnessPublicKeyHash(), false); + + Data res; + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToWitnessPublicKeyHash(res), true); + EXPECT_EQ(hex(res), "79091972186c449eb1ded22b78e40d009bdf0089"); + + EXPECT_EQ(PayToScriptHash.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToWitnessScriptHash.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToWitnessPublicKeyHash(res), false); +} + +TEST(BitcoinScript, WitnessProgram) { + EXPECT_EQ(PayToWitnessScriptHash.isWitnessProgram(), true); + EXPECT_EQ(PayToWitnessPublicKeyHash.isWitnessProgram(), true); + + EXPECT_EQ(PayToScriptHash.isWitnessProgram(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isWitnessProgram(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isWitnessProgram(), false); + EXPECT_EQ(PayToPublicKeyHash.isWitnessProgram(), false); +} + +TEST(BitcoinScript, EncodeNumber) { + EXPECT_EQ(Script::encodeNumber(0), OP_0); + 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) { + EXPECT_EQ(Script::decodeNumber(OP_0), 0); + EXPECT_EQ(Script::decodeNumber(OP_1), 1); + EXPECT_EQ(Script::decodeNumber(OP_3), 3); + EXPECT_EQ(Script::decodeNumber(OP_9), 9); +} + +TEST(BitcoinScript, GetScriptOp) { + { + size_t index = 5; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("")).getScriptOp(index, opcode, operand), false); + } + { + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4f")).getScriptOp(index, opcode, operand), true); + 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, 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, 7ul); + EXPECT_EQ(opcode, 0x4c); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA1 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4c")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA1 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4c" "05" "010203")).getScriptOp(index, opcode, operand), false); + } + { // 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, 8ul); + EXPECT_EQ(opcode, 0x4d); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA2 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4d" "05")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA2 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4d" "0500" "010203")).getScriptOp(index, opcode, operand), false); + } + { // 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, 10ul); + EXPECT_EQ(opcode, 0x4e); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA4 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4e" "0500")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA4 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4e" "05000000" "010203")).getScriptOp(index, opcode, operand), false); + } +} + +TEST(BitcoinScript, MatchMultiSig) { + std::vector keys; + int required; + EXPECT_EQ(Script(parse_hex("")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("20")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("00ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("4fae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("20ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514cae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c05ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "05" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "00ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "52ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51aeae")).matchMultisig(keys, required), false); + + // 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(), 1ul); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("52" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); + + // 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(), 2ul); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + // OP_PUSHDATA1 + EXPECT_EQ(Script(parse_hex("514cae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c" "05" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c" "05" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + + // 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(), 1ul); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + // OP_PUSHDATA2 + EXPECT_EQ(Script(parse_hex("514dae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514d" "0500" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514d" "0500" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514d" "2100" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + + // 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(), 1ul); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + // OP_PUSHDATA4 + EXPECT_EQ(Script(parse_hex("514eae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "0500" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "05000000" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "05000000" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + + // 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(), 1ul); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + // valid three keys, mixed + EXPECT_EQ(Script(parse_hex("53" + "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" + "4d" "2100" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" + "4e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" + "53" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 3); + ASSERT_EQ(keys.size(), 3ul); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); +} + +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"); + } + { + 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(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) { + { + std::vector input = {}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), ""); + } + { + std::vector input = {parse_hex("")}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), "00"); + } + { + std::vector input = {parse_hex("09")}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), "59" "09"); + } + { + std::vector input = {parse_hex("00010203040506070809")}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), "0a" "00010203040506070809"); + } + { + std::vector input = {parse_hex("0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809")}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), "4c50" "0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809"); + } + { + // 2-byte len + Data in1 = Data(256 + 10); + Data expected = parse_hex("4d" "0a01"); + TW::append(expected, in1); + std::vector input = {in1}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), hex(expected)); + } + { + // 4-byte len + Data in1 = Data(65536 + 256 + 10); + Data expected = parse_hex("4e" "0a010100"); + TW::append(expected, in1); + std::vector input = {in1}; + Data res = SignatureBuilder::pushAll(input); + EXPECT_EQ(hex(res), hex(expected)); + } +} +} // 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/chains/Bitcoin/InputSelectorTests.cpp b/tests/chains/Bitcoin/InputSelectorTests.cpp new file mode 100644 index 00000000000..eef037af45a --- /dev/null +++ b/tests/chains/Bitcoin/InputSelectorTests.cpp @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "TxComparisonHelper.h" +#include "Bitcoin/InputSelector.h" + +#include + +namespace TW::Bitcoin { + +TEST(BitcoinInputSelector, SelectUnspents1) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 11000, 12000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(5000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {11000})); +} + +TEST(BitcoinInputSelector, SelectUnspents2) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(10000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {50000})); +} + +TEST(BitcoinInputSelector, SelectUnspents3) { + auto utxos = buildTestUTXOs({4000, 2000, 5000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(6000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {4000, 5000})); +} + +TEST(BitcoinInputSelector, SelectUnspents4) { + auto utxos = buildTestUTXOs({40000, 30000, 30000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(50000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {30000, 40000})); +} + +TEST(BitcoinInputSelector, SelectUnspents5) { + auto utxos = buildTestUTXOs({1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(28000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {6000, 7000, 8000, 9000})); +} + +TEST(BitcoinInputSelector, SelectUnspents5_simple) { + auto utxos = buildTestUTXOs({1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); + + auto selector = InputSelector(utxos); + auto selected = selector.selectSimple(28000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000})); +} + +TEST(BitcoinInputSelector, SelectUnspentsInsufficient) { + auto utxos = buildTestUTXOs({4000, 4000, 4000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(15000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); +} + +TEST(BitcoinInputSelector, SelectCustomCase) { + auto utxos = buildTestUTXOs({794121, 2289357}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(2287189, 61); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {794121, 2289357})); +} + +TEST(BitcoinInputSelector, SelectNegativeNoUTXOs) { + auto utxos = buildTestUTXOs({}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(100000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); +} + +TEST(BitcoinInputSelector, SelectNegativeTarget0) { + auto utxos = buildTestUTXOs({100'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(0, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); +} + +TEST(BitcoinInputSelector, SelectOneTypical) { + auto utxos = buildTestUTXOs({100'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(50'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); +} + +TEST(BitcoinInputSelector, SelectOneInsufficient) { + auto utxos = buildTestUTXOs({100'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(200'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); +} + +TEST(BitcoinInputSelector, SelectOneInsufficientEqual) { + auto utxos = buildTestUTXOs({100'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(100'000, 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. + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); +} + +TEST(BitcoinInputSelector, SelectOneInsufficientHigher) { + auto utxos = buildTestUTXOs({100'000}); + + 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. + 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); + auto expectedFee = 174; + auto selected = selector.select(100'000 - expectedFee, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + 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. + // 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, {100'000})); +} + +TEST(BitcoinInputSelector, SelectOneFitsExactlyHighfee) { + auto utxos = buildTestUTXOs({100'000}); + + const auto byteFee = 10; + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto expectedFee = 1740; + auto selected = selector.select(100'000 - expectedFee, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + 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. + // 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, {100'000})); +} + +TEST(BitcoinInputSelector, SelectThreeNoDust) { + auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + 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; + // Now 100'000 fits with no dust + selected = selector.select(100'000 - 174 - dustLimit, 1); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + // One more and we are over dust limit + selected = selector.select(100'000 - 174 - dustLimit + 1, 1); + EXPECT_TRUE(verifySelectedUTXOs(selected, {75'000, 100'000})); +} + +TEST(BitcoinInputSelector, SelectTwoFirstEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(15'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {20'000})); +} + +TEST(BitcoinInputSelector, SelectTwoSecondEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(70'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {80'000})); +} + +TEST(BitcoinInputSelector, SelectTwoBoth) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(90'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {20'000, 80'000})); +} + +TEST(BitcoinInputSelector, SelectTwoFirstEnoughButSecond) { + auto utxos = buildTestUTXOs({20'000, 22'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(18'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {22'000})); +} + +TEST(BitcoinInputSelector, SelectTenThree) { + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5'000, 125'000, 6'000, 150'000, 7'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(300'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); +} + +TEST(BitcoinInputSelector, SelectTenThree_simple1) { + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5'000, 125'000, 6'000, 150'000, 7'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.selectSimple(300'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {1'000, 2'000, 100'000, 3'000, 4'000, 5'000, 125'000, 6'000, 150'000})); +} + +TEST(BitcoinInputSelector, SelectTenThree_simple2) { + auto utxos = buildTestUTXOs({150'000, 125'000, 100'000, 7'000, 6'000, 5'000, 4'000, 3'000, 2'000, 1'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.selectSimple(300'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {150'000, 125'000, 100'000})); +} + +TEST(BitcoinInputSelector, SelectTenThree_simple3) { + auto utxos = buildTestUTXOs({1'000, 2'000, 3'000, 4'000, 5'000, 6'000, 7'000, 100'000, 125'000, 150'000}); + + auto selector = InputSelector(utxos); + auto selected = selector.selectSimple(300'000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {1'000, 2'000, 3'000, 4'000, 5'000, 6'000, 7'000, 100'000, 125'000, 150'000})); +} + +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); + const auto dustLimit = 102; + auto selected = selector.select(375'000 - 376 - dustLimit, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); + + ASSERT_EQ(feeCalculator.calculate(3, 2, 1), 376); + + // one more, and it's too much + selected = selector.select(375'000 - 376 - dustLimit + 1, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {7'000, 100'000, 125'000, 150'000})); +} + +TEST(BitcoinInputSelector, SelectMaxAmountOne) { + auto utxos = buildTestUTXOs({10189534}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto selected = selector.selectMaxAmount(1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); + + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); +} + +TEST(BitcoinInputSelector, SelectAllAvail) { + auto utxos = buildTestUTXOs({10189534}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto selected = selector.select(10189534 - 226, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); + + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); +} + +TEST(BitcoinInputSelector, SelectMaxAmount5of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto byteFee = 1; + auto selected = selector.selectMaxAmount(byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {400, 500, 600, 800, 1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 102); + EXPECT_EQ(feeCalculator.calculate(5, 1, byteFee), 548); +} + +TEST(BitcoinInputSelector, SelectMaxAmount4of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto byteFee = 4; + auto selected = selector.selectMaxAmount(byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {500, 600, 800, 1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 408); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1784); +} + +TEST(BitcoinInputSelector, SelectMaxAmount1of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto byteFee = 8; + auto selected = selector.selectMaxAmount(byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 816); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 1144); +} + +TEST(BitcoinInputSelector, SelectMaxAmountNone) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = InputSelector(utxos); + auto byteFee = 10; + auto selected = selector.selectMaxAmount(byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 1020); +} + +TEST(BitcoinInputSelector, SelectMaxAmountNoUTXOs) { + auto utxos = buildTestUTXOs({}); + + auto selector = InputSelector(utxos); + auto selected = selector.selectMaxAmount(1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); +} + +TEST(BitcoinInputSelector, SelectZcashUnspents) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); + + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); + auto selected = selector.select(10000, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {73774})); +} + +TEST(BitcoinInputSelector, SelectGroestlUnspents) { + auto utxos = buildTestUTXOs({499971976}); + + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); + auto selected = selector.select(499951976, 1, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {499971976})); +} + +TEST(BitcoinInputSelector, SelectZcashMaxAmount) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); + + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); + auto selected = selector.selectMaxAmount(1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100000, 2592, 73774})); +} + +TEST(BitcoinInputSelector, SelectZcashMaxUnspents2) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); + + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); + auto selected = selector.select(176366 - 6, 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. + EXPECT_TRUE(verifySelectedUTXOs(selected, {2592, 73774, 100000})); +} + +TEST(BitcoinInputSelector, ManyUtxos_900) { + const auto n = 900; + const auto byteFee = 10; + std::vector values; + uint64_t valueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + valueSum += val; + } + const uint64_t requestedAmount = valueSum / 8; + EXPECT_EQ(requestedAmount, 5'068'125ul); + auto utxos = buildTestUTXOs(values); + + auto selector = InputSelector(utxos); + auto selected = selector.select(requestedAmount, byteFee); + + // expected result: 59 utxos, with the largest amounts + std::vector subset; + uint64_t subsetSum = 0; + for (int i = n - 59; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + subset.push_back(val); + subsetSum += val; + } + EXPECT_EQ(subset.size(), 59ul); + EXPECT_EQ(subsetSum, 5'138'900ul); + EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); +} + +TEST(BitcoinInputSelector, ManyUtxos_5000_simple) { + const auto n = 5000; + const auto byteFee = 10; + std::vector values; + uint64_t valueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + valueSum += val; + } + const uint64_t requestedAmount = valueSum / 20; + EXPECT_EQ(requestedAmount, 62'512'500ul); + auto utxos = buildTestUTXOs(values); + + auto selector = InputSelector(utxos); + auto selected = selector.selectSimple(requestedAmount, byteFee); + + // 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) { + const uint64_t val = (i + 1) * 100; + subset.push_back(val); + subsetSum += val; + } + EXPECT_EQ(subset.size(), 1205ul); + EXPECT_EQ(subsetSum, 73'866'500ul); + EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); +} + +TEST(BitcoinInputSelector, ManyUtxos_MaxAmount_5000) { + const auto n = 5000; + const auto byteFee = 10; + std::vector values; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + } + auto utxos = buildTestUTXOs(values); + + auto selector = InputSelector(utxos); + auto selected = selector.selectMaxAmount(byteFee); + + // expected result: 4990 utxos (none of which is dust) + std::vector subset; + uint64_t subsetSum = 0; + 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(), 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/chains/Bitcoin/SegwitAddressTests.cpp b/tests/chains/Bitcoin/SegwitAddressTests.cpp new file mode 100644 index 00000000000..62c7fe7fde5 --- /dev/null +++ b/tests/chains/Bitcoin/SegwitAddressTests.cpp @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bech32.h" +#include "Bitcoin/SegwitAddress.h" +#include "HDWallet.h" +#include "HexCoding.h" + +#include +#include +#include +#include + +using namespace TW; +namespace TW::Bitcoin::tests { + +static const std::string valid_checksum[] = { + "A12UEL5L", + "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", +}; + +static const std::string invalid_checksum[] = { + " 1nwldj5", + "\x7f""1axkwrx", + "an124characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx0123456789012345678901234567890123456789", + "pzry9x0s0muk", + "1pzry9x0s0muk", + "x1b4n0q5v", + "li1dgmt3", + "de1lg7wt\xff", +}; + +struct valid_address_data { + std::string address; + std::string scriptPubKeyHex; +}; + +struct invalid_address_data { + std::string hrp; + int version; + size_t program_length; +}; + +static const std::vector valid_address = { + /// test vectors from BIP173 + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6"}, + {"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}, + //{"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, + //{"BC1SW50QA3JX3S", "6002751e"}, + //{"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323"}, + {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", "00140cb9f5c6b62c03249367bc20a90dd2425e6926af"}, + {"bc1qm9jzmujvdqjj6y28hptk859zs3yyv78hpqqjfj", "0014d9642df24c68252d1147b85763d0a284484678f7"}, + {"bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf", "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7"}, // Taproot + + /// test vectors from BIP350 + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, + {"BC1SW50QGDZ25J", "6002751e"}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", "5210751e76e8199196d454941c45d1b3a323"}, + {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, +}; + +static const std::vector invalid_address = { + /// test vectors from BIP73 + //"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", // Invalid human-readable part + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", // Invalid checksum + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", // Invalid witness version + "bc1rw5uspcuh", // Invalid program length + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", // Invalid program length + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", // Invalid program length for witness version 0 (per BIP141) + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", // Mixed case + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", // zero padding of more than 4 bits + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", // Non-zero padding in 8-to-5 conversion + "bc1gmk9yu", // Empty data section + + /// test vectors from BIP350 + //"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", // Invalid human-readable part + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", // Invalid checksum (Bech32 instead of Bech32m) + "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", // Invalid checksum (Bech32 instead of Bech32m) + "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", // Invalid checksum (Bech32 instead of Bech32m) + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", // Invalid checksum (Bech32m instead of Bech32) + "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", // Invalid checksum (Bech32m instead of Bech32) + "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", // Invalid character in checksum + "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", // Invalid witness version + "bc1pw5dgrnzv", // Invalid program length (1 byte) + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", // Invalid program length (41 bytes) + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", // Invalid program length for witness version 0 (per BIP141) + "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", // Mixed case + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", // zero padding of more than 4 bits + "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", // Non-zero padding in 8-to-5 conversion + "bc1gmk9yu", // Empty data section + + "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", // overall max length exceeded + "bc1q9zpgru", // 1 byte data (only version byte) + "BC1SW50QA3JX3S", // version = 16 + "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", // version = 2 +}; + +static const invalid_address_data invalid_address_enc[] = { + {"BC", 0, 20}, + {"bc", 0, 21}, + {"bc", 17, 32}, + {"bc", 1, 1}, + {"bc", 16, 41}, +}; + +static Data segwit_scriptpubkey(int witver, const Data& witprog) { + Data ret; + ret.push_back(witver ? (0x50 + (witver & 0x1f)) : 0); + ret.push_back(witprog.size()); + ret.insert(ret.end(), witprog.begin(), witprog.end()); + return ret; +} + +bool case_insensitive_equal(const std::string& s1, const std::string& s2) { + std::string s1l = s1; + std::transform(s1l.begin(), s1l.end(), s1l.begin(), [](unsigned char c){ return std::tolower(c); }); + std::string s2l = s2; + std::transform(s2l.begin(), s2l.end(), s2l.begin(), [](unsigned char c){ return std::tolower(c); }); + return s1l == s2l; +} + +TEST(SegwitAddress, ValidChecksum) { + 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()); + + auto recode = Bech32::encode(std::get<0>(dec), std::get<1>(dec), Bech32::ChecksumVariant::Bech32); + ASSERT_FALSE(recode.empty()); + + ASSERT_TRUE(case_insensitive_equal(recode, valid_checksum[i])); + } +} + +TEST(SegwitAddress, InvalidChecksum) { + 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()); + } +} + +TEST(SegwitAddress, ValidAddress) { + for (auto& td: valid_address) { + 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(), 2ul); // hrp + + // recode + std::string recode = std::get<0>(dec).string(); + EXPECT_FALSE(recode.empty()) << "Recode failed for " << td.address; + EXPECT_TRUE(case_insensitive_equal(td.address, recode)); + + Data spk = segwit_scriptpubkey(std::get<0>(dec).witnessVersion, std::get<0>(dec).witnessProgram); + EXPECT_EQ(hex(spk), td.scriptPubKeyHex); + } +} + +TEST(SegwitAddress, InvalidAddress) { + 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 = 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()); + } +} + +TEST(SegwitAddress, LegacyAddress) { + auto result = SegwitAddress::decode("TLWEciM1CjP5fJqM2r9wymAidkkYtTU5k3"); + EXPECT_FALSE(std::get<2>(result)); +} + +TEST(SegwitAddress, fromRaw) { + { + auto addr = SegwitAddress::fromRaw("bc", parse_hex("000e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16")); + EXPECT_TRUE(addr.second); + EXPECT_EQ(addr.first.string(), "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); + } + { + // empty data + auto addr = SegwitAddress::fromRaw("bc", Data()); + EXPECT_FALSE(addr.second); + } +} + +TEST(SegwitAddress, Equals) { + const auto dec1 = SegwitAddress::decode("bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"); + EXPECT_TRUE(std::get<2>(dec1)); + const auto addr1 = std::get<0>(dec1); + const auto dec2 = SegwitAddress::decode("bc1qm9jzmujvdqjj6y28hptk859zs3yyv78hpqqjfj"); + EXPECT_TRUE(std::get<2>(dec2)); + const auto addr2 = std::get<0>(dec2); + + 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/chains/Bitcoin/TWAnyAddressTests.cpp b/tests/chains/Bitcoin/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..f482783a01b --- /dev/null +++ b/tests/chains/Bitcoin/TWAnyAddressTests.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include "Hash.h" +#include "PublicKey.h" + +#include "TestUtilities.h" +#include + +namespace TW::Bitcoin::tests { + +void testBitcoinAddressFromPublicKeyDerivation(const char* privateKey, TWDerivation derivation, const char* expectedAddr) { + const auto data = DATA(privateKey); + const auto privkey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); + const auto pubkey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyByType(privkey.get(), TWPublicKeyTypeSECP256k1)); + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey.get(), TWCoinTypeBitcoin, derivation)); + const auto addrDescription = WRAPS(TWAnyAddressDescription(addr.get())); + assertStringsEqual(addrDescription, expectedAddr); +} + +TEST(TWBitcoinAnyAddress, CreateFromPublicKey) { + testBitcoinAddressFromPublicKeyDerivation( + "a26b7ffda8ad29cf3aba066cc43ce255cb13b3fba5fa9b638f4685333e3670fd", + TWDerivationDefault, + "bc1qj7uu67l9zkajuhx976d8fvj2ylc29gnq3kjm3r" + ); +} + +TEST(TWBitcoinAnyAddress, CreateFromPublicKeyWithDerivationLegacy) { + testBitcoinAddressFromPublicKeyDerivation( + "a8212108f1f2a42b18b642c2dbe6e388e75de89ca7be9b6f7648bab06cdedabf", + TWDerivationBitcoinLegacy, + "1JspsmYcHGLjRDavb62hodTb7BW8GGia7v" + ); +} + +TEST(TWBitcoinAnyAddress, CreateFromPublicKeyWithDerivationTaproot) { + testBitcoinAddressFromPublicKeyDerivation( + "e5bbc9cab0ff9ec86889bcd84d79ca484b017763614cd4fa3bf4aa49ad9d55a9", + TWDerivationBitcoinTaproot, + "bc1pkup60ng64cthqujp38l57y0eewkyqy35s86tmqjcc5yzgmz9l2gscz37tx" + ); + testBitcoinAddressFromPublicKeyDerivation( + "ae39965b1e73944763eb8ddaeb126d9cbde772a5c141e3ad5791f8e7f094a089", + TWDerivationBitcoinTaproot, + "bc1plvzy756f4t2p32n29jy9vrqcrj46g5yka7u7e89tkeufx5urgyss38y98d" + ); + testBitcoinAddressFromPublicKeyDerivation( + "e0721275d0f94bf4d0f59bf0bef42179f680e38dfb306e1d0e3c13ab1d797d20", + TWDerivationBitcoinTaproot, + "bc1puu933ez5v6w99w9dqsmwdeajpnrvknc65tlnrdx3xhxwd954mudsa39gum" + ); +} + +} // 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..83ef3a6c6ba --- /dev/null +++ b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Bitcoin.pb.h" +#include "proto/BitcoinV2.pb.h" +#include "PrivateKey.h" +#include "TestUtilities.h" + +#include + +namespace TW::Bitcoin::PsbtTests { + +const auto gPrivateKey = PrivateKey(parse_hex("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")); +const auto gPsbt = parse_hex("70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000"); + +TEST(TWBitcoinPsbt, SignThorSwap) { + BitcoinV2::Proto::SigningInput signing; + signing.mutable_psbt()->set_psbt(gPsbt.data(), gPsbt.size()); + signing.add_private_keys(gPrivateKey.bytes.data(), gPrivateKey.bytes.size()); + + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = signing; + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeBitcoin); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(output.has_signing_result_v2()); + + const auto& outputV2 = output.signing_result_v2(); + EXPECT_TRUE(outputV2.has_psbt()); + EXPECT_EQ(hex(outputV2.psbt().psbt()), "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); + EXPECT_EQ(hex(outputV2.encoded()), "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); +} + +TEST(TWBitcoinPsbt, PlanThorSwap) { + const auto publicKey = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + BitcoinV2::Proto::SigningInput inputV2; + inputV2.mutable_psbt()->set_psbt(gPsbt.data(), gPsbt.size()); + inputV2.add_public_keys(publicKey.bytes.data(), publicKey.bytes.size()); + + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = inputV2; + + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(legacy, plan, TWCoinTypeBitcoin); + + EXPECT_EQ(plan.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(plan.has_planning_result_v2()); + + const auto& planV2 = plan.planning_result_v2(); + EXPECT_EQ(planV2.send_amount(), 66'406); + EXPECT_EQ(planV2.fee_estimate(), 1'736); +} + +} diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp similarity index 86% rename from tests/Bitcoin/TWBitcoinScriptTests.cpp rename to tests/chains/Bitcoin/TWBitcoinScriptTests.cpp index 457c8f7dbff..1851e7ba9ba 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp @@ -1,37 +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 "../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); } } @@ -69,7 +75,7 @@ TEST(TWBitcoinScript, MatchPayToPubkey) { ASSERT_EQ(TWBitcoinScriptMatchPayToPubkey(PayToScriptHash.get()), nullptr); } -TEST(TWBitcoinScript, TWBitcoinScriptMatchPayToPubkeyHash) { +TEST(TWBitcoinScript, MatchPayToPubkeyHash) { const auto res = WRAPD(TWBitcoinScriptMatchPayToPubkeyHash(PayToPublicKeyHash.get())); ASSERT_TRUE(res.get() != nullptr); const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); @@ -112,12 +118,23 @@ TEST(TWBitcoinScript, Encode) { ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "17a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); } +TEST(TWBitcoinScript, BuildPayToPublicKey) { + const auto pubkey = DATA("03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + 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"); +} + TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { const auto hash = DATA("79091972186c449eb1ded22b78e40d009bdf0089"); 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) { @@ -125,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) { @@ -194,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"); } @@ -208,6 +226,7 @@ TEST(TWBitcoinSigHashType, HashTypeForCoin) { EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeLitecoin), (uint32_t)TWBitcoinSigHashTypeAll); EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeZcash), (uint32_t)TWBitcoinSigHashTypeAll); EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinCash), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeECash), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinGold), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG); } @@ -224,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..ed56956b738 --- /dev/null +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -0,0 +1,2385 @@ +// 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + + 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.add_private_keys(key.bytes.data(), key.bytes.size()); + signing.set_dangerous_use_fixed_schnorr_rng(true); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::V2); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); + + auto& chainInfo = *signing.mutable_chain_info(); + chainInfo.set_p2pkh_prefix(0); + chainInfo.set_p2sh_prefix(5); + + auto& in = *builder.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 = *builder.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 = *builder.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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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(), 1u); + + *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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + input.privateKeys.push_back(utxoKey0); + + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); + 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, TWCurveSECP256k1)); + auto key1 = parse_hex("11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"); + input.privateKeys.push_back(PrivateKey(key1, TWCurveSECP256k1)); + auto key2 = parse_hex("77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"); + input.privateKeys.push_back(PrivateKey(key2, TWCurveSECP256k1)); + auto key3 = parse_hex("14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"); + input.privateKeys.push_back(PrivateKey(key3, TWCurveSECP256k1)); + auto key4 = parse_hex("fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"); + input.privateKeys.push_back(PrivateKey(key4, TWCurveSECP256k1)); + auto key5 = parse_hex("428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"); + input.privateKeys.push_back(PrivateKey(key5, TWCurveSECP256k1)); + + 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"), TWCurveSECP256k1); + input.privateKeys.push_back(utxoKey0); + + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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..fd1aeb639c6 --- /dev/null +++ b/tests/chains/Bitcoin/TransactionCompilerTests.cpp @@ -0,0 +1,374 @@ +// 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"); + + input.mutable_chain_info()->set_p2pkh_prefix(0); + input.mutable_chain_info()->set_p2sh_prefix(5); + input.add_public_keys(alicePublicKey.bytes.data(), alicePublicKey.bytes.size()); + + auto txid0 = parse_hex("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + std::reverse(txid0.begin(), txid0.end()); + + // Step 1: Prepare transaction input (protobuf) + + auto& builder = *input.mutable_builder(); + + auto& utxo0 = *builder.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 = *builder.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()); + + builder.set_version(BitcoinV2::Proto::TransactionVersion::V2); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.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/chains/Bitcoin/TransactionPlanTests.cpp b/tests/chains/Bitcoin/TransactionPlanTests.cpp new file mode 100644 index 00000000000..357d4784979 --- /dev/null +++ b/tests/chains/Bitcoin/TransactionPlanTests.cpp @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TxComparisonHelper.h" +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/FeeCalculator.h" +#include "HexCoding.h" +#include "proto/Bitcoin.pb.h" +#include + +#include + +namespace TW::Bitcoin { + +TEST(TransactionPlan, OneTypical) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto sigingInput = buildSigningInput(50'000, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 147)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174); +} + +TEST(TransactionPlan, OneInsufficient) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(200'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // Max is returned + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 99'887, 113)); +} + +TEST(TransactionPlan, OneInsufficientEqual) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(100'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // Max is returned + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 99'887, 113)); +} + +TEST(TransactionPlan, OneInsufficientLower100) { + // requested is only slightly lower than avail, not enough for fee, cannot be satisfied + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(100'000 - 100, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); +} + +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 - 146, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); +} + +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); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 300, 147)); +} + +TEST(TransactionPlan, OneMoreRequested) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto sigingInput = buildSigningInput(150'000, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // Max is returned + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 99'887, 113)); +} + +TEST(TransactionPlan, OneFitsExactly) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + 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); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 174, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174); +} + +TEST(TransactionPlan, OneFitsExactlyHighFee) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 10; + 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); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 1740, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 1740); +} + +TEST(TransactionPlan, OneMissingPrivateKey) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto sigingInput = buildSigningInput(50'000, byteFee, utxos, false, TWCoinTypeBitcoin, true); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 147)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174); +} + +TEST(TransactionPlan, TwoFirstEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + auto sigingInput = buildSigningInput(15'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {20'000}, 15'000, 147)); +} + +TEST(TransactionPlan, TwoSecondEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + auto sigingInput = buildSigningInput(70'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {80'000}, 70'000, 147)); +} + +TEST(TransactionPlan, TwoBoth) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + auto sigingInput = buildSigningInput(90'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {20'000, 80'000}, 90'000, 215)); +} + +TEST(TransactionPlan, TwoFirstEnoughButSecond) { + auto utxos = buildTestUTXOs({20'000, 22'000}); + auto sigingInput = buildSigningInput(18'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {22'000}, 18'000, 147)); +} + +TEST(TransactionPlan, ThreeNoDust) { + auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); + auto sigingInput = buildSigningInput(100'000 - 174 - 10, 1, utxos); + + // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 174 - 10, 215)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); + EXPECT_EQ(feeCalculator.calculate(2, 2, 1), 275); + + const auto dustLimit = 102; + // Now 100'000 fits with no dust + sigingInput = buildSigningInput(100'000 - 174 - dustLimit, 1, utxos); + txPlan = TransactionBuilder::plan(sigingInput); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 174 - dustLimit, 147)); + + // One more and we are over dust limit + sigingInput = buildSigningInput(100'000 - 174 - dustLimit + 1, 1, utxos); + txPlan = TransactionBuilder::plan(sigingInput); + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 174 - dustLimit + 1, 215)); +} + +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 sigingInput = buildSigningInput(300'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000, 125'000, 150'000}, 300'000, 283)); +} + +TEST(TransactionPlan, NonMaxAmount) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); + auto sigingInput = buildSigningInput(10000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {50000}, 10000, 147)); +} + +TEST(TransactionPlan, UnspentsInsufficient) { + auto utxos = buildTestUTXOs({4000, 4000, 4000}); + auto sigingInput = buildSigningInput(15000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // Max is returned + EXPECT_TRUE(verifyPlan(txPlan, {4000, 4000, 4000}, 11751, 249)); +} + +TEST(TransactionPlan, SelectionSuboptimal_ExtraSmallUtxo) { + // Solution found 4-in-2-out {500, 600, 800, 1000} avail 2900 txamount 1570 fee 702 change 628 + // Better solution: 3-in-2-out {600, 800, 1000} avail 2400 txamount 1570 fee 566 change 264 + // 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 1570 fee 838 change 892 + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1'000}); + auto byteFee = 2; + auto sigingInput = buildSigningInput(1'570, byteFee, utxos); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 702; + EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1'000}, 1'570, expectedFee)); + auto change = 2'900 - 1'570 - expectedFee; + auto firstUtxo = txPlan.utxos[0].amount; + EXPECT_TRUE(change - 204 < txPlan.utxos[0].amount); + EXPECT_EQ(change, 628); + 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. + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1'000}); + auto byteFee = 2; + auto sigingInput = buildSigningInput(1'775, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {400, 500, 600, 800, 1000}, 1775, 838)); +} + +TEST(TransactionPlan, Inputs5_33Req19NoDustFee2) { + auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); + auto byteFee = 2; + auto sigingInput = buildSigningInput(19'000, byteFee, utxos); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 283 * byteFee; + EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 204); +} + +TEST(TransactionPlan, Inputs5_33Req19Dust1Fee5) { + auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); + auto byteFee = 5; + auto sigingInput = buildSigningInput(19'000, byteFee, utxos); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 283 * byteFee; + EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 510); +} + +TEST(TransactionPlan, Inputs5_33Req19Dust1Fee9) { + auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); + auto byteFee = 9; + auto sigingInput = buildSigningInput(19'000, byteFee, utxos); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 283 * byteFee; + EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 918); +} + +TEST(TransactionPlan, Inputs5_33Req19Fee20) { + auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); + auto byteFee = 20; + auto sigingInput = buildSigningInput(19'000, byteFee, utxos); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); +} + +TEST(TransactionPlan, Inputs5_33Req13Fee20) { + auto utxos = buildTestUTXOs({600, 1'200, 6'000, 8'000, 10'000}); + auto byteFee = 20; + auto sigingInput = buildSigningInput(13'000, byteFee, utxos); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 283 * byteFee; + EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 13'000, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 2040); +} + +TEST(TransactionPlan, NoUTXOs) { + auto utxos = buildTestUTXOs({}); + auto sigingInput = buildSigningInput(15000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_missing_input_utxos)); +} + +TEST(TransactionPlan, CustomCase) { + auto utxos = buildTestUTXOs({794121, 2289357}); + auto byteFee = 61; + auto sigingInput = buildSigningInput(2287189, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {794121, 2289357}, 2287189, 13115)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(2, 2, byteFee), 16775); +} + +TEST(TransactionPlan, Target0) { + auto utxos = buildTestUTXOs({2000, 3000}); + auto sigingInput = buildSigningInput(0, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_zero_amount_requested)); +} + +TEST(TransactionPlan, MaxAmount) { + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 40; + auto sigingInput = buildSigningInput(39200, byteFee, utxos, true); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4080); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 7240; + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - expectedFee, expectedFee)); +} + +TEST(TransactionPlan, MaxAmountOne) { + auto utxos = buildTestUTXOs({10189534}); + auto sigingInput = buildSigningInput(100, 1, utxos, true); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 113; + EXPECT_TRUE(verifyPlan(txPlan, {10189534}, 10189534 - expectedFee, expectedFee)); +} + +TEST(TransactionPlan, AmountEqualsMaxButNotUseMax) { + // amount is set to max, but UseMax is not set --> Max is returned + auto utxos = buildTestUTXOs({10189534}); + auto sigingInput = buildSigningInput(10189534, 1, utxos, false); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {10189534}, 10189421, 113)); +} + +TEST(TransactionPlan, MaxAmountRequestedIsLower) { + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 40; + auto sigingInput = buildSigningInput(10, byteFee, utxos, true); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4080); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 7240; + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - expectedFee, expectedFee)); +} + +TEST(TransactionPlan, MaxAmountRequestedZero) { + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 40; + auto sigingInput = buildSigningInput(0, byteFee, utxos, true); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4080); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 7240; + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - expectedFee, expectedFee)); +} + +TEST(TransactionPlan, MaxAmountNoDustFee2) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + auto byteFee = 2; + auto sigingInput = buildSigningInput(100, byteFee, utxos, true); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 770; + EXPECT_TRUE(verifyPlan(txPlan, {400, 500, 600, 800, 1000}, 3'300 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 204); + EXPECT_EQ(feeCalculator.calculate(5, 1, byteFee), 1096); +} + +TEST(TransactionPlan, MaxAmountDust1Fee4) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + auto byteFee = 4; + auto sigingInput = buildSigningInput(100, byteFee, utxos, true); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 1268; + EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1000}, 2'900 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 408); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1784); +} + +TEST(TransactionPlan, MaxAmountDust2Fee5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + auto byteFee = 5; + auto sigingInput = buildSigningInput(100, byteFee, utxos, true); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 1245; + EXPECT_TRUE(verifyPlan(txPlan, {600, 800, 1000}, 2'400 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 510); + EXPECT_EQ(feeCalculator.calculate(3, 1, byteFee), 1725); +} + +TEST(TransactionPlan, MaxAmountDustAllFee10) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + auto byteFee = 10; + auto sigingInput = buildSigningInput(100, byteFee, utxos, true); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 1020); +} + +TEST(TransactionPlan, One_MaxAmount_FeeMoreThanAvailable) { + auto utxos = buildTestUTXOs({340}); + auto byteFee = 1; + auto expectedFee = 113; + auto sigingInput = buildSigningInput(340, byteFee, utxos, true); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // Fee is reduced to availableAmount + EXPECT_TRUE(verifyPlan(txPlan, {340}, 340 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 143); +} + +TEST(TransactionPlan, MaxAmountDoge) { + auto utxos = buildTestUTXOs({Amount(100000000), Amount(2000000000), Amount(200000000)}); + ASSERT_EQ(sumUTXOs(utxos), Amount(2300000000)); + auto sigingInput = buildSigningInput(Amount(2300000000), 100, utxos, true, TWCoinTypeDogecoin); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100000000, 2000000000, 200000000}, 2299951200, 48800)); +} + +TEST(TransactionPlan, AmountDecred) { + auto utxos = buildTestUTXOs({Amount(39900000)}); + auto sigingInput = buildSigningInput(Amount(10000000), 10, utxos, false, TWCoinTypeDecred); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {39900000}, 10000000, 2540)); +} + +TEST(TransactionPlan, ManyUtxosNonmax_400) { + const auto n = 400; + const auto byteFee = 10; + std::vector values; + uint64_t valueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + valueSum += val; + } + const uint64_t requestedAmount = valueSum / 8; + EXPECT_EQ(requestedAmount, 1'002'500ul); + + auto utxos = buildTestUTXOs(values); + auto sigingInput = buildSigningInput(requestedAmount, byteFee, utxos, false, TWCoinTypeBitcoin); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // expected result: 27 utxos, with the largest amounts + std::vector subset; + uint64_t subsetSum = 0; + for (int i = n - 27; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + subset.push_back(val); + subsetSum += val; + } + EXPECT_EQ(subset.size(), 27ul); + EXPECT_EQ(subsetSum, 1'044'900ul); + EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 19'150)); +} + +TEST(TransactionPlan, ManyUtxosNonmax_5000_simple) { + const auto n = 5000; + const auto byteFee = 10; + std::vector values; + uint64_t valueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + valueSum += val; + } + const uint64_t requestedAmount = valueSum / 20; + 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); + auto sigingInput = buildSigningInput(requestedAmount, byteFee, utxos, false, TWCoinTypeRavencoin); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // expected result: 1220 utxos, with the smaller amounts (except the very small dust ones) + std::vector subset; + uint64_t subsetSum = 0; + for (int i = 14; i < 1220 + 14; ++i) { + const uint64_t val = (i + 1) * 100; + subset.push_back(val); + subsetSum += val; + } + EXPECT_EQ(subset.size(), 1220ul); + EXPECT_EQ(subsetSum, 76'189'000ul); + EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 1'806'380)); +} + +TEST(TransactionPlan, ManyUtxosMax_400) { + const auto n = 400; + const auto byteFee = 10; + std::vector values; + uint64_t valueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + valueSum += val; + } + + // Use Ravencoin, because of faster non-segwit estimation, and one of the original issues was with this coin. + auto utxos = buildTestUTXOs(values); + auto sigingInput = buildSigningInput(valueSum, byteFee, utxos, true, TWCoinTypeRavencoin); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // all are selected, except a few smallest UTXOs are filtered out + const uint64_t dustLimit = byteFee * 148; + std::vector filteredValues; + uint64_t filteredValueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + if (val > dustLimit) { + filteredValues.push_back(val); + filteredValueSum += val; + } + } + 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)); +} + +TEST(TransactionPlan, ManyUtxosMax_5000_simple) { + const auto n = 5000; + const auto byteFee = 10; + std::vector values; + uint64_t valueSum = 0; + for (int i = 0; i < n; ++i) { + const uint64_t val = (i + 1) * 100; + values.push_back(val); + valueSum += val; + } + + // Use Ravencoin, because of faster non-segwit estimation, and one of the original issues was with this coin. + auto utxos = buildTestUTXOs(values); + auto sigingInput = buildSigningInput(valueSum, byteFee, utxos, true, TWCoinTypeRavencoin); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + // only 3000 are selected, the first ones minus a few small dust ones + const uint64_t dustLimit = byteFee * 150; + std::vector filteredValues; + uint64_t filteredValueSum = 0; + for (int i = 0; i < 3000 + 14; ++i) { + const uint64_t val = (i + 1) * 100; + if (val >= dustLimit) { + filteredValues.push_back(val); + filteredValueSum += val; + } + } + 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)); +} + +TEST(TransactionPlan, OpReturn) { + auto ownAddress = "bc1q7s0a2l4aguksehx8hf93hs9yggl6njxds6m02g"; + auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; + auto utxoAmount = 342101; + auto toAmount = 300000; + int byteFee = 126; + Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); + + 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.set_output_op_return(memo.data(), memo.size()); + + auto& utxo = *signingInput.add_utxo(); + auto utxoHash = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(1); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + utxo.set_amount(utxoAmount); + + auto txPlan = TransactionBuilder::plan(signingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {342101}, 300000, 205 * byteFee)); + 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/chains/Bitcoin/TxComparisonHelper.cpp b/tests/chains/Bitcoin/TxComparisonHelper.cpp new file mode 100644 index 00000000000..3996815dd7c --- /dev/null +++ b/tests/chains/Bitcoin/TxComparisonHelper.cpp @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TxComparisonHelper.h" + +#include + +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "proto/Bitcoin.pb.h" +#include "Data.h" +#include "PrivateKey.h" +#include "HexCoding.h" +#include "BinaryCoding.h" + +#include +#include + +namespace TW::Bitcoin { + +auto emptyTxOutPoint = OutPoint(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"), 0); + +UTXO buildTestUTXO(int64_t amount) { + UTXO utxo; + utxo.amount = amount; + utxo.outPoint = emptyTxOutPoint; + utxo.outPoint.sequence = UINT32_MAX; + utxo.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + return utxo; +} + +UTXOs buildTestUTXOs(const std::vector& amounts) { + UTXOs utxos; + for (long long amount : amounts) { + utxos.push_back(buildTestUTXO(amount)); + } + return utxos; +} + +SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, bool useMaxAmount, enum TWCoinType coin, bool omitPrivateKey) { + SigningInput input; + input.amount = amount; + input.byteFee = byteFee; + input.useMaxAmount = useMaxAmount; + input.coinType = coin; + input.dustCalculator = std::make_shared(coin); + + if (!omitPrivateKey) { + auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); + auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); + assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + input.privateKeys.push_back(utxoKey); + } + + input.utxos = utxos; + input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; + input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; + return input; +} + +int64_t sumUTXOs(const UTXOs& utxos) { + int64_t s = 0u; + for (auto& utxo : utxos) { + s += utxo.amount; + } + return s; +} + +bool verifySelectedUTXOs(const UTXOs& selected, const std::vector& expectedAmounts) { + bool ret = true; + if (selected.size() != expectedAmounts.size()) { + ret = false; + std::cerr << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; + } + int errorCount = 0; + for (auto i = 0ul; i < selected.size() && i < expectedAmounts.size(); ++i) { + if (expectedAmounts[i] != selected[i].amount) { + ret = false; + ++errorCount; + if (errorCount < 10) { + std::cerr << "Wrong UTXOs amount, pos " << i << " amount " << selected[i].amount << " expected " << expectedAmounts[i] << std::endl; + } + } + } + return ret; +} + +bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmounts, int64_t outputAmount, int64_t fee, Common::Proto::SigningError error) { + bool ret = true; + if (!verifySelectedUTXOs(plan.utxos, utxoAmounts)) { + ret = false; + } + if (plan.amount != outputAmount) { + ret = false; + std::cerr << "Mismatch in amount, act " << plan.amount << ", exp " << outputAmount << std::endl; + } + if (plan.fee != fee) { + ret = false; + std::cerr << "Mismatch in fee, act " << plan.fee << ", exp " << fee << std::endl; + } + int64_t sumExpectedUTXOs = 0; + for (long long utxoAmount : utxoAmounts) { + sumExpectedUTXOs += utxoAmount; + } + if (plan.availableAmount != sumExpectedUTXOs) { + ret = false; + std::cerr << "Mismatch in availableAmount, act " << plan.availableAmount << ", exp " << sumExpectedUTXOs << std::endl; + } + int64_t expectedChange = sumExpectedUTXOs - outputAmount - fee; + if (plan.change != expectedChange) { + ret = false; + std::cerr << "Mismatch in change, act " << plan.change << ", exp " << expectedChange << std::endl; + } + if (plan.error != Common::Proto::OK) { + if (error != Common::Proto::OK) { + if (plan.error != error) { + ret = false; + std::cerr << "Unexpected error, act " << std::to_string(plan.error) << ", exp " << std::to_string(plan.error) << std::endl; + } + } else { + ret = false; + std::cerr << "Unexpected error " << std::to_string(plan.error) << std::endl; + } + } else { + if (error != Common::Proto::OK) { + ret = false; + std::cerr << "Missing expected error " << std::to_string(plan.error) << std::endl; + } + } + return ret; +} + +bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2) { + return s1.virtualBytes == s2.virtualBytes && s1.segwit == s2.segwit && s1.nonSegwit == s2.nonSegwit; +} + +EncodedTxSize getEncodedTxSize(const Transaction& tx) { + EncodedTxSize size{}; + { // full segwit size + Data data; + tx.encode(data, Transaction::SegwitFormatMode::Segwit); + size.segwit = data.size(); + } + { // non-segwit + Data data; + tx.encode(data, Transaction::SegwitFormatMode::NonSegwit); + size.nonSegwit = data.size(); + } + int64_t witnessSize = 0; + { // double check witness part: witness plus 2 bytes is the difference between segwit and non-segwit size + Data data; + tx.encodeWitness(data); + witnessSize = data.size(); + 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; + 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); + assert(size.virtualBytes == vSize2); + return size; +} + +bool validateEstimatedSize(const Transaction& tx, int smallerTolerance, int biggerTolerance) { + if (tx.previousEstimatedVirtualSize == 0) { + // no estimated size, do nothing + return true; + } + bool ret = true; + auto estSize = tx.previousEstimatedVirtualSize; + uint64_t vsize = getEncodedTxSize(tx).virtualBytes; + int64_t diff = estSize - vsize; + if (diff < smallerTolerance) { + ret = false; + std::cerr << "Estimated size too small! " << std::to_string(estSize) << " vs. " << std::to_string(vsize) << std::endl; + } + if (diff > biggerTolerance) { + ret = false; + std::cerr << "Estimated size too big! " << std::to_string(estSize) << " vs. " << std::to_string(vsize) << std::endl; + } + return ret; +} + +void prettyPrintScript(const Script& script) { + Data data; + encodeVarInt(script.bytes.size(), data); + std::cout << " \"" << hex(data) << "\""; + std::cout << " \"" << hex(script.bytes) << "\""; +} + +void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { + Data data; + encode32LE(tx._version, data); + std::cout << " \"" << hex(data) << "\" // version\n"; + + if (useWitnessFormat) { + std::cout << " \"0001\" // marker & flag\n"; + } + + // txins + data.clear(); + encodeVarInt(tx.inputs.size(), data); + std::cout << " \"" << hex(data) << "\" // inputs\n"; + for (auto& input : tx.inputs) { + auto& outpoint = reinterpret_cast(input.previousOutput); + std::cout << " \"" << hex(outpoint.hash) << "\""; + data.clear(); + encode32LE(outpoint.index, data); + std::cout << " \"" << hex(data) << "\""; + prettyPrintScript(input.script); + data.clear(); + encode32LE(input.sequence, data); + std::cout << " \"" << hex(data) << "\"\n"; + } + + // txouts + data.clear(); + encodeVarInt(tx.outputs.size(), data); + std::cout << " \"" << hex(data) << "\" // outputs\n"; + for (auto& output : tx.outputs) { + data.clear(); + encode64LE(output.value, data); + std::cout << " \"" << hex(data) << "\""; + prettyPrintScript(output.script); + std::cout << "\n"; + } + + if (useWitnessFormat) { + std::cout << " // witness\n"; + for (auto& input : tx.inputs) { + data.clear(); + encodeVarInt(input.scriptWitness.size(), data); + std::cout << " \"" << hex(data) << "\"\n"; + for (auto& item : input.scriptWitness) { + data.clear(); + encodeVarInt(item.size(), data); + std::cout << " \"" << hex(data) << "\""; + std::cout << " \"" << hex(item) << "\"\n"; + } + } + } + + data.clear(); + encode32LE(tx.lockTime, data); // nLockTime + 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/chains/BitcoinCash/TWBitcoinCashTests.cpp b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp new file mode 100644 index 00000000000..d29e118c648 --- /dev/null +++ b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Address.h" +#include "Bitcoin/SigHashType.h" +#include "HexCoding.h" +#include "proto/Bitcoin.pb.h" +#include "proto/BitcoinV2.pb.h" +#include "TestUtilities.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace TW; + +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)); +} + +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); +} + +TEST(BitcoinCash, InvalidAddress) { + // Wrong checksum + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeBitcoinCash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeBitcoinCash)); + + // Valid eCash addresses are invalid for BCH + EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeBitcoinCash)); + + EXPECT_TRUE(TWAnyAddressIsValid(STRING("ecash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("ecash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeBitcoinCash)); + + // Wrong prefix + EXPECT_FALSE(TWAnyAddressIsValid(STRING("bcash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + + // Wrong base 32 (characters o, i) + EXPECT_FALSE(TWAnyAddressIsValid(STRING("poi578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); +} + +TEST(BitcoinCash, LegacyToCashAddr) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), 0)); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); + + auto cashAddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeBitcoinCash)); + auto cashAddressString = WRAPS(TWAnyAddressDescription(cashAddress.get())); + assertStringsEqual(cashAddressString, "bitcoincash:qruxj7zq6yzpdx8dld0e9hfvt7u47zrw9gfr5hy0vh"); +} + +TEST(BitcoinCash, LockScript) { + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoinCash)); + auto data = WRAPD(TWAnyAddressData(address.get())); + auto rawData = WRAPD(TWDataCreateWithSize(0)); + TWDataAppendByte(rawData.get(), 0x00); + TWDataAppendData(rawData.get(), data.get()); + + auto legacyAddress = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(rawData.get())); + auto legacyString = WRAPS(TWBitcoinAddressDescription(legacyAddress.get())); + assertStringsEqual(legacyString, "1AwDXywmyhASpCCFWkqhySgZf8KiswFoGh"); + + auto keyHash = WRAPD(TWDataCreateWithBytes(legacyAddress.get()->impl.bytes.data() + 1, 20)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(keyHash.get())); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l").get(), TWCoinTypeBitcoinCash)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "a914b9604b7820876bc510009b8247316c4b801aff8a87"); +} + +TEST(BitcoinCash, 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 xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeBitcoinCash, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeBitcoinCash, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9yEvwSfPanK5gLYVnYvNyF2CEWJx1RsktQtKDeT6jnCnqASBiPCvFYHFSApXv39bZbF6hRaha1kWQBVhN1xjo7NHuhAn5uUfzy79TBuGiHh"); + assertStringsEqual(xpub, "xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw"); +} + +TEST(BitcoinCash, DeriveFromXPub) { + auto xpub = STRING("xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw"); + auto pubKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get())); + auto pubKey9 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/9").get())); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2.get(), TWCoinTypeBitcoinCash)); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + + auto address9 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey9.get(), TWCoinTypeBitcoinCash)); + auto address9String = WRAPS(TWAnyAddressDescription(address9.get())); + + assertStringsEqual(address2String, "bitcoincash:qq4cm0hcc4trsj98v425f4ackdq7h92rsy6zzstrgy"); + assertStringsEqual(address9String, "bitcoincash:qqyqupaugd7mycyr87j899u02exc6t2tcg9frrqnve"); +} + +TEST(BitcoinCash, SignTransaction) { + const int64_t amount = 600; + + // Transaction on Bitcoin Cash Mainnet + // https://blockchair.com/bitcoin-cash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 + + auto input = Proto::SigningInput(); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinCash)); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto hash0 = DATA("e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05"); + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + utxo0->mutable_out_point()->set_index(2); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(5151); + auto script0 = parse_hex("76a914aff1e0789e5fe316b729577665aa0a04d5b0f8c788ac"); + utxo0->set_script(script0.data(), script0.size()); + + auto utxoKey0 = DATA("7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384"); + input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); + + // Sign + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeBitcoinCash); + + 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(), 226ul); + ASSERT_EQ(hex(output.encoded()), + "01000000" + "01" + "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" "02000000" "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" "ffffffff" + "02" + "5802000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000"); +} + +TEST(BitcoinCash, SignTransactionV2) { + auto privateKey = parse_hex("7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384"); + auto txId = parse_hex("050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2"); + std::reverse(txId.begin(), txId.end()); + + BitcoinV2::Proto::SigningInput signing; + signing.add_private_keys(privateKey.data(), privateKey.size()); + signing.mutable_chain_info()->set_p2pkh_prefix(0); + signing.mutable_chain_info()->set_p2sh_prefix(5); + signing.mutable_chain_info()->set_hrp("bitcoincash"); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::V1); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); + + auto& in = *builder.add_inputs(); + auto& inOutPoint = *in.mutable_out_point(); + inOutPoint.set_hash(txId.data(), txId.size()); + inOutPoint.set_vout(2); + in.set_value(5151); + // Cash address without prefix. + in.set_receiver_address("qzhlrcrcne07x94h99thved2pgzdtv8ccujjy73xya"); + in.set_sighash_type(TWBitcoinSigHashTypeAll | TWBitcoinSigHashTypeFork); + + auto& out0 = *builder.add_outputs(); + out0.set_value(600); + // Legacy address. + out0.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + + auto& explicitChangeOutput = *builder.add_outputs(); + explicitChangeOutput.set_value(4325); + // Cash address with an explicit prefix. + explicitChangeOutput.set_to_address("bitcoincash:qz0q3xmg38sr94rw8wg45vujah7kzma3cskxymnw06"); + + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = signing; + legacy.set_coin_type(TWCoinTypeBitcoinCash); + + Proto::TransactionPlan plan; + ANY_PLAN(legacy, plan, TWCoinTypeBitcoin); + + ASSERT_EQ(plan.error(), Common::Proto::SigningError::OK); + const auto planV2 = plan.planning_result_v2(); + EXPECT_EQ(planV2.error(), Common::Proto::SigningError::OK) << planV2.error_message(); + + EXPECT_EQ(planV2.inputs_size(), 1); + EXPECT_EQ(planV2.outputs_size(), 2); + EXPECT_EQ(planV2.vsize_estimate(), 227ul); + EXPECT_EQ(planV2.fee_estimate(), 226); + EXPECT_EQ(planV2.change(), 0); + + Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeBitcoin); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_TRUE(output.has_signing_result_v2()); + const auto outputV2 = output.signing_result_v2(); + EXPECT_EQ(outputV2.error(), Common::Proto::SigningError::OK) << outputV2.error_message(); + ASSERT_EQ(hex(outputV2.encoded()), + "01000000" + "01" + "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" "02000000" "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" "ffffffff" + "02" + "5802000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "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..20e12d01de1 --- /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(TWBlockchainBitcoinCash, 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..bf0916d7617 --- /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"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); + 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"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); + 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"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); + 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..f71410173e6 --- /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"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); + 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/chains/BitcoinGold/TWAddressTests.cpp b/tests/chains/BitcoinGold/TWAddressTests.cpp new file mode 100644 index 00000000000..67d006b5b63 --- /dev/null +++ b/tests/chains/BitcoinGold/TWAddressTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include "TestUtilities.h" +#include "Bitcoin/Address.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" +#include "Coin.h" +#include +#include + +using namespace TW; + +const char *PRIVATE_KEY = "CA6D1402199530A5D610A01A53505B6A344CF61B0CCB2902D5AEFBEA63C274BB"; +const char *ADDRESS = "GSGUyooxtCUVBonYV8AANp7FvKy3WTvpMR"; +const char *FAKEADDRESS = "GSGUyooxtCUVBonYV9AANp7FvKy3WTvpMR"; + +TEST(TWBitcoinGoldAddress, Valid) { + ASSERT_TRUE(Bitcoin::Address::isValid(std::string(ADDRESS))); + ASSERT_FALSE(Bitcoin::Address::isValid(std::string(FAKEADDRESS))); +} + +TEST(TWBitcoinGoldAddress, PubkeyToAddress) { + const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY), TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + /// construct with public key + auto address = Bitcoin::Address(PublicKey(publicKey), p2pkhPrefix(TWCoinTypeBitcoinGold)); + ASSERT_EQ(address.string(), ADDRESS); +} diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp similarity index 89% rename from tests/BitcoinGold/TWBitcoinGoldTests.cpp rename to tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp index cb42b63979b..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())); @@ -78,12 +78,13 @@ 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()); @@ -93,22 +94,21 @@ 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); utxo0->mutable_out_point()->set_sequence(0xfffffffd); // Sign - auto txSigner = TransactionSigner(std::move(input)); - txSigner.transaction.lockTime = 0x00098971; - auto result = txSigner.sign(); + auto signingInput = SigningInput(input); + auto result = TransactionSigner::sign(signingInput); ASSERT_TRUE(result) << std::to_string(result.error()); auto signedTx = result.payload(); Data serialized; - txSigner.encodeTx(signedTx, serialized); + signedTx.encode(serialized); ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction "01000000" // version "0001" // marker & flag @@ -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 80% rename from tests/BitcoinGold/TWSignerTests.cpp rename to tests/chains/BitcoinGold/TWSignerTests.cpp index 06ebd74e5ed..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,17 +48,17 @@ 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 - ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + ANY_PLAN(input, plan, TWCoinTypeBitcoinGold); EXPECT_TRUE(verifyPlan(plan, {99'000}, amount, 141)); } @@ -73,17 +68,17 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { input.mutable_plan()->set_change(88851); // Sign - auto txSigner = TransactionSigner(std::move(input)); - txSigner.transaction.lockTime = 0x00098971; - auto result = txSigner.sign(); + auto signingInput = SigningInput(input); + auto result = TransactionSigner::sign(signingInput); ASSERT_TRUE(result) << std::to_string(result.error()); auto signedTx = result.payload(); Data serialized; - txSigner.encodeTx(signedTx, 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..43b4a62856e --- /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://eth.bobascan.com/tx/0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103"); + assertStringsEqual(accUrl, "https://eth.bobascan.com/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..ba8ed18e998 --- /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.callistodao.org/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.callistodao.org/address/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..aa2e4fb5763 --- /dev/null +++ b/tests/chains/Cardano/SigningTests.cpp @@ -0,0 +1,1072 @@ +// 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, data("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, data("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, TWCurveED25519ExtendedCardano); + 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, SignTransferTokenAmountNonUtf8) { + const auto ownAddress = "addr1q83kuum4jhwu3gxdwftdv2vezr0etmt3tp7phw5assltzl6t4afzguegnkcrdzp79vdcqswly775f33jvtpayl280qeqts960l"; + const auto privateKey = "009aba22621d98e008c266a8d19c493f5f80a3a4f55048a83168a9c856726852fc240e6e95d7dc4e8ea599d09d64f84fdbe951b2282f5e5ed374252d17be9507643b2d078e607b5327397f212e4f6607ff0b6dfc93bdc9ad2bd0a682887edb9f304a573e99c7c2022c925511f004c7c9b89e8569080d09e2c53dfb1d53726852d4735794e3d32eac2b17d4d7c94742a77b7400b66fa11eaeb6ae38ba2dea84612f0c38fd68b9751ed4cb4ac48fb5e19f985f809fff1cfe5303fbfd29aca43d66"; + const auto gensTokenPolicy = "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb"; + // Non UTF-8 assetName according to https://github.com/cardano-foundation/CIPs/tree/master/CIP-0067 + const auto gensTokenNameHex = "0014df1047454e53"; + const auto currentSlot = 138'888'357ul; + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("7b377e0cf7b83d67bb6919008c38e1a63be86c4831a93ad0cb45778b9f2f7e28"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(4); + utxo1->set_address(ownAddress); + utxo1->set_amount(1'700'000ul); + // GENS token (asset1266q2ewhgul7jh3xqpvjzqarrepfjuler20akr). + auto* token1 = utxo1->add_token_amount(); + token1->set_policy_id(gensTokenPolicy); + token1->set_asset_name_hex(gensTokenNameHex); + const auto tokenAmount1 = store(uint256_t(44'660'987ul)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + const auto privateKeyData = parse_hex(privateKey); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q875r037fjeqveg6xv5wke922ff897eyrnshlj3ryp4mypzt4afzguegnkcrdzp79vdcqswly775f33jvtpayl280qeq7zgptp"); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(666ul); // doesn't matter, max is used + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(gensTokenPolicy); + toToken->set_asset_name_hex(gensTokenNameHex); + const auto toTokenAmount = store(uint256_t(666ul)); // doesn't matter, max is used + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(currentSlot + 7200ul); + + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeCardano); + + EXPECT_EQ(plan.error(), Common::Proto::SigningError::OK); + { + EXPECT_EQ(plan.available_amount(), 1'700'000ul); + EXPECT_EQ(plan.amount(), 1'700'000ul - 167'818ul); + EXPECT_EQ(plan.fee(), 167'818ul); + EXPECT_EQ(plan.change(), 0ul); + EXPECT_EQ(plan.utxos_size(), 1); + EXPECT_EQ(plan.available_tokens_size(), 1); + + EXPECT_EQ(load(plan.available_tokens(0).amount()), 44'660'987ul); + // `assetName` must be empty as it's not a UTF-8 string. + EXPECT_EQ(plan.available_tokens(0).asset_name(), ""); + EXPECT_EQ(plan.available_tokens(0).asset_name_hex(), gensTokenNameHex); + + EXPECT_EQ(plan.output_tokens_size(), 1); + EXPECT_EQ(load(plan.output_tokens(0).amount()), 44'660'987ul); + // `assetName` must be empty as it's not a UTF-8 string. + EXPECT_EQ(plan.output_tokens(0).asset_name(), ""); + EXPECT_EQ(plan.output_tokens(0).asset_name_hex(), gensTokenNameHex); + EXPECT_EQ(plan.change_tokens_size(), 0); + } + + // set plan with specific fee, to match the real transaction + *input.mutable_plan() = plan; + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCardano); + + // https://cardanoscan.io/transaction/df89e81fbaec7485ba65ac3a2ffe4121a888f4937d085f3ad4f7e8e5192dea74 + // 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), "83a400818258207b377e0cf7b83d67bb6919008c38e1a63be86c4831a93ad0cb45778b9f2f7e2804018182583901fd41be3e4cb206651a3328eb64aa525272fb241ce17fca23206bb2044baf522473289db036883e2b1b8041df27bd44c63262c3d27d477832821a00176116a1581cdda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fba1480014df1047454e531a02a978fb021a00028f8a031a084760c5a10081825820748022805ee71f9fa31d06e60f14f0715a37c278c0690b565f26e1e1e83f930e5840386c5d05fb5cfdb11f1296e909a80314616cdd2779e5be5ea583e1a938ee8409f58b585c90248e1c0633638cc0f4517c03fdb59f17434267c2955e0fbbb3b609f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "df89e81fbaec7485ba65ac3a2ffe4121a888f4937d085f3ad4f7e8e5192dea74"); +} + +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..1fed5d1ff81 --- /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, TWCurveED25519ExtendedCardano).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, TWCurveED25519ExtendedCardano).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, TWCurveED25519ExtendedCardano).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, TWCurveED25519ExtendedCardano).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, TWCurveED25519ExtendedCardano).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..58f8f987883 --- /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, data("TOKEN1"), 0)}); + EXPECT_EQ(tb.minAdaAmount(), 1444443ul); + } + { // 2 policyId, 2 4-char asset names + auto tb = TokenBundle(); + tb.add(TokenAmount("012345678901234567890POLICY1", data("TOK1"), 20)); + tb.add(TokenAmount("012345678901234567890POLICY2", data("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); + Data name = data("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, data("TOK1"), 10), + TokenAmount(policyId2, data("TOK2"), 20), + TokenAmount(policyId2, data("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", data("TOK1"), 20)); + bundle.add(TokenAmount("012345678901234567890POLICY2", data("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", data("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", data("AADA"), 20)); + bundle.add(TokenAmount("6ac8ef33b510ec004fe11585f7c5a9f0c07f0c23428ab4f29c1d7d10", data("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/Cardano/VoteDelegationTests.cpp b/tests/chains/Cardano/VoteDelegationTests.cpp new file mode 100644 index 00000000000..4befdedf809 --- /dev/null +++ b/tests/chains/Cardano/VoteDelegationTests.cpp @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cardano/Transaction.h" +#include "Cardano/AddressV3.h" +#include "Cardano/Signer.h" +#include "proto/Cardano.pb.h" +#include + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Cbor.h" +#include "TestUtilities.h" + +#include + +namespace TW::Cardano::VoteDelegationTests { + +const auto privateKeyTest1 = "40b03ce671f57c56b3483a9f946d4650ed03b1583357018b40aeef3bec81e74df8472ff07b6dfe76e83ea50102f7063595fb24aedaaf01dd9b2cd526a5c7f2db2275254e04a05729852388f1be93a47f859f42087c22e4c78f9d4f6f09d9557820716973437afb26051f2493813d22fc356a1fbdd91ab44f3294ca26ec81e74d7729d9e2323846c7545e6d780bbeb2df204c1a9a1b58abfac647200aefa3cbc1e0746aaad10f546ceb656c75e4d6dadffa5197cf9dc58579f1348a6b3d397d58"; +const auto ownAddress1 = "addr1qx2v7hcfyctmv9vv42yntu40um8t67lxkdzq24gvkfjmy8mlajpf8n6kj3v2sprfkaqfqhtayzjy7nx6apm0vn3j0dqs8x4237"; +const auto stakingAddress1 = "stake1u9l7eq5neatfgk9gq35mwsyst47jpfz0fndwsahkfce8ksgsx8ef2"; +const auto dRepAddressCIP129 = "drep1y29h2q6cst2pvkl2sqqvf5ljcy36uv7pmy482xnczddzgqshus24w"; // Eternl DRep Committee +const auto dRepAddressCIP105 = "drep13d6sxkyz6st9h65qqrzd8ukpywhr8swe9f6357qntgjqye0gttd"; // Eternl DRep Committee + +TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP129) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).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("b02dfc255f1048260d1915c9ebcfcdb69bab7677e75d45ec1245c468f0283d7e"); + 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(4790541ul); + + 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_use_max_amount(true); + input.set_ttl(148398202); + + // Vote delegate to always abstain + input.mutable_vote_delegation()->set_staking_address(stakingAddress); + input.mutable_vote_delegation()->set_drep_type(Proto::VoteDelegation::DREP_ID); + input.mutable_vote_delegation()->set_drep_id(dRepAddressCIP129); + + const auto amount = 4617164ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(4790541ul); + input.mutable_plan()->set_fee(173377); + 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); + + const auto txid = data(output.tx_id()); + // Broadcasted here: https://cardanoscan.io/transaction/290f07d69f75f396e78b4ee65088dbf7dc63b86d5b3a5bb7d6aeec59bd487d25 + EXPECT_EQ(hex(txid), "290f07d69f75f396e78b4ee65088dbf7dc63b86d5b3a5bb7d6aeec59bd487d25"); + + const auto encoded = data(output.encoded()); + const auto txHex = hex(encoded); + EXPECT_EQ(txHex, "84a50081825820b02dfc255f1048260d1915c9ebcfcdb69bab7677e75d45ec1245c468f0283d7e0001818258390194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b411a004673cc021a0002a541031a08d8607a048183098200581c7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b418200581c8b75035882d4165bea8000c4d3f2c123ae33c1d92a751a78135a2402a10082825820b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed04710365840c3f26171f723e4876cccbbac40edb373c64ccee8b5eec1b39bfa2a478f0b963540a7e91f10423b2a42b43732c5fbbe536c85dbc228e9b395889c05bf5cb1190982582064982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc4035840518b68bcca2b0d732614b114f384abfffe79b92877d599bf7e9810642f4eef2df696ea113b698eff6a6e25a791d342faef0ef565aaeed3226c7b88f357ba1900f5f6"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"b02dfc255f1048260d1915c9ebcfcdb69bab7677e75d45ec1245c468f0283d7e\", 0]], 1: [[h\"0194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\", 4617164]], 2: 173377, 3: 148398202, 4: [[9, [0, h\"7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\"], [0, h\"8b75035882d4165bea8000c4d3f2c123ae33c1d92a751a78135a2402\"]]]}, {0: [[h\"b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed0471036\", h\"c3f26171f723e4876cccbbac40edb373c64ccee8b5eec1b39bfa2a478f0b963540a7e91f10423b2a42b43732c5fbbe536c85dbc228e9b395889c05bf5cb11909\"], [h\"64982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc403\", h\"518b68bcca2b0d732614b114f384abfffe79b92877d599bf7e9810642f4eef2df696ea113b698eff6a6e25a791d342faef0ef565aaeed3226c7b88f357ba1900\"]]}, spec 21, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 4ul); + } +} + +TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP105) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).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("b02dfc255f1048260d1915c9ebcfcdb69bab7677e75d45ec1245c468f0283d7e"); + 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(4790541ul); + + 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_use_max_amount(true); + input.set_ttl(148398202); + + // Vote delegate to always abstain + input.mutable_vote_delegation()->set_staking_address(stakingAddress); + input.mutable_vote_delegation()->set_drep_type(Proto::VoteDelegation::DREP_ID); + input.mutable_vote_delegation()->set_drep_id(dRepAddressCIP105); + + const auto amount = 4617164ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(4790541ul); + input.mutable_plan()->set_fee(173377); + 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); + + const auto txid = data(output.tx_id()); + // Broadcasted here: https://cardanoscan.io/transaction/290f07d69f75f396e78b4ee65088dbf7dc63b86d5b3a5bb7d6aeec59bd487d25 + EXPECT_EQ(hex(txid), "290f07d69f75f396e78b4ee65088dbf7dc63b86d5b3a5bb7d6aeec59bd487d25"); + + const auto encoded = data(output.encoded()); + const auto txHex = hex(encoded); + EXPECT_EQ(txHex, "84a50081825820b02dfc255f1048260d1915c9ebcfcdb69bab7677e75d45ec1245c468f0283d7e0001818258390194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b411a004673cc021a0002a541031a08d8607a048183098200581c7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b418200581c8b75035882d4165bea8000c4d3f2c123ae33c1d92a751a78135a2402a10082825820b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed04710365840c3f26171f723e4876cccbbac40edb373c64ccee8b5eec1b39bfa2a478f0b963540a7e91f10423b2a42b43732c5fbbe536c85dbc228e9b395889c05bf5cb1190982582064982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc4035840518b68bcca2b0d732614b114f384abfffe79b92877d599bf7e9810642f4eef2df696ea113b698eff6a6e25a791d342faef0ef565aaeed3226c7b88f357ba1900f5f6"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"b02dfc255f1048260d1915c9ebcfcdb69bab7677e75d45ec1245c468f0283d7e\", 0]], 1: [[h\"0194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\", 4617164]], 2: 173377, 3: 148398202, 4: [[9, [0, h\"7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\"], [0, h\"8b75035882d4165bea8000c4d3f2c123ae33c1d92a751a78135a2402\"]]]}, {0: [[h\"b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed0471036\", h\"c3f26171f723e4876cccbbac40edb373c64ccee8b5eec1b39bfa2a478f0b963540a7e91f10423b2a42b43732c5fbbe536c85dbc228e9b395889c05bf5cb11909\"], [h\"64982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc403\", h\"518b68bcca2b0d732614b114f384abfffe79b92877d599bf7e9810642f4eef2df696ea113b698eff6a6e25a791d342faef0ef565aaeed3226c7b88f357ba1900\"]]}, spec 21, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 4ul); + } +} + +TEST(CardanoVoteDelegation, DelegateToAlwaysAbstain) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).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("6a96dc0aaa9a7ec6cd7dfcea679a71ff55e7661d609772829f3a4a33a0b7c942"); + 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(5307944ul); + + 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_use_max_amount(true); + input.set_ttl(148390086); + + // Vote delegate to always abstain + input.mutable_vote_delegation()->set_staking_address(stakingAddress); + input.mutable_vote_delegation()->set_drep_type(Proto::VoteDelegation::DREP_ALWAYS_ABSTAIN); + + const auto amount = 5135887ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(5307944ul); + input.mutable_plan()->set_fee(172057); + 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); + + const auto txid = data(output.tx_id()); + // Broadcasted here: https://cardanoscan.io/transaction/99b8a9d39e6c0c7df8ff3004097b68f7e6acd0e382182126afa00859c37b1bda + EXPECT_EQ(hex(txid), "99b8a9d39e6c0c7df8ff3004097b68f7e6acd0e382182126afa00859c37b1bda"); + + const auto encoded = data(output.encoded()); + const auto txHex = hex(encoded); + EXPECT_EQ(txHex, "84a500818258206a96dc0aaa9a7ec6cd7dfcea679a71ff55e7661d609772829f3a4a33a0b7c9420001818258390194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b411a004e5e0f021a0002a019031a08d840c6048183098200581c7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b418102a10082825820b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed04710365840c5ff299707cab2067d4c7d62cc1e72fd73c510d5d509376fea25ec8e7545fdb686db986a88b9a50c7100b61d564dd40fb796c1aeb1a2a1d3555df2d0cc086e0282582064982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc4035840555ee0ed2bc5ed50e3a22981b08e61867353c6a591265e5b66aec736ba0ee12eb5b181a38e678175df84b7bc8686b74fbf37e1bc3773748d5af002f1501a5d00f5f6"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"6a96dc0aaa9a7ec6cd7dfcea679a71ff55e7661d609772829f3a4a33a0b7c942\", 0]], 1: [[h\"0194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\", 5135887]], 2: 172057, 3: 148390086, 4: [[9, [0, h\"7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\"], [2]]]}, {0: [[h\"b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed0471036\", h\"c5ff299707cab2067d4c7d62cc1e72fd73c510d5d509376fea25ec8e7545fdb686db986a88b9a50c7100b61d564dd40fb796c1aeb1a2a1d3555df2d0cc086e02\"], [h\"64982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc403\", h\"555ee0ed2bc5ed50e3a22981b08e61867353c6a591265e5b66aec736ba0ee12eb5b181a38e678175df84b7bc8686b74fbf37e1bc3773748d5af002f1501a5d00\"]]}, spec 21, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 4ul); + } +} + +TEST(CardanoVoteDelegation, DelegateToNoConfidence) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).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("8de1ec78274095fbc02ad579b471c9394665d402f29fc8329f8de61039328bbf"); + 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(5000000ul); + + 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_use_max_amount(true); + input.set_ttl(148393182ul); + + // Vote delegate to always abstain + input.mutable_vote_delegation()->set_staking_address(stakingAddress); + input.mutable_vote_delegation()->set_drep_type(Proto::VoteDelegation::DREP_NO_CONFIDENCE); + + const auto amount = 4827943ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(5000000ul); + input.mutable_plan()->set_fee(172057); + 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); + + const auto txid = data(output.tx_id()); + // Broadcasted here: https://cardanoscan.io/transaction/b2fa7c73261af656bd7dd3869d3f3e57e527d7c0777f82766c851a627defb4e2 + EXPECT_EQ(hex(txid), "b2fa7c73261af656bd7dd3869d3f3e57e527d7c0777f82766c851a627defb4e2"); + + const auto encoded = data(output.encoded()); + const auto txHex = hex(encoded); + EXPECT_EQ(txHex, "84a500818258208de1ec78274095fbc02ad579b471c9394665d402f29fc8329f8de61039328bbf0101818258390194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b411a0049ab27021a0002a019031a08d84cde048183098200581c7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b418103a10082825820b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed04710365840ee72cde1827222eb587ada0f4dc4a6f4a35a3059d91c8cebdca0d8424126f322db540f985e0e8968e526c5bdd15a63b04afcd7864bd45a038de38ce057bbf00982582064982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc4035840895174677319cfc209030d64b6d50bce1604f6b30b873d5905bb3d33551e01bb7d340a5f3f36e60e6fa42befcb2bec4f0f890e1ec6977b064b21511837e8cc0ff5f6"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"8de1ec78274095fbc02ad579b471c9394665d402f29fc8329f8de61039328bbf\", 1]], 1: [[h\"0194cf5f092617b6158caa8935f2afe6cebd7be6b34405550cb265b21f7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\", 4827943]], 2: 172057, 3: 148393182, 4: [[9, [0, h\"7fec8293cf569458a80469b740905d7d20a44f4cdae876f64e327b41\"], [3]]]}, {0: [[h\"b4ccb8cb788b37b038d327f0da87b32c6abedf4b131c87103637ef2ed0471036\", h\"ee72cde1827222eb587ada0f4dc4a6f4a35a3059d91c8cebdca0d8424126f322db540f985e0e8968e526c5bdd15a63b04afcd7864bd45a038de38ce057bbf009\"], [h\"64982cd6a2a0f49aa8225ab48b3ae3bffab2e19e486f932b303052dc227bc403\", h\"895174677319cfc209030d64b6d50bce1604f6b30b873d5905bb3d33551e01bb7d340a5f3f36e60e6fa42befcb2bec4f0f890e1ec6977b064b21511837e8cc0f\"]]}, spec 21, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 4ul); + } +} + +} // namespace TW::Cardano::VoteDelegationTests 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..87032b6ec58 --- /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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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/chains/Cosmos/Protobuf/.gitignore b/tests/chains/Cosmos/Protobuf/.gitignore new file mode 100644 index 00000000000..c96d61208c0 --- /dev/null +++ b/tests/chains/Cosmos/Protobuf/.gitignore @@ -0,0 +1,3 @@ +*.cc +*.h + diff --git a/tests/chains/Cosmos/Protobuf/Article.proto b/tests/chains/Cosmos/Protobuf/Article.proto new file mode 100644 index 00000000000..d6f8ade819e --- /dev/null +++ b/tests/chains/Cosmos/Protobuf/Article.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package blog; + +enum Type { + TYPE_UNSPECIFIED = 0; + IMAGES = 1; + NEWS = 2; +}; + +enum Review { + REVIEW_UNSPECIFIED = 0; + ACCEPTED = 1; + REJECTED = 2; +}; + +message Article { + string title = 1; + string description = 2; + uint64 created = 3; + uint64 updated = 4; + bool public = 5; + bool promoted = 6; + Type type = 7; + Review review = 8; + repeated string comments = 9; + repeated string backlinks = 10; +}; 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..57f39e57293 --- /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, TWCurveSECP256k1).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..b0f8f9d9d83 --- /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, TWCurveSECP256k1).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, TWCurveSECP256k1).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, TWCoinTypeCurve(TWCoinTypeBinance))), 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, TWCoinTypeCurve(TWCoinTypeBinance))), 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, TWCurveSECP256k1).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, TWCurveSECP256k1).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, TWCurveSECP256k1).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, TWCurveSECP256k1).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..1aabc6655f9 --- /dev/null +++ b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp @@ -0,0 +1,416 @@ +// 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, TWCurveSECP256k1).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(), 144ul); + // 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); + + // Override the chainId as it has been after a hardfork recently. + txInput.set_chain_id("thorchain-mainnet-v1"); + 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(), 147ul); + // 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_chain_id("thorchain-mainnet-v1"); + 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..0d8884dbee3 --- /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"), TWCurveED25519); + 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..5e34583badf --- /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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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..c42bf5ca753 --- /dev/null +++ b/tests/chains/Decred/TWAnySignerTests.cpp @@ -0,0 +1,180 @@ + +// 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)); +} + +TEST(TWAnySignerDecred, SignV2) { + auto privateKey = parse_hex("99ed469e6b7d9f188962940d9d0f9fd8582c6c37e52394348f177ff0526b8a03"); + auto txId = parse_hex("c5cc3b1fc20c9e43a7d1127ba7e4802d04c16515a7eaaad58a1bc388acacfeae"); + std::reverse(txId.begin(), txId.end()); + int64_t inAmount = 100'000'000; + int64_t outAmount1 = 10'000'000; + int64_t outAmount2 = 5'000'000; + auto senderAddress = "DscNJ2Ki7HUPHrLGF2teBxSda3uxKSY7Fm6"; + auto toAddress = "Dsofok7qyhDLVRXcTqYdFgmGsUFSiHonbWH"; + + BitcoinV2::Proto::SigningInput signing; + signing.add_private_keys(privateKey.data(), privateKey.size()); + + auto& chainInfo = *signing.mutable_chain_info(); + chainInfo.set_p2pkh_prefix(63); + chainInfo.set_p2sh_prefix(26); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::UseDefault); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::SelectDescending); + builder.set_fixed_dust_threshold(546); + builder.set_fee_per_vb(10); + + auto& in = *builder.add_inputs(); + auto& inOutPoint = *in.mutable_out_point(); + inOutPoint.set_hash(txId.data(), txId.size()); + inOutPoint.set_vout(0); + in.set_value(inAmount); + in.set_receiver_address(senderAddress); + in.set_sighash_type(TWBitcoinSigHashTypeAll); + + // 0.1 DCR to another address. + auto& out1 = *builder.add_outputs(); + out1.set_value(outAmount1); + out1.set_to_address(toAddress); + + // 0.05 DCR to self. + auto& out2 = *builder.add_outputs(); + out2.set_value(outAmount2); + out2.set_to_address(senderAddress); + + // Send remaining amount to self by my address. + auto& changeOut = *builder.mutable_change_output(); + changeOut.set_to_address(senderAddress); + + Bitcoin::Proto::SigningInput legacy; + legacy.set_coin_type(TWCoinTypeDecred); + *legacy.mutable_signing_v2() = signing; + + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(legacy, plan, TWCoinTypeDecred); + EXPECT_EQ(plan.error(), Common::Proto::OK); + ASSERT_TRUE(plan.has_planning_result_v2()); + EXPECT_EQ(plan.planning_result_v2().error(), Common::Proto::SigningError::OK) + << plan.planning_result_v2().error_message(); + EXPECT_EQ(plan.planning_result_v2().vsize_estimate(), 289ul); + + Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeDecred); + + 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()), "0100000001aefeacac88c31b8ad5aaeaa71565c1042d80e4a77b12d1a7439e0cc21f3bccc50000000000ffffffff03809698000000000000001976a914f90f06478396b97df24c012b922f953fa894676188ac404b4c000000000000001976a9147d15c291fb7de05a38e39121df1e02f875a49f8988acf6f310050000000000001976a9147d15c291fb7de05a38e39121df1e02f875a49f8988ac00000000000000000100e1f5050000000000000000ffffffff6b483045022100c5464db9df8196c563db40ba1f7650291c755713ad87920a70c38c15db17bc3d02201c6bc9e4f8e43fdd548b0be387dd7baf7270bced8c9da4148128a655bed52e1a01210241d97dd9273a477c3560f1c5dba9dfd49624d8718ccca5619ff4a688dfcef01b"); + EXPECT_EQ(hex(output.signing_result_v2().txid()), "686a9057b6cf4d8aec1dd4ec0963b6f8e172d9fe90784456d2f93c403f163409"); +} + +} // 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..1c9921bebc2 --- /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, TWCurveSECP256k1).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 78% rename from tests/DigiByte/TWDigiByteTests.cpp rename to tests/chains/DigiByte/TWDigiByteTests.cpp index 5df0d26c4dc..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); @@ -57,35 +55,33 @@ TEST(DigiByteTransaction, SignTransaction) { plan.fee = fee; plan.change = utxo_amount - amount - fee; - auto &protoPlan = *input.mutable_plan(); + auto& protoPlan = *input.mutable_plan(); protoPlan = plan.proto(); // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); + auto result = Bitcoin::TransactionSigner::sign(Bitcoin::SigningInput(input)); auto signedTx = result.payload(); - ASSERT_TRUE(result); - ASSERT_EQ(fee, signer.plan.fee); 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) { @@ -101,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); @@ -111,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"); @@ -119,19 +116,18 @@ TEST(DigiByteTransaction, SignP2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); + auto result = Bitcoin::TransactionSigner::sign(input); ASSERT_TRUE(result) << std::to_string(result.error()); auto signedTx = result.payload(); Data serialized; - signer.encodeTx(signedTx, serialized); + signedTx.encode(serialized); ASSERT_EQ(hex(serialized), "0100000000010180a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea59400100000000ffffffff0280841e00000000001600145c91bc8d2073529224e8be0764128ac22f000564d3351e00000000001600144b62694cfdd7bdac59cbed211288ccd5c0dabd0202473044022057b876880b6c98511d9e5baab00428c50bf96868bdf4dc50bd61c2477ed8438b0220312ff89a078ab5a38b7b909ceb58310d93a5b4e2d637b37b77c4d7baf35a1815012102ac2e56f40d38530fcbf21f1eba0c3c668aa839cda8f2c615e99df44b6447772600000000"); } 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"); @@ -141,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..96928e1828e --- /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(TWBlockchainBitcoinCash, 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/chains/ECash/TWECashTests.cpp b/tests/chains/ECash/TWECashTests.cpp new file mode 100644 index 00000000000..bd9f5738b3b --- /dev/null +++ b/tests/chains/ECash/TWECashTests.cpp @@ -0,0 +1,169 @@ +// 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 +// file LICENSE at the root of the source code distribution tree. + +#include "Bitcoin/Address.h" +#include "Bitcoin/SigHashType.h" +#include "HexCoding.h" +#include "proto/Bitcoin.pb.h" +#include "TestUtilities.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace TW::Bitcoin { + +TEST(ECash, Address) { + EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); +} + +TEST(ECash, ValidAddress) { + auto string = STRING("ecash:qqra3amvnyyhrltyn5h97klwe68cuw3sfcgry9hl9k"); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeECash)); + ASSERT_NE(address.get(), nullptr); + + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(string.get(), TWCoinTypeECash)); + ASSERT_FALSE(TWBitcoinScriptSize(script.get()) == 0); +} + +TEST(ECash, InvalidAddress) { + // Wrong checksum + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeECash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("ecash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvffffffff").get(), TWCoinTypeECash)); + + // Valid BCH addresses are invalid for eCash + EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeECash)); + + EXPECT_TRUE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeECash)); + + // Wrong prefix + EXPECT_FALSE(TWAnyAddressIsValid(STRING("fcash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); + + // Wrong base 32 (characters o, i) + EXPECT_FALSE(TWAnyAddressIsValid(STRING("poi578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeECash)); +} + +TEST(ECash, LegacyToECashAddr) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), 0)); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); + + auto ecashAddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeECash)); + auto ecashAddressString = WRAPS(TWAnyAddressDescription(ecashAddress.get())); + assertStringsEqual(ecashAddressString, "ecash:qruxj7zq6yzpdx8dld0e9hfvt7u47zrw9gswqul42q"); +} + +TEST(ECash, LockScript) { + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("ecash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju564r6szft").get(), TWCoinTypeECash)); + auto data = WRAPD(TWAnyAddressData(address.get())); + auto rawData = WRAPD(TWDataCreateWithSize(0)); + TWDataAppendByte(rawData.get(), 0x00); + TWDataAppendData(rawData.get(), data.get()); + + auto legacyAddress = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(rawData.get())); + auto legacyString = WRAPS(TWBitcoinAddressDescription(legacyAddress.get())); + assertStringsEqual(legacyString, "1AwDXywmyhASpCCFWkqhySgZf8KiswFoGh"); + + auto keyHash = WRAPD(TWDataCreateWithBytes(legacyAddress.get()->impl.bytes.data() + 1, 20)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(keyHash.get())); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3gkypy0vjg").get(), TWCoinTypeECash)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "a914b9604b7820876bc510009b8247316c4b801aff8a87"); +} + +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())); + + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeECash, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeECash, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9xjBcTizebJaV61xMkuMJ89vis7saMmwFgTYeF83KwinEksJ4frk7wB4mDiKiwXDCbJmgmh6Bp1FkF8SopNZhbF3B5wyX32cuDVFZtuUDvB"); + assertStringsEqual(xpub, "xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7"); +} + +TEST(ECash, DeriveFromXPub) { + auto xpub = STRING("xpub6BiY1yFtUxrsha6RTnSMfG6fGtxMypVncuP9SdXetHFm7ZCScDAzfjVYcW32bkNCGJ5DTqawAHSTbJdTBL8wVxqUDGpxnRtukrhhBoS7Wy7"); + auto pubKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeECash, STRING("m/44'/899'/0'/0/2").get())); + auto pubKey9 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeECash, STRING("m/44'/899'/0'/0/9").get())); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2.get(), TWCoinTypeECash)); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + + auto address9 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey9.get(), TWCoinTypeECash)); + auto address9String = WRAPS(TWAnyAddressDescription(address9.get())); + + assertStringsEqual(address2String, "ecash:qpttymfhuq3v8tasfv7drlglhq6ne6zxquqltu3dcj"); + assertStringsEqual(address9String, "ecash:qqjraw2s5pwqwzql4znjpvp4vtvy3c9gmugq62r2j7"); +} + +TEST(ECash, SignTransaction) { + const int64_t amount = 600; + + // Transaction on eCash Mainnet + // 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); + input.set_to_address("ecash:qpmfhhledgp0jy66r5vmwjwmdfu0up7ujqpvm4v8rm"); + input.set_change_address("ecash:qz0q3xmg38sr94rw8wg45vujah7kzma3cs0tssg5fd"); + + auto hash0 = DATA("e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05"); + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + utxo0->mutable_out_point()->set_index(2); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(5151); + auto script0 = parse_hex("76a914aff1e0789e5fe316b729577665aa0a04d5b0f8c788ac"); + utxo0->set_script(script0.data(), script0.size()); + + auto utxoKey0 = DATA("7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384"); + input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); + + // Sign + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeECash); + + 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(), 226ul); + ASSERT_EQ(hex(output.encoded()), + "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..684aac0228e --- /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')), TWCurveSECP256k1); + 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')), TWCurveNIST256p1); + 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..63bee37c017 --- /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"), TWCurveSECP256k1); + 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..f521de95d1d --- /dev/null +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include +#include "proto/Ethereum.pb.h" +#include "proto/Barz.pb.h" +#include "HexCoding.h" +#include "uint256.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 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 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 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 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_scw_execute()->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 = STRING("0x3fC708630d85A3B5ec217E53100eC2b735d4f800"); + auto verificationFacet = STRING( "0x5034534Efe9902779eD6eA6983F435c00f3bc510"); + auto publicKey = PublicKey(parse_hex("0x04b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f"), TWPublicKeyTypeNIST256p1Extended); + auto salt = 0; + + const auto& initCodeData = WRAPD(TWBarzGetInitCode(factory.get(), WRAP(TWPublicKey, new TWPublicKey{ TW::PublicKey(publicKey) }).get(), verificationFacet.get(), 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(TWDataBytes(initCodeData.get()), TWDataSize(initCodeData.get())); + + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_scw_execute()->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_scw_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 auto barzAddress = STRING("0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"); + const auto chainId = 3604; + + const auto& prefixedMsgHash = TWBarzGetPrefixedMsgHash(WRAPD(TWDataCreateWithBytes(msgHash.data(), msgHash.size())).get(), barzAddress.get(), chainId); + const auto& prefixedMsgHashData = hexEncoded(*reinterpret_cast(WRAPD(prefixedMsgHash).get())); + ASSERT_EQ(prefixedMsgHashData, "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157"); + } +} + +TEST(Barz, GetPrefixedMsgHashWithZeroChainId) { + { + const Data& msgHash = parse_hex("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9"); + const std::string& barzAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto chainId = 0; + + const auto& prefixedMsgHash = TWBarzGetPrefixedMsgHash(WRAPD(TWDataCreateWithBytes(msgHash.data(), msgHash.size())).get(), WRAPS(TWStringCreateWithUTF8Bytes(barzAddress.c_str())).get(), chainId); + const auto& prefixedMsgHashData = hexEncoded(*reinterpret_cast(WRAPD(prefixedMsgHash).get())); + ASSERT_EQ(prefixedMsgHashData, "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b"); + } +} + +TEST(Barz, GetDiamondCutCode) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + + auto init_data = parse_hex("0x00"); + input.set_init_data(init_data.data(), init_data.size()); + + 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& inputData = input.SerializeAsString(); + const auto& inputTWData = WRAPD(TWDataCreateWithBytes((uint8_t*)inputData.data(), inputData.size())); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(inputTWData.get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithMultipleCut) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + + auto init_data = parse_hex("0x12341234"); + input.set_init_data(init_data.data(), init_data.size()); + + 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& serialized = input.SerializeAsString(); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(WRAPD(TWDataCreateWithBytes((const uint8_t *)serialized.data(), serialized.size())).get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithZeroSelector) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + auto init_data = parse_hex("0x00"); + input.set_init_data(init_data.data(), init_data.size()); + + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + const auto& serialized = input.SerializeAsString(); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(WRAPD(TWDataCreateWithBytes((const uint8_t *)serialized.data(), serialized.size())).get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithLongInitData) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + + auto init_data = parse_hex("0xb61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000"); + input.set_init_data(init_data.data(), init_data.size()); + + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + const auto& serialized = input.SerializeAsString(); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(WRAPD(TWDataCreateWithBytes((const uint8_t *)serialized.data(), serialized.size())).get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetAuthorizationHash) { + { + const auto chainId = store(uint256_t(1)); + const auto contractAddress = STRING("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + const auto nonce = store(uint256_t(1)); + + const auto& authorizationHash = TWBarzGetAuthorizationHash(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), contractAddress.get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get()); + const auto& authorizationHashData = hexEncoded(*reinterpret_cast(WRAPD(authorizationHash).get())); + ASSERT_EQ(authorizationHashData, "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9"); // Verified with viem + } +} + +TEST(Barz, SignAuthorization) { + { + const auto chainId = store(uint256_t(1)); + const auto contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto nonce = store(uint256_t(1)); + const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + + const auto signedAuthorization = WRAPS(TWBarzSignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); + auto json = nlohmann::json::parse(std::string(TWStringUTF8Bytes(signedAuthorization.get()))); + // Verified with viem + ASSERT_EQ(json["chainId"], hexEncoded(chainId)); + ASSERT_EQ(json["address"], contractAddress); + ASSERT_EQ(json["nonce"], hexEncoded(nonce)); + ASSERT_EQ(json["yParity"], hexEncoded(store(uint256_t(1)))); + ASSERT_EQ(json["r"], "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710"); + ASSERT_EQ(json["s"], "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c"); + } +} + +TEST(Barz, GetEncodedHash) { + { + const auto chainId = store(uint256_t(31337), 32); + const auto codeAddress = STRING("0x2e234DAe75C793f67A35089C9d99245E1C58470b"); + const auto codeName = STRING("Biz"); + const auto codeVersion = STRING("v1.0.0"); + const auto typeHash = STRING("0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"); + const auto domainSeparatorHash = STRING("0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"); + const auto sender = STRING("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"); + const auto userOpHash = STRING("0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"); + + const auto& encodedHash = TWBarzGetEncodedHash( + WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), + codeAddress.get(), + codeName.get(), + codeVersion.get(), + typeHash.get(), + domainSeparatorHash.get(), + sender.get(), + userOpHash.get()); + const auto& encodedHashData = hexEncoded(*reinterpret_cast(WRAPD(encodedHash).get())); + ASSERT_EQ(encodedHashData, "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); + } +} + +TEST(Barz, GetSignedHash) { + { + const auto hash = STRING("0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); + const auto privateKey = STRING("0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"); + const auto signedHash = TWBarzGetSignedHash(hash.get(), privateKey.get()); + const auto& signedHashData = hexEncoded(*reinterpret_cast(WRAPD(signedHash).get())); + ASSERT_EQ(signedHashData, "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); + } +} + +} 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/chains/Ethereum/Data/1inch.json b/tests/chains/Ethereum/Data/1inch.json new file mode 100644 index 00000000000..2bd96166e0f --- /dev/null +++ b/tests/chains/Ethereum/Data/1inch.json @@ -0,0 +1,65 @@ +{ + "7c025200": { + "inputs": [ + { + "name": "caller", + "type": "address" + }, + { + "components": [ + { + "name": "srcToken", + "type": "address" + }, + { + "name": "dstToken", + "type": "address" + }, + { + "name": "srcReceiver", + "type": "address" + }, + { + "name": "dstReceiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + }, + { + "name": "minReturnAmount", + "type": "uint256" + }, + { + "name": "flags", + "type": "uint256" + }, + { + "name": "permit", + "type": "bytes" + } + ], + "name": "desc", + "type": "tuple" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "name": "returnAmount", + "type": "uint256" + }, + { + "name": "gasLeft", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + } +} \ No newline at end of file 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/chains/Ethereum/Data/eip712_emptyArray.json b/tests/chains/Ethereum/Data/eip712_emptyArray.json new file mode 100644 index 00000000000..676a776d76c --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_emptyArray.json @@ -0,0 +1,108 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "address" + }, + { + "name": "version", + "type": "string" + } + ], + "Action": [ + { + "name": "action", + "type": "string" + }, + { + "name": "params", + "type": "string" + } + ], + "Cell": [ + { + "name": "capacity", + "type": "string" + }, + { + "name": "lock", + "type": "string" + }, + { + "name": "type", + "type": "string" + }, + { + "name": "data", + "type": "string" + }, + { + "name": "extraData", + "type": "string" + } + ], + "Transaction": [ + { + "name": "DAS_MESSAGE", + "type": "string" + }, + { + "name": "inputsCapacity", + "type": "string" + }, + { + "name": "outputsCapacity", + "type": "string" + }, + { + "name": "fee", + "type": "string" + }, + { + "name": "action", + "type": "Action" + }, + { + "name": "inputs", + "type": "Cell[]" + }, + { + "name": "outputs", + "type": "Cell[]" + }, + { + "name": "digest", + "type": "bytes32" + } + ] + }, + "primaryType": "Transaction", + "domain": { + "chainId": 1, + "name": "da.systems", + "verifyingContract": "0x0000000000000000000000000000000020210722", + "version": "1" + }, + "message": { + "DAS_MESSAGE": "TRANSFER FROM 0x54366bcd1e73baf55449377bd23123274803236e(906.74221046 CKB) TO ckt1qyqvsej8jggu4hmr45g4h8d9pfkpd0fayfksz44t9q(764.13228446 CKB), 0x54366bcd1e73baf55449377bd23123274803236e(142.609826 CKB)", + "inputsCapacity": "906.74221046 CKB", + "outputsCapacity": "906.74211046 CKB", + "fee": "0.0001 CKB", + "digest": "0x29cd28dbeb470adb17548563ceb4988953fec7b499e716c16381e5ae4b04021f", + "action": { + "action": "transfer", + "params": "0x00" + }, + "inputs": [], + "outputs": [] + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/eip712_emptyString.json b/tests/chains/Ethereum/Data/eip712_emptyString.json new file mode 100644 index 00000000000..0f507959149 --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_emptyString.json @@ -0,0 +1,132 @@ + +{ + "types": { + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "address" + }, + { + "name": "version", + "type": "string" + } + ], + "Action": [ + { + "name": "action", + "type": "string" + }, + { + "name": "params", + "type": "string" + } + ], + "Cell": [ + { + "name": "capacity", + "type": "string" + }, + { + "name": "lock", + "type": "string" + }, + { + "name": "type", + "type": "string" + }, + { + "name": "data", + "type": "string" + }, + { + "name": "extraData", + "type": "string" + } + ], + "Transaction": [ + { + "name": "DAS_MESSAGE", + "type": "string" + }, + { + "name": "inputsCapacity", + "type": "string" + }, + { + "name": "outputsCapacity", + "type": "string" + }, + { + "name": "fee", + "type": "string" + }, + { + "name": "action", + "type": "Action" + }, + { + "name": "inputs", + "type": "Cell[]" + }, + { + "name": "outputs", + "type": "Cell[]" + }, + { + "name": "digest", + "type": "bytes32" + } + ] + }, + "primaryType": "Transaction", + "domain": { + "chainId": "1", + "name": "did.id", + "verifyingContract": "0x0000000000000000000000000000000020210722", + "version": "1" + }, + "message": { + "DAS_MESSAGE": "SELL specer.bit FOR 100000 CKB", + "inputsCapacity": "1216.9999 CKB", + "outputsCapacity": "1216.9998 CKB", + "fee": "0.0001 CKB", + "digest": "0x53a6c0f19ec281604607f5d6817e442082ad1882bef0df64d84d3810dae561eb", + "action": { + "action": "start_account_sale", + "params": "0x00" + }, + "inputs": [ + { + "capacity": "218 CKB", + "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...", + "type": "account-cell-type,0x01,0x", + "data": "{ account: specer.bit, expired_at: 1670913958 }", + "extraData": "{ status: 0, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }" + } + ], + "outputs": [ + { + "capacity": "218 CKB", + "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...", + "type": "account-cell-type,0x01,0x", + "data": "{ account: specer.bit, expired_at: 1670913958 }", + "extraData": "{ status: 1, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }" + }, + { + "capacity": "201 CKB", + "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...", + "type": "account-sale-cell-type,0x01,0x", + "data": "0x1209460ef3cb5f1c68ed2c43a3e020eec2d9de6e...", + "extraData": "" + } + ] + } +} \ No newline at end of file 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/chains/Ethereum/Data/eip712_rarible.json b/tests/chains/Ethereum/Data/eip712_rarible.json new file mode 100644 index 00000000000..38ca61d28a9 --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_rarible.json @@ -0,0 +1,110 @@ +{ + "types": { + "EIP712Domain": [ + { + "type": "string", + "name": "name" + }, + { + "type": "string", + "name": "version" + }, + { + "type": "uint256", + "name": "chainId" + }, + { + "type": "address", + "name": "verifyingContract" + } + ], + "AssetType": [ + { + "name": "assetClass", + "type": "bytes4" + }, + { + "name": "data", + "type": "bytes" + } + ], + "Asset": [ + { + "name": "assetType", + "type": "AssetType" + }, + { + "name": "value", + "type": "uint256" + } + ], + "Order": [ + { + "name": "maker", + "type": "address" + }, + { + "name": "makeAsset", + "type": "Asset" + }, + { + "name": "taker", + "type": "address" + }, + { + "name": "takeAsset", + "type": "Asset" + }, + { + "name": "salt", + "type": "uint256" + }, + { + "name": "start", + "type": "uint256" + }, + { + "name": "end", + "type": "uint256" + }, + { + "name": "dataType", + "type": "bytes4" + }, + { + "name": "data", + "type": "bytes" + } + ] + }, + "domain": { + "chainId": 1, + "name": "Exchange", + "verifyingContract": "0x9757f2d2b135150bbeb65308d4a91804107cd8d6", + "version": "2" + }, + "primaryType": "Order", + "message": { + "maker": "0xc182a38ae564fd05b0261cf6eec416aef02fc3fe", + "makeAsset": { + "assetType": { + "assetClass": "0x73ad2146", + "data": "0x0000000000000000000000006a5ff3ceecae9ceb96e6ac6c76b82af8b39f0eb30000000000000000000000000000000000000000000000000000000000001398" + }, + "value": "1" + }, + "taker": "0x0000000000000000000000000000000000000000", + "takeAsset": { + "assetType": { + "assetClass": "0xaaaebeba", + "data": "0x" + }, + "value": "699000000000000000000" + }, + "salt": "108946545279938080742200552539328985411221282471150769260125599178294648928102", + "start": 0, + "end": 0, + "dataType": "0x4c234266", + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001cf0df2a5a20cd61d68d4489eebbf85b8d39e18a00000000000000000000000000000000000000000000000000000000000000fa" + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/eip712_snapshot_v4.json b/tests/chains/Ethereum/Data/eip712_snapshot_v4.json new file mode 100644 index 00000000000..0f10d174ee9 --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_snapshot_v4.json @@ -0,0 +1,56 @@ +{ + "types": { + "Vote": [ + { + "name": "from", + "type": "address" + }, + { + "name": "space", + "type": "string" + }, + { + "name": "timestamp", + "type": "uint64" + }, + { + "name": "proposal", + "type": "string" + }, + { + "name": "choice", + "type": "uint32[]" + }, + { + "name": "metadata", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ] + }, + "domain": { + "name": "snapshot", + "version": "0.1.4" + }, + "primaryType": "Vote", + "message": { + "from": "0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f", + "space": "fabien.eth", + "timestamp": "1626136951", + "proposal": "QmNueeqwrnFPpiQkv8pFeSe8JPKJcdXXvMsHu5ecjM28j3", + "choice": [ + "1", + "3" + ], + "metadata": "{}" + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/eip712_walletconnect.json b/tests/chains/Ethereum/Data/eip712_walletconnect.json new file mode 100644 index 00000000000..768e5d620bf --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_walletconnect.json @@ -0,0 +1,95 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "RelayRequest": [ + { + "name": "target", + "type": "address" + }, + { + "name": "encodedFunction", + "type": "bytes" + }, + { + "name": "gasData", + "type": "GasData" + }, + { + "name": "relayData", + "type": "RelayData" + } + ], + "GasData": [ + { + "name": "gasLimit", + "type": "uint256" + }, + { + "name": "gasPrice", + "type": "uint256" + }, + { + "name": "pctRelayFee", + "type": "uint256" + }, + { + "name": "baseRelayFee", + "type": "uint256" + } + ], + "RelayData": [ + { + "name": "senderAddress", + "type": "address" + }, + { + "name": "senderNonce", + "type": "uint256" + }, + { + "name": "relayWorker", + "type": "address" + }, + { + "name": "paymaster", + "type": "address" + } + ] + }, + "domain": { + "name": "GSN Relayed Transaction", + "version": "1", + "chainId": 42, + "verifyingContract": "0x6453D37248Ab2C16eBd1A8f782a2CBC65860E60B" + }, + "primaryType": "RelayRequest", + "message": { + "target": "0x9cf40ef3d1622efe270fe6fe720585b4be4eeeff", + "encodedFunction": "0xa9059cbb0000000000000000000000002e0d94754b348d208d64d52d78bcd443afa9fa520000000000000000000000000000000000000000000000000000000000000007", + "gasData": { + "gasLimit": "39507", + "gasPrice": "1700000000", + "pctRelayFee": "70", + "baseRelayFee": "0" + }, + "relayData": { + "senderAddress": "0x22d491bde2303f2f43325b2108d26f1eaba1e32b", + "senderNonce": "3", + "relayWorker": "0x3baee457ad824c94bd3953183d725847d023a2cf", + "paymaster": "0x957F270d45e9Ceca5c5af2b49f1b5dC1Abb0421c" + } + } +} \ No newline at end of file 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/chains/Ethereum/Data/eth_feeHistory.json b/tests/chains/Ethereum/Data/eth_feeHistory.json new file mode 100644 index 00000000000..66b46227b71 --- /dev/null +++ b/tests/chains/Ethereum/Data/eth_feeHistory.json @@ -0,0 +1,39 @@ +{ + "oldestBlock": "0xc85c97", + "baseFeePerGas": [ + "0x101d68d563", + "0xe39853832", + "0xde93efcda", + "0xd452f480f", + "0xc1fbe805a", + "0xda370a6b0", + "0xdd7c2412c", + "0xc5ff00ab1", + "0xdebddcf28", + "0xd6966ade5", + "0xf16850e31", + "0x10f8cc9b24", + "0x1317cea5e4", + "0x10b4d4d128", + "0x11fa926967", + "0x13ced7648b", + "0x123fe58db3", + "0x12b6493b32", + "0x12867c9e32", + "0x1256353caf", + "0x1121ffbe15", + "0xfd580ff9d", + "0xddad0dfaa", + "0xf960de5a9", + "0x10dda4cef8", + "0x102f945b80" + ], + "gasUsedRatio": [ + 0.030820766666666666, 0.4118232666666667, 0.3157259, 0.15448973333333332, + 0.9996493, 0.5599394666666667, 0.07579376666666666, 0.9999198782388997, + 0.35356874178311704, 0.9999342, 0.9994454666666667, 0.9999153666666667, 0, + 0.8046559956729882, 0.9069632937415121, 0.18530296666666668, 0.601363, + 0.4600865, 0.45927973333333333, 0.23737084070738257, 0.19676682418443775, 0, + 0.9998718666666667, 0.8284060281801487, 0.33874143333333334 + ] +} diff --git a/tests/chains/Ethereum/Data/eth_feeHistory2.json b/tests/chains/Ethereum/Data/eth_feeHistory2.json new file mode 100644 index 00000000000..e4f0484b7a3 --- /dev/null +++ b/tests/chains/Ethereum/Data/eth_feeHistory2.json @@ -0,0 +1,102 @@ +{ + "oldestBlock": "0xc9fbef", + "reward": [ + ["0x59682f00"], + ["0x3b9aca00"], + ["0x59682f00"], + ["0x0"], + ["0x6aa391bd"], + ["0x59682f00"], + ["0x59682f00"], + ["0x59682f00"], + ["0x10df711a"], + ["0x59682f00"], + ["0x77359400"], + ["0x59682f00"], + ["0x59682f00"], + ["0x540ae480"], + ["0x59682f00"], + ["0x13665043c"], + ["0x47f906c7"], + ["0x43590d65"], + ["0x59682f00"], + ["0x77359400"], + ["0x59682f00"], + ["0x59682f00"], + ["0x540ae480"], + ["0x59682f00"], + ["0x59682f00"], + ["0x540ae480"], + ["0x224c2cd41"], + ["0x4a7bbda8"], + ["0x3b9aca00"], + ["0x59682f00"], + ["0x0"], + ["0x59682f00"], + ["0x47ef002a"], + ["0x53649756"], + ["0x439e19c1"], + ["0x544f3880"], + ["0x59682f00"], + ["0x439e19c1"], + ["0x77359400"], + ["0x59682f00"] + ], + "baseFeePerGas": [ + "0x12258b8d60", + "0x13152c82d1", + "0x113cce1190", + "0x136405db18", + "0x1129830143", + "0x134e13a14f", + "0x136e77322a", + "0x15cdc9710f", + "0x14e38b92e6", + "0x13e59d97f5", + "0x1263016576", + "0x14aed946ad", + "0x157a3f4c71", + "0x1324cdb952", + "0x11855e127d", + "0x12cf9ad7c4", + "0x107b8fceec", + "0x10aa950ccc", + "0x116d8c2f3e", + "0xf8fb6db80", + "0x11814b5a16", + "0x132271f3b1", + "0x1158997faa", + "0x10530fbb42", + "0xf0b6a1699", + "0xf42330c88", + "0xf8d312abf", + "0xdadcb9a58", + "0xf59022c42", + "0xdf2763933", + "0xfa20d639d", + "0xe31433ab8", + "0xcc1ed2fd6", + "0xd996f5dbb", + "0xf1c05f7e3", + "0xecdfe1180", + "0xe7399a9ff", + "0xf1733b435", + "0xf33594177", + "0x110aa9e2b9", + "0x12ed797e24" + ], + "gasUsedRatio": [ + 0.7063299021838508, 0.11322243333333333, 0.9996531, 0.040283233333333335, + 0.9994318666666666, 0.5262148333333333, 0.9883509666666667, + 0.3321385666896276, 0.31005893333333334, 0.19639628569368678, + 0.9995475666666667, 0.6536576666666667, 0.06535701221090938, + 0.16092406666666667, 0.7945019666666666, 0.004906966666666667, 0.5445738, + 0.6827836284534818, 0.0715986, 0.9996142, 0.8723484482597256, + 0.12612991772125653, 0.26441184861422024, 0.18639173739048515, + 0.5568985799772874, 0.5767934666666666, 0.0183385, 0.9879933, + 0.13497489701306675, 0.9835122333333334, 0.13139943333333334, + 0.09558540581544545, 0.7639477796845944, 0.9441711647507081, + 0.41930673333333335, 0.4045990666666667, 0.6768858333333333, + 0.5291433581774064, 0.9844746666666667, 0.9426754 + ] +} diff --git a/tests/chains/Ethereum/Data/eth_feeHistory3.json b/tests/chains/Ethereum/Data/eth_feeHistory3.json new file mode 100644 index 00000000000..096c0841ee6 --- /dev/null +++ b/tests/chains/Ethereum/Data/eth_feeHistory3.json @@ -0,0 +1,68 @@ +{ + "oldestBlock": "0xcbbcb8", + "reward": [ + ["0x59682f00"], + ["0x0"], + ["0x59682f00"], + ["0x3b9aca00"], + ["0x3b9aca00"], + ["0x59682f00"], + ["0x59682f00"], + ["0x59682f00"], + ["0x3b9aca00"], + ["0x0"], + ["0x59682f00"], + ["0x59682f00"], + ["0x59682f00"], + ["0x540ae480"], + ["0x59682f00"], + ["0x59682f00"], + ["0x47868c00"], + ["0x59682f00"], + ["0x0"], + ["0x59682f00"], + ["0x59682f00"], + ["0x3b9aca00"], + ["0x3b9aca00"], + ["0x59682f00"], + ["0x59682f00"] + ], + "baseFeePerGas": [ + "0x8a30e9697", + "0x8ffdfc00b", + "0x824485464", + "0x879cc1e36", + "0x7d2d7e7ea", + "0x81848011d", + "0x78e82100d", + "0x88023196b", + "0x990123e7f", + "0xabe11ffe7", + "0xb3506d36b", + "0x9f347afa7", + "0x9718e2235", + "0x8abd52afd", + "0x99b004eab", + "0xac0b3b0cc", + "0xa1db63728", + "0x9b7b6a50a", + "0xa7f430228", + "0x951c8e234", + "0x92b24de36", + "0x94a3843c4", + "0x940e2b551", + "0x8f20aa2c1", + "0x9a8598570", + "0x8894a15ac" + ], + "gasUsedRatio": [ + 0.6679143313670418, 0.11874393063907729, 0.6641136176088029, + 0.19222202603732763, 0.6386785897722058, 0.23406464498230267, + 0.9996185945229787, 0.9998474750970308, 0.9934480934047584, + 0.6730229755919641, 0.05142049589305907, 0.2962927991741801, + 0.17285541195516949, 0.9309667383577928, 0.9777560841290119, + 0.26315613047433595, 0.3424561000622894, 0.8208541215524073, + 0.051250190010382016, 0.43521513333333334, 0.5529592686075381, + 0.48430050149048476, 0.3668673, 0.8184364, 0.03556683333333333 + ] +} diff --git a/tests/chains/Ethereum/Data/eth_feeHistory4.json b/tests/chains/Ethereum/Data/eth_feeHistory4.json new file mode 100644 index 00000000000..bb17e75c38d --- /dev/null +++ b/tests/chains/Ethereum/Data/eth_feeHistory4.json @@ -0,0 +1,103 @@ +{ + "oldestBlock": "0xccdaa1", + "reward": [ + ["0x59682f00"], + ["0x59682f00"], + ["0x3b9aca00"], + ["0x59682f00"], + ["0x59682f00"], + ["0x540ae480"], + ["0x0"], + ["0x59682f00"], + ["0x59682f00"], + ["0x3b9aca00"], + ["0x59682f00"], + ["0x59682f00"], + ["0x77359400"], + ["0x77359400"], + ["0x2c5179e9216"], + ["0x2ef6295a566"], + ["0x2e93f154cb4"], + ["0x2c1cdd41c52"], + ["0x300725154e4"], + ["0x2d587b2814d"], + ["0x31beacff01e"], + ["0x36be9c29918"], + ["0x3366e0dd5f9"], + ["0x33e50ddf3ed"], + ["0x354780036b7"], + ["0x3389d2f94a5"], + ["0x33f88076bcf"], + ["0x361ae5e0e3a"], + ["0x3253d31b655"], + ["0x3136779dffc"], + ["0x2cf0d6b4b78"], + ["0x174876e8000"], + ["0x8618d1c3b2"], + ["0x5783d7127"], + ["0x3b9aca00"], + ["0x3b9aca00"], + ["0x77359400"], + ["0x73a20d00"], + ["0x9502f900"], + ["0x908a9040"] + ], + "baseFeePerGas": [ + "0x16bbb5f95f", + "0x1992cd3f1f", + "0x1cc4693590", + "0x1ad26d7524", + "0x1b54f2999e", + "0x18ea37e696", + "0x178d2180bc", + "0x156801b8b8", + "0x12c13f4733", + "0x14cb8eb2d6", + "0x13e7072005", + "0x11c49ac4e4", + "0x13406bb849", + "0x159f44aae0", + "0x185302f9ea", + "0x1b5cb6acc9", + "0x1ec732a54c", + "0x2298f11bae", + "0x26ebbb8fc0", + "0x2bc8ef4392", + "0x31420c5be2", + "0x3768d1a6e8", + "0x3e53989a07", + "0x461dec1dde", + "0x4eda940949", + "0x58b42a8cb0", + "0x63ca8cd431", + "0x70352401c6", + "0x7e3944f52a", + "0x8e001dd1cd", + "0x9fbaa5f2b9", + "0xb3ace82ea4", + "0xca1c49a44e", + "0xe35c679ed9", + "0xffc4308ac5", + "0x103e7df4e08", + "0xe6750b7040", + "0xd73386fc1f", + "0xc6dae88f29", + "0xbe8ab66221", + "0xa96bb7c4f0" + ], + "gasUsedRatio": [ + 0.9997436666666667, 0.9995472666666667, 0.2295189414203697, + 0.5760337666666666, 0.14628616666666666, 0.28107523333333334, + 0.13568603333333334, 0.0045556847072167365, 0.9351454666666666, + 0.32828806666666666, 0.0710145, 0.8340068, 0.9925288955335115, + 0.9998806666666666, 0.9995666897940827, 0.9993689996203751, + 0.9963672771755393, 0.9998523666666667, 0.9998937666666666, + 0.9999988666666667, 0.9995288333333333, 0.999344273650114, + 0.9999690604548399, 0.9984213067345702, 0.9996563378388673, + 0.9999758568624304, 0.9976939, 0.9996499085309861, 0.9999613999626866, + 0.9993967, 0.9995037845543826, 0.9994579591297924, 0.9997356911754021, + 0.9997412054258034, 0.5647369595171532, 0.046781166666666665, + 0.2352058881169205, 0.19616969096284828, 0.33277743118995984, + 0.05661030673760812 + ] +} 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/chains/Ethereum/Data/tuple_nested.json b/tests/chains/Ethereum/Data/tuple_nested.json new file mode 100644 index 00000000000..c4e62ac28d3 --- /dev/null +++ b/tests/chains/Ethereum/Data/tuple_nested.json @@ -0,0 +1,46 @@ +{ + "74b6ef0b": { + "inputs": [ + { + "name": "param1", + "type": "uint16" + }, + { + "name": "param2", + "type": "tuple", + "components": [ + { + "name": "param21", + "type": "uint16" + }, + { + "name": "param22", + "type": "tuple", + "components": [ + { + "name": "param221", + "type": "uint16" + }, + { + "name": "param222", + "type": "uint64" + } + ] + }, + { + "name": "param23", + "type": "uint32" + } + ] + }, + { + "name": "param3", + "type": "bool" + } + ], + "name": "nested_tuple", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +} \ No newline at end of file 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..1c8f7d1ab0a --- /dev/null +++ b/tests/chains/Ethereum/EIP1014Tests.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include +#include + +#include + +namespace TW::Ethereum::tests { + TEST(EthereumEip1014, Example0) { + { + const auto from = STRING("0x0000000000000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"); + } + } + + TEST(EthereumEip1014, Example1) { + { + const auto from = STRING("0xdeadbeef00000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3"); + } + } + + TEST(EthereumEip1014, Example2) { + const auto from = STRING("0xdeadbeef00000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x000000000000000000000000feed000000000000000000000000000000000000")).get())); + Data initCodeData = parse_hex("0x00"); + initCodeData.resize(32); + const auto initCode = WRAPD(TWDataCreateWithBytes(initCodeData.data(), initCodeData.size())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCode.get())); + assertStringsEqual(address, "0x2DB27D1d6BE32C9abfA484BA3d591101881D4B9f"); + } + + TEST(EthereumEip1014, Example3) { + const auto from = STRING("0x0000000000000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + Data initCodeData = parse_hex("0xdeadbeef"); + initCodeData.resize(32); + const auto initCode = WRAPD(TWDataCreateWithBytes(initCodeData.data(), initCodeData.size())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCode.get())); + assertStringsEqual(address, "0x219438aC82230Cb9A9C13Cd99D324fA1d66CF018"); + } + + TEST(EthereumEip1014, Example4) { + const auto from = STRING("0x00000000000000000000000000000000deadbeef"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00000000000000000000000000000000000000000000000000000000cafebabe")).get())); + Data initCodeData = parse_hex("0xdeadbeef"); + const auto initCode = WRAPD(TWDataCreateWithBytes(initCodeData.data(), initCodeData.size())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7"); + } + + TEST(EthereumEip1014, Example5) { + const auto from = STRING("0x00000000000000000000000000000000deadbeef"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00000000000000000000000000000000000000000000000000000000cafebabe")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C"); + } + + TEST(EthereumEip1014, Example6) { + const auto from = STRING("0x0000000000000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0"); + } + + TEST(EthereumEip1014, Example7) { + const auto from = STRING("0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000d9ec9e840bb5df076dbbb488d01485058f421e5800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000be8fa0112dcb7d21dc63645b633073651e19934800000000000000000000000000000000000000000000000000000000")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x4455e5f0038795939c001aa4d296A45956C460AA"); + } +} diff --git a/tests/chains/Ethereum/EIP1967Tests.cpp b/tests/chains/Ethereum/EIP1967Tests.cpp new file mode 100644 index 00000000000..6790afaa995 --- /dev/null +++ b/tests/chains/Ethereum/EIP1967Tests.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#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 auto twData = WRAPD(TWDataCreateWithBytes(data.data(), data.size())); + const auto initCodeData = TWEthereumEip1967ProxyInitCode(STRING(login_address.c_str()).get(), twData.get()); + const auto& initCode = hexEncoded(*reinterpret_cast(WRAPD(initCodeData).get())); + ASSERT_EQ(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..9bbe53e3df9 --- /dev/null +++ b/tests/chains/Ethereum/TWEthereumAbiTests.cpp @@ -0,0 +1,357 @@ +// 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" + ); +} + +TEST(TWEthereumAbi, GetFunctionSignature) { + const auto abiJson = R"|({"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"})|"; + const auto abiJsonStr = WRAPS(TWStringCreateWithUTF8Bytes(abiJson)); + + const auto result = WRAPS(TWEthereumAbiGetFunctionSignature(abiJsonStr.get())); + const auto expected = WRAPS(TWStringCreateWithUTF8Bytes("transfer(address,uint256)")); + EXPECT_TRUE(TWStringEqual(result.get(), expected.get())); +} + +} // namespace TW::Ethereum diff --git a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp similarity index 91% rename from tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp rename to tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp index 8cedffa7c28..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; @@ -22,7 +20,7 @@ TEST(TWEthereumAbiValue, decodeUInt256) { "0000000000000000000000000000000000000000000000000000091d0eb3e2af0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000091d0eb3e2af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", }; - for (auto &input: inputs) { + for (auto& input: inputs) { auto data = WRAPD(TWDataCreateWithHexString(STRING(input.c_str()).get())); auto result = WRAPS(TWEthereumAbiValueDecodeUInt256(data.get())); assertStringsEqual(result, expected); @@ -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/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp new file mode 100644 index 00000000000..128edf79144 --- /dev/null +++ b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Script.h" +#include "Groestlcoin/Signer.h" +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "proto/Bitcoin.pb.h" +#include "TestUtilities.h" +#include "../Bitcoin/TxComparisonHelper.h" +#include +#include +#include +#include + +#include + +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); + input.set_to_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); + input.set_change_address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + + auto utxoKey0 = parse_hex("dc334e7347f2f9f72fce789b11832bdf78adf0158bc6617e6d2d2a530a0d4bc6"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Script(parse_hex("00147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce")); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(4774); + auto hash0 = parse_hex("9568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); + } + + // 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(2048); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeGroestlcoin); + + // https://blockbook.groestlcoin.org/tx/40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64 + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), 2500); + EXPECT_EQ(output.transaction().outputs(1).value(), 2048); + ASSERT_EQ(hex(output.encoded()), "010000000001019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f0100000000ffffffff02c40900000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700080000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88ac024830450221009bbd0228dcb7343828633ded99d216555d587b74db40c4a46f560187eca222dd022032364cf6dbf9c0213076beb6b4a20935d4e9c827a551c3f6f8cbb22d8b464467012102e9c9b9b76e982ad8fa9a7f48470eafbeeba9bf6d287579318c517db5157d936e00000000"); +} + +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); + 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 = 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); + + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + 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); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeGroestlcoin); + + // https://blockbook.groestlcoin.org/tx/74a0dd12bc178cfcc1e0982a2a5b2c01a50e41abbb63beb031bcd21b3e28eac0 + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), 2500); + EXPECT_EQ(output.transaction().outputs(1).value(), 2274); + 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); + input.set_to_address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + input.set_change_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + + // TX input + auto utxoKey0 = PrivateKey(parse_hex("302fc195a8fc96c5a581471e67e4c1ac2efda252f76ad5c77a53764c70d58f91")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + 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), "0055b0c94df477ee6b9f75185dfc9aa8ce2e52e4"); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Script(parse_hex("a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e487")); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(10'000); + auto hash0 = DATA("fdae0772d7d1d33804a6b1ca0e391668b116bb7a70028427d3d82232189ce863"); // UTXO hash backwards + 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); + + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {10'000}, 5000, 167)); + } + + // 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(4774); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeGroestlcoin); + + // https://blockbook.groestlcoin.org/tx/8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895 + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), 5000); + EXPECT_EQ(output.transaction().outputs(1).value(), 4774); + ASSERT_EQ(hex(output.encoded()), "01000000000101fdae0772d7d1d33804a6b1ca0e391668b116bb7a70028427d3d82232189ce86300000000171600142fc7d70acef142d1f7b5ef2f20b1a9b759797674ffffffff0288130000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88aca6120000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce024730440220614df57babf74029afaa6dda202afa47d3555cca7a0f20a22e466aeb7029e7d002207974b4c16f346811aff6720d09b9c58d0c4e01e8d258c3d203cc3c1ad228c61a012102fb6ad115761ea928f1367befb2bee79c0b3497314b45e0b734cd150f0601706c00000000"); +} + +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); + input.set_to_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); + input.set_change_address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + + auto utxoKey0 = parse_hex("dc334e7347f2f9f72fce789b11832bdf78adf0158bc6617e6d2d2a530a0d4bc6"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Script(parse_hex("00147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce")); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(4774); + auto hash0 = parse_hex("9568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + + EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); + EXPECT_EQ(plan.branch_id(), ""); +} + +// Tests the BitcoinV2 API through the legacy `SigningInput`. +// Successfully broadcasted: https://blockbook.groestlcoin.org/tx/40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64 +TEST(GroestlcoinSigning, SignV2P2WPKH) { + auto privateKey = parse_hex("dc334e7347f2f9f72fce789b11832bdf78adf0158bc6617e6d2d2a530a0d4bc6"); + auto txId = parse_hex("8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895"); + std::reverse(txId.begin(), txId.end()); + int64_t inAmount = 4774; + int64_t outAmount = 2500; + int64_t changeAmount = 2048; + auto senderAddress = "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"; + auto toAddress = "31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"; + auto changeAddress = "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"; + + BitcoinV2::Proto::SigningInput signing; + signing.add_private_keys(privateKey.data(), privateKey.size()); + + auto& chainInfo = *signing.mutable_chain_info(); + chainInfo.set_p2pkh_prefix(36); + chainInfo.set_p2sh_prefix(5); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::UseDefault); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); + + auto& in = *builder.add_inputs(); + auto& inOutPoint = *in.mutable_out_point(); + inOutPoint.set_hash(txId.data(), txId.size()); + inOutPoint.set_vout(1); + in.set_value(inAmount); + in.set_receiver_address(senderAddress); + in.set_sighash_type(TWBitcoinSigHashTypeAll); + + auto& out = *builder.add_outputs(); + out.set_value(outAmount); + out.set_to_address(toAddress); + + auto& changeOut = *builder.add_outputs(); + changeOut.set_value(changeAmount); + changeOut.set_to_address(changeAddress); + + Bitcoin::Proto::SigningInput legacy; + legacy.set_coin_type(TWCoinTypeGroestlcoin); + *legacy.mutable_signing_v2() = signing; + + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(legacy, plan, TWCoinTypeGroestlcoin); + EXPECT_EQ(plan.error(), Common::Proto::OK); + ASSERT_TRUE(plan.has_planning_result_v2()); + EXPECT_EQ(plan.planning_result_v2().error(), Common::Proto::SigningError::OK) + << plan.planning_result_v2().error_message(); + EXPECT_EQ(plan.planning_result_v2().vsize_estimate(), 145ul); + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeGroestlcoin); + + 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()), "010000000001019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f0100000000ffffffff02c40900000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700080000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88ac024830450221009bbd0228dcb7343828633ded99d216555d587b74db40c4a46f560187eca222dd022032364cf6dbf9c0213076beb6b4a20935d4e9c827a551c3f6f8cbb22d8b464467012102e9c9b9b76e982ad8fa9a7f48470eafbeeba9bf6d287579318c517db5157d936e00000000"); + EXPECT_EQ(hex(output.signing_result_v2().txid()), "40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64"); +} + +} // 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..55e522a11a5 --- /dev/null +++ b/tests/chains/ImmutableX/StarkKeyTests.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "ImmutableX/Constants.h" +#include "ImmutableX/StarkKey.h" +#include +#include +#include "TestUtilities.h" + +namespace TW::ImmutableX::tests { + +TEST(ImmutableX, PathFromAddress) { + // https://github.com/immutable/imx-core-sdk-swift/blob/main/Tests/ImmutableXCoreTests/Crypto/Stark/StarkKeyTests.swift#L30 + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING("0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f").get(), STRING(internal::gLayer).get(), STRING(internal::gApplication).get(), STRING(internal::gIndex).get())); + assertStringsEqual(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); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(address.c_str()).get(), STRING(internal::gLayer).get(), STRING(internal::gApplication).get(), STRING(internal::gIndex).get())); + auto path = DerivationPath(TWStringUTF8Bytes(res.get())); + 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"; + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(address.c_str()).get(), STRING(internal::gLayer).get(), STRING(internal::gApplication).get(), STRING(internal::gIndex).get())); + auto path = DerivationPath(TWStringUTF8Bytes(res.get())); + 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..b0f180f85a7 --- /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, "KAIA"); + 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..df40c2253f8 --- /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), TWBlockchainKomodo); + 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/TWAnyAddressTests.cpp b/tests/chains/Kusama/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..426c15989de --- /dev/null +++ b/tests/chains/Kusama/TWAnyAddressTests.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +namespace TW::Kusama::tests { + +TEST(KusamaAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ").get(), TWCoinTypeKusama)); + // Polkadot ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony").get(), TWCoinTypeKusama)); + // Polkadot sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony").get(), TWCoinTypeKusama)); + // Bitcoin + ASSERT_FALSE(TWAnyAddressIsValid(STRING("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA").get(), TWCoinTypeKusama)); + + // Kusama ed25519 + ASSERT_TRUE(TWAnyAddressIsValid(STRING("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ").get(), TWCoinTypeKusama)); + // Kusama secp256k1 + ASSERT_TRUE(TWAnyAddressIsValid(STRING("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx").get(), TWCoinTypeKusama)); + // Kusama sr25519 + ASSERT_TRUE(TWAnyAddressIsValid(STRING("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe").get(), TWCoinTypeKusama)); +} + +TEST(KusamaAddress, FromPrivateKey) { + // from subkey: tiny escape drive pupil flavor endless love walk gadget match filter luxury + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0xa21981f3bb990c40837df44df639541ff57c5e600f9eb4ac00ed8d1f718364e5").get())); + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeKusama)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeKusama)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM").get())); +} + +TEST(KusamaAddress, FromPublicKey) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0x032eb287017c5cde2940b5dd062d413f9d09f8aa44723fc80bf46b96c81ac23d").get(), TWPublicKeyTypeED25519)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeKusama)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM").get())); +} + +TEST(KusamaAddress, FromString) { + auto addressStr1 = STRING("CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(addressStr1.get(), TWCoinTypeKusama)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressStr1.get())); +} + +} // namespace TW::Kusama::tests diff --git a/tests/chains/Kusama/TWAnySignerTests.cpp b/tests/chains/Kusama/TWAnySignerTests.cpp new file mode 100644 index 00000000000..988e99cc0e4 --- /dev/null +++ b/tests/chains/Kusama/TWAnySignerTests.cpp @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { +extern uint32_t kusamaPrefix; +extern PrivateKey privateKey; +extern TWPublicKey* publicKey; +auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); + +extern Data helper_encodePayload(TWCoinType coin, const Proto::SigningInput& input); + +TEST(TWAnySignerKusama, SignTransferKSM) { + auto blockHash = parse_hex("4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + + 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(kusamaPrefix); + 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("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); + transfer.set_value(value.data(), value.size()); + + auto preimage = helper_encodePayload(TWCoinTypeKusama, input); + ASSERT_EQ(hex(preimage), "04008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0000000e307000002000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeKusama); + ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); +} + +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/chains/Litecoin/LitecoinAddressTests.cpp b/tests/chains/Litecoin/LitecoinAddressTests.cpp new file mode 100644 index 00000000000..19bee636bb4 --- /dev/null +++ b/tests/chains/Litecoin/LitecoinAddressTests.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Address.h" +#include "Bitcoin/SegwitAddress.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include +#include + +#include +#include + +using namespace TW; + +namespace TW::Bitcoin::tests { + +TEST(LitecoinAddress, deriveAddress_legacy) { + const auto pubKey = PublicKey(parse_hex("03b49081a4d7ad24b20e209bc6fe10491aadb5607777baf0509a036cce96025db0"), TWPublicKeyTypeSECP256k1); + auto addrStr = deriveAddress(TWCoinTypeLitecoin, pubKey, TWDerivationLitecoinLegacy); + EXPECT_EQ(addrStr, "LW6HjAU6GL9fK2LZWUA6VZCzomTdrpx3nr"); + + const auto address = Address(pubKey, TWCoinTypeP2pkhPrefix(TWCoinTypeLitecoin)); + EXPECT_EQ(address.string(), addrStr); +} + +TEST(LitecoinAddress, deriveAddress_segwit) { + const auto pubKey = PublicKey(parse_hex("030fc2fdd1a0b5d43b31227a4b1cd57e7d35a6edc93fb12f9315e67762abeb8be0"), TWPublicKeyTypeSECP256k1); + auto addrStr = deriveAddress(TWCoinTypeLitecoin, pubKey, TWDerivationBitcoinSegwit); + EXPECT_EQ(addrStr, "ltc1q3m3ujh350qrqdl33pv7pjw0d0m9qnm6qjcjpga"); + + 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 81% rename from tests/Monacoin/TWMonacoinTransactionTests.cpp rename to tests/chains/Monacoin/TWMonacoinTransactionTests.cpp index 8b989be0245..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) { /* @@ -59,7 +55,7 @@ TEST(MonacoinTransaction, SignTransaction) { plan.set_fee(fee); plan.set_change(utxo_amount - amount - fee); - auto &protoPlan = *input.mutable_plan(); + auto& protoPlan = *input.mutable_plan(); protoPlan = plan; Proto::SigningOutput output; @@ -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..c419f3482b7 --- /dev/null +++ b/tests/chains/MultiversX/SerializationTests.cpp @@ -0,0 +1,140 @@ +// 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); +} + +TEST(MultiversXSerialization, SerializeTransactionWithRelayerAddress) { + Transaction transaction; + transaction.nonce = 42; + transaction.value = "1000000000000000000"; + transaction.sender = ALICE_BECH32; + transaction.receiver = BOB_BECH32; + transaction.relayer = CAROL_BECH32; + 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"("gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,)" + R"("relayer":"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..96401c1e104 --- /dev/null +++ b/tests/chains/MultiversX/SignerTests.cpp @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#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" +#include "boost/format.hpp" + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(MultiversXSigner, SignGenericAction) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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(MultiversXSigner, SignGenericActionWithRelayer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); + 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_relayer(CAROL_BECH32); + input.mutable_generic_action()->set_value("1000000000000000000"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(2); + 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 = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":100000, + "gasPrice":1000000000, + "relayer":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "nonce":42, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003", + "value":"1000000000000000000", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignEGLDTransferWithRelayer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); + 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_relayer(CAROL_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "c86491a51d553889df9fb7ff75880843e2b21aec97ae3e4004b70801a5494a8958af8daf56906f9720b0af6a25ad2ab82b3af05940fb6dfe0dea529f1bf8d90f"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":100000, + "gasPrice":1000000000, + "relayer":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"c86491a51d553889df9fb7ff75880843e2b21aec97ae3e4004b70801a5494a8958af8daf56906f9720b0af6a25ad2ab82b3af05940fb6dfe0dea529f1bf8d90f", + "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), TWCurveED25519); + 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..359b8622407 --- /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), TWCurveED25519); + 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..9df91a44432 --- /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), TWCurveED25519); + 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), TWCurveED25519); + 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), TWCurveED25519); + 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..466e3565924 --- /dev/null +++ b/tests/chains/MultiversX/TransactionFactoryTests.cpp @@ -0,0 +1,297 @@ +// 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); +} + +TEST(MultiversXTransactionFactory, createWithRelayer) { + Proto::SigningInput signingInputWithGenericAction; + signingInputWithGenericAction.set_gas_limit(107500); + signingInputWithGenericAction.mutable_generic_action()->set_data("hello"); + signingInputWithGenericAction.mutable_generic_action()->mutable_accounts()->set_relayer(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_relayer(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_relayer(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_relayer(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::Default, tx1.options); + ASSERT_EQ(TransactionOptions::Default, tx2.options); + ASSERT_EQ(TransactionOptions::Default, tx3.options); + ASSERT_EQ(TransactionOptions::Default, tx4.options); + + ASSERT_EQ(CAROL_BECH32, tx1.relayer); + ASSERT_EQ(CAROL_BECH32, tx2.relayer); + ASSERT_EQ(CAROL_BECH32, tx3.relayer); + ASSERT_EQ(CAROL_BECH32, tx4.relayer); + + ASSERT_EQ(107500ul, tx1.gasLimit); + ASSERT_EQ(100000ul, tx2.gasLimit); + ASSERT_EQ(475000ul, tx3.gasLimit); + ASSERT_EQ(987500ul, tx4.gasLimit); +} + +} // 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..d9a3b56a17e --- /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"), TWCurveNIST256p1)); + 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"), TWCurveNIST256p1); + 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..359a643c59b --- /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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + + // 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + + // 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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), TWCurveED25519Blake2bNano); + 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..236020317fd --- /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://nanexplorer.com/nano/block/C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F"); + assertStringsEqual(accUrl, "https://nanexplorer.com/nano/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..637e83bff97 --- /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"), TWCoinTypeCurve(TWCoinTypeNebl)); + 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"), TWCoinTypeCurve(TWCoinTypeNebl)); + 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..e420d8684b4 --- /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"), TWCoinTypeCurve(TWCoinTypeNebl)); + 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..03762bc2146 --- /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"), TWCoinTypeCurve(TWCoinTypeNebl)); + 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..6cfdb58ae3a --- /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"), TWCoinTypeCurve(TWCoinTypeNebl)); + 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..8e8c107a549 --- /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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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..d8a56bdb9cd --- /dev/null +++ b/tests/chains/Nimiq/SignerTests.cpp @@ -0,0 +1,44 @@ +// 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, + Proto::NetworkId::Mainnet + ); + + 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..f8879960cd1 --- /dev/null +++ b/tests/chains/Nimiq/TWAnySignerTests.cpp @@ -0,0 +1,49 @@ +// 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"); +} + +TEST(TWAnySignerNimiq, SignPoS) { + 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()); + input.set_network_id(Proto::NetworkId::MainnetAlbatross); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNimiq); + + EXPECT_EQ(hex(output.encoded()), "000070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f180ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405"); +} + +} // 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..cd6eaeb2b22 --- /dev/null +++ b/tests/chains/Nimiq/TransactionTests.cpp @@ -0,0 +1,54 @@ +// 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, + Proto::NetworkId::UseDefault + ); + 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, + Proto::NetworkId::Mainnet + ); + + 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/Pactus/AddressTests.cpp b/tests/chains/Pactus/AddressTests.cpp new file mode 100644 index 00000000000..c3bcb0a260d --- /dev/null +++ b/tests/chains/Pactus/AddressTests.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Pactus/Entry.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Pactus::tests { + +TEST(PactusAddress, AddressData) { + auto string = STRING("pc1rspm7ps49gar9ft5g0tkl6lhxs8ygeakq87quh3"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePactus)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "038077e0c2a5474654ae887aedfd7ee681c88cf6c0"); +} + +TEST(PactusAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("2134ae97465505dfd5a1fd05a8a0f146209c601eb3f1b0363b4cfe4b47ba1ab4")); + auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypePactus, pubkey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "pc1r7jkvfnegf0rf5ua05fzu9krjhjxcrrygl3v4nl"); +} + +TEST(PactusAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("600d30a4373ae788e2d4a08f4728f45d259593fbdd9632bbe283c4c37ac6a3df"), TWPublicKeyTypeED25519); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypePactus, publicKey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "pc1r7jkvfnegf0rf5ua05fzu9krjhjxcrrygl3v4nl"); +} + +} // namespace TW::Pactus::tests \ No newline at end of file diff --git a/tests/chains/Pactus/CoinTypeTests.cpp b/tests/chains/Pactus/CoinTypeTests.cpp new file mode 100644 index 00000000000..cd28ee9de14 --- /dev/null +++ b/tests/chains/Pactus/CoinTypeTests.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include "Pactus/Entry.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include +#include + +namespace TW::Pactus::tests { + +TEST(PactusCoinType, TWCoinType) { + const auto coin = TWCoinTypePactus; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "pactus"); + assertStringsEqual(name, "Pactus"); + assertStringsEqual(symbol, "PAC"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 9); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainPactus); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://pacviewer.com/transaction/"); + assertStringsEqual(accUrl, "https://pacviewer.com/address/"); +} + +} \ No newline at end of file diff --git a/tests/chains/Pactus/CompilerTests.cpp b/tests/chains/Pactus/CompilerTests.cpp new file mode 100644 index 00000000000..0d4a902e2d9 --- /dev/null +++ b/tests/chains/Pactus/CompilerTests.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestCases.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" +#include "proto/Pactus.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +using namespace TW; + +TEST(PactusCompiler, CompileAndSign) { + for (const auto& testCase : TEST_CASES) { + auto input = testCase.createSigningInput(); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + // Pre-hash the transaction. + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypePactus, 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), testCase.dataToSign); + + // Sign the pre-hash data. + auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY_HEX)); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + EXPECT_EQ(hex(signature), testCase.signature); + + // Compile the transaction. + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypePactus, inputStrData, {signature}, {publicKey}); + TW::Pactus::Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.signed_transaction_data()), testCase.signedData); + ASSERT_EQ(hex(output.signature()), testCase.signature); + ASSERT_EQ(hex(output.transaction_id()), testCase.transactionID); + } +} diff --git a/tests/chains/Pactus/SignerTests.cpp b/tests/chains/Pactus/SignerTests.cpp new file mode 100644 index 00000000000..cbb469310c0 --- /dev/null +++ b/tests/chains/Pactus/SignerTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestCases.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" +#include "proto/Pactus.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +using namespace TW; + +TEST(PactusSigner, Sign) { + for (const auto& testCase : TEST_CASES) { + auto input = testCase.createSigningInput(); + + auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Pactus::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypePactus); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.signed_transaction_data()), testCase.signedData); + ASSERT_EQ(hex(output.signature()), testCase.signature); + ASSERT_EQ(hex(output.transaction_id()), testCase.transactionID); + } +} diff --git a/tests/chains/Pactus/TestCases.h b/tests/chains/Pactus/TestCases.h new file mode 100644 index 00000000000..1fc953b54e4 --- /dev/null +++ b/tests/chains/Pactus/TestCases.h @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "proto/Pactus.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +const std::string PRIVATE_KEY_HEX = "4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6"; + +namespace TransferTransaction { +// Successfully broadcasted transaction: +// https://pacviewer.com/transaction/1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f +static TW::Pactus::Proto::SigningInput createSigningInput() { + TW::Pactus::Proto::SigningInput input; + TW::Pactus::Proto::TransactionMessage* trx = input.mutable_transaction(); + trx->set_lock_time(2335524); + trx->set_fee(10000000); + trx->set_memo("wallet-core"); + + TW::Pactus::Proto::TransferPayload* pld = trx->mutable_transfer(); + pld->set_sender("pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + pld->set_receiver("pc1r0g22ufzn8qtw0742dmfglnw73e260hep0k3yra"); + pld->set_amount(200000000); + + return input; +} + +const std::string transactionID = "1b6b7226f7935a15f05371d1a1fefead585a89704ce464b7cc1d453d299d235f"; +const std::string signature = "4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9b49d33a0fc" + "8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736693eda8506"; +const std::string dataToSign = "0124a3230080ade2040b77616c6c65742d636f726501037098338e0b6808119d" + "fd4457ab806b9c2059b89b037a14ae24533816e7faaa6ed28fcdde8e55a7df21" + "8084af5f"; +const std::string signedData = "000124a3230080ade2040b77616c6c65742d636f726501037098338e0b680811" + "9dfd4457ab806b9c2059b89b037a14ae24533816e7faaa6ed28fcdde8e55a7df" + "218084af5f4ed8fee3d8992e82660dd05bbe8608fc56ceabffdeeee61e3213b9" + "b49d33a0fc8dea6d79ee7ec60f66433f189ed9b3c50b2ad6fa004e26790ee736" + "693eda850695794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560b" + "b72145f4fa"; +} // namespace TransferTransaction + +namespace BondWithPublicKeyTransaction { +// Successfully broadcasted transaction: +// https://pacviewer.com/transaction/d194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f + +static TW::Pactus::Proto::SigningInput createSigningInput() { + TW::Pactus::Proto::SigningInput input; + TW::Pactus::Proto::TransactionMessage* trx = input.mutable_transaction(); + trx->set_lock_time(2339009); + trx->set_fee(10000000); + trx->set_memo("wallet-core"); + + TW::Pactus::Proto::BondPayload* pld = trx->mutable_bond(); + pld->set_sender("pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + pld->set_receiver("pc1p9y5gmu9l002tt60wak9extgvwm69rq3a9ackrl"); + pld->set_stake(1000000000); + pld->set_public_key("public1pnz75msstqdrq5eguvcwanug0zauhqjw2cc4flmez3qethnp68y64ehc4k69amapj7x4na2uda0snqz4yxujgx3jsse4f64fgy7jkh0xauvhrc5ts09vfk48g85t0js66hvajm6xruemsvlxqv3xvkyur8v9v0mtn"); + + return input; +} + +const std::string transactionID = "d194b445642a04ec78ced4448696e50b733f2f0b517a23871882c0eefaf1c28f"; +const std::string signature = "0d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda55b4338328adac7" + "9d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff65e39ce7006300"; +const std::string dataToSign = "01c1b0230080ade2040b77616c6c65742d636f726502037098338e0b6808119d" + "fd4457ab806b9c2059b89b0129288df0bf7bd4b5e9eeed8b932d0c76f451823d" + "6098bd4dc20b03460a651c661dd9f10f17797049cac62a9fef228832bbcc3a39" + "355cdf15b68bddf432f1ab3eab8debe1300aa43724834650866a9d552827a56b" + "bcdde32e3c517079589b54e83d16f9435abb3b2de8c3e677067cc0644ccb1383" + "3b8094ebdc03"; +const std::string signedData = "0001c1b0230080ade2040b77616c6c65742d636f726502037098338e0b680811" + "9dfd4457ab806b9c2059b89b0129288df0bf7bd4b5e9eeed8b932d0c76f45182" + "3d6098bd4dc20b03460a651c661dd9f10f17797049cac62a9fef228832bbcc3a" + "39355cdf15b68bddf432f1ab3eab8debe1300aa43724834650866a9d552827a5" + "6bbcdde32e3c517079589b54e83d16f9435abb3b2de8c3e677067cc0644ccb13" + "833b8094ebdc030d7bc6d94927534b89e2f53bcfc9fc849e0e2982438955eda5" + "5b4338328adac79d4ee3216d143f0e1629764ab650734f8ba188e716d71f9eff" + "65e39ce700630095794161374b22c696dabb98e93f6ca9300b22f3b904921fbf" + "560bb72145f4fa"; +} // namespace BondWithPublicKeyTransaction + +namespace BondWithoutPublicKeyTransaction { +// Successfully broadcasted transaction: +// https://pacviewer.com/transaction/f83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80 + +static TW::Pactus::Proto::SigningInput createSigningInput() { + TW::Pactus::Proto::SigningInput input; + TW::Pactus::Proto::TransactionMessage* trx = input.mutable_transaction(); + trx->set_lock_time(2335580); + trx->set_fee(10000000); + trx->set_memo("wallet-core"); + + TW::Pactus::Proto::BondPayload* pld = trx->mutable_bond(); + pld->set_sender("pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + pld->set_receiver("pc1p6taz5l2kq5ppnxv4agnqj48svnvsy797xpe6wd"); + pld->set_stake(1000000000); + + return input; +} + +const std::string transactionID = "f83f583a5c40adf93a90ea536a7e4b467d30ca4f308d5da52624d80c42adec80"; +const std::string signature = "9e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d85c087a8748ff0" + "715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc436aa58f9a8f00d"; +const std::string dataToSign = "015ca3230080ade2040b77616c6c65742d636f726502037098338e0b6808119d" + "fd4457ab806b9c2059b89b01d2fa2a7d560502199995ea260954f064d90278be" + "008094ebdc03"; +const std::string signedData = "00015ca3230080ade2040b77616c6c65742d636f726502037098338e0b680811" + "9dfd4457ab806b9c2059b89b01d2fa2a7d560502199995ea260954f064d90278" + "be008094ebdc039e6279fb64067c7d7316ac74630bbb8589df268aa4548f1c7d" + "85c087a8748ff0715b9149afbd94c5d8ee6b37c787ec63e963cbb38be513ebc4" + "36aa58f9a8f00d95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf" + "560bb72145f4fa"; + +} // namespace BondWithoutPublicKeyTransaction + +struct TestCase { + std::function createSigningInput; + std::string transactionID; + std::string signature; + std::string dataToSign; + std::string signedData; +}; + +const TestCase TEST_CASES[] = { + { + TransferTransaction::createSigningInput, + TransferTransaction::transactionID, + TransferTransaction::signature, + TransferTransaction::dataToSign, + TransferTransaction::signedData, + }, + { + BondWithPublicKeyTransaction::createSigningInput, + BondWithPublicKeyTransaction::transactionID, + BondWithPublicKeyTransaction::signature, + BondWithPublicKeyTransaction::dataToSign, + BondWithPublicKeyTransaction::signedData, + }, + { + BondWithoutPublicKeyTransaction::createSigningInput, + BondWithoutPublicKeyTransaction::transactionID, + BondWithoutPublicKeyTransaction::signature, + BondWithoutPublicKeyTransaction::dataToSign, + BondWithoutPublicKeyTransaction::signedData, + }, +}; \ No newline at end of file diff --git a/tests/chains/Pactus/WalletTests.cpp b/tests/chains/Pactus/WalletTests.cpp new file mode 100644 index 00000000000..0a2f553b8e6 --- /dev/null +++ b/tests/chains/Pactus/WalletTests.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include "Pactus/Entry.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include +#include + +namespace TW::Pactus::tests { + +TEST(PactusWallet, DerivationPath) { + auto derivationPath = TWCoinTypeDerivationPath(TWCoinTypePactus); + assertStringsEqual(WRAPS(derivationPath), "m/44'/21888'/3'/0'"); +} + +TEST(PactusWallet, HDWallet_MainnetDerivation) { + auto mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus"; + auto passphrase = ""; + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto derivationPath1 = TWStringCreateWithUTF8Bytes("m/44'/21888'/3'/0'"); + auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath1)); + auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey1.get())); + auto address1 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey1.get(), TWCoinTypePactus)); + auto addressStr1 = WRAPS(TWAnyAddressDescription(address1.get())); + + auto derivationPath2 = TWStringCreateWithUTF8Bytes("m/44'/21888'/3'/1'"); + auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath2)); + auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey2.get())); + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey2.get(), TWCoinTypePactus)); + auto addressStr2 = WRAPS(TWAnyAddressDescription(address2.get())); + + assertStringsEqual(addressStr1, "pc1rcx9x55nfme5juwdgxd2ksjdcmhvmvkrygmxpa3"); + assertStringsEqual(addressStr2, "pc1r7aynw9urvh66ktr3fte2gskjjnxzruflkgde94"); + TWStringDelete(derivationPath1); + TWStringDelete(derivationPath2); +} + +TEST(PactusWallet, HDWallet_TestnetDerivation) { + auto mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus"; + auto passphrase = ""; + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto derivationPath1 = TWStringCreateWithUTF8Bytes("m/44'/21777'/3'/0'"); + auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath1)); + auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey1.get())); + auto address1 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(publicKey1.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + auto addressStr1 = WRAPS(TWAnyAddressDescription(address1.get())); + + auto derivationPath2 = TWStringCreateWithUTF8Bytes("m/44'/21777'/3'/1'"); + auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath2)); + auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey2.get())); + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(publicKey2.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + auto addressStr2 = WRAPS(TWAnyAddressDescription(address2.get())); + + assertStringsEqual(addressStr1, "tpc1r35xwz99uw2qrhz9wmdanaqcsge2nzsfegvv555"); + assertStringsEqual(addressStr2, "tpc1r34xj32k004j8v35fx6uqw4yaka54g6jdr58tvk"); + TWStringDelete(derivationPath1); + TWStringDelete(derivationPath2); +} + +} \ No newline at end of file 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/TWAnyAddressTests.cpp b/tests/chains/Polkadot/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..2a18ba00c41 --- /dev/null +++ b/tests/chains/Polkadot/TWAnyAddressTests.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +namespace TW::Polkadot::tests { +extern uint32_t polkadotPrefix; +extern uint32_t kusamaPrefix; +extern uint32_t astarPrefix; +extern uint32_t parallelPrefix; + +TEST(PolkadotAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ").get(), TWCoinTypePolkadot)); + // Bitcoin + ASSERT_FALSE(TWAnyAddressIsValid(STRING("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA").get(), TWCoinTypePolkadot)); + // Kusama ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ").get(), TWCoinTypePolkadot)); + // Kusama secp256k1 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx").get(), TWCoinTypePolkadot)); + // Kusama sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe").get(), TWCoinTypePolkadot)); + + // Polkadot ed25519 + ASSERT_TRUE(TWAnyAddressIsValid(STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get(), TWCoinTypePolkadot)); + // Polkadot sr25519 + ASSERT_TRUE(TWAnyAddressIsValid(STRING("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony").get(), TWCoinTypePolkadot)); + + ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb").get(), TWCoinTypePolkadot, 64)); + ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolkadot, 64)); +} + +TEST(PolkadotAddress, FromPrivateKey) { + // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get())); + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypePolkadot)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolkadot)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get())); +} + +TEST(PolkadotAddress, FromPublicKey) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908").get(), TWPublicKeyTypeED25519)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolkadot)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get())); +} + +TEST(PolkadotAddress, FromPublicKeyWithPrefix) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0x92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3").get(), TWPublicKeyTypeED25519)); + + const auto addressPolkadot = STRING("14KjL5vGAYJCbKgZJmFKDSjewtBpvaxx9YvRZvi7qmb5s8CC"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolkadot, polkadotPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolkadot.get())); + } + + const auto addressAstar = STRING("ZG2d3dH5zfqNchsqReS6x4nBJuJCW7Z6Fh5eLvdA3ZXGkPd"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolkadot, astarPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressAstar.get())); + } + + const auto addressParallel = STRING("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolkadot, parallelPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressParallel.get())); + } +} + +TEST(PolkadotAddress, FromString) { + auto addressStr1 = STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(addressStr1.get(), TWCoinTypePolkadot)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressStr1.get())); +} + +TEST(PolkadotAddress, FromStringWithPrefix) { + const auto kusamaAddress = STRING("Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(kusamaAddress.get(), TWCoinTypeKusama, kusamaPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), kusamaAddress.get())); + } + + auto addressParallel = STRING("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(addressParallel.get(), TWCoinTypePolkadot, parallelPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressParallel.get())); + } +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/TWAnySignerTests.cpp b/tests/chains/Polkadot/TWAnySignerTests.cpp new file mode 100644 index 00000000000..7c6328d4d98 --- /dev/null +++ b/tests/chains/Polkadot/TWAnySignerTests.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AnyAddress.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polkadot.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "uint256.h" + +#include +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { +uint32_t polkadotPrefix = ss58Prefix(TWCoinTypePolkadot); +uint32_t kusamaPrefix = ss58Prefix(TWCoinTypeKusama); +uint32_t astarPrefix = 5; +uint32_t parallelPrefix = 172; + +auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); +auto privateKeyThrow2Data = DATA("70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f"); +auto privateKeyThrow2 = TWPrivateKeyCreateWithData(privateKeyThrow2Data.get()); +auto addressThrow2 = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2"; +auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + +Data helper_encodePayload(TWCoinType coin, const Proto::SigningInput& input) { + auto txInputData = data(input.SerializeAsString()); + 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 preSigningOutput; + preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size())); + + EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); + return data(preSigningOutput.data()); +} + +TEST(TWAnySignerPolkadot, SignTransfer_9fd062) { + auto toAddressStr = STRING("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); + { + auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKeyThrow2)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey.get(), TWCoinTypePolkadot)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + ASSERT_STREQ(TWStringUTF8Bytes(addressStr.get()), addressThrow2); + } + input.set_private_key(TWDataBytes(privateKeyThrow2Data.get()), TWDataSize(privateKeyThrow2Data.get())); + 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(TWStringUTF8Bytes(toAddressStr.get())); + transfer->set_value(value.data(), value.size()); + + auto preimage = helper_encodePayload(TWCoinTypePolkadot, input); + EXPECT_EQ(hex(preimage), "05007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577a5030c001a0000000500000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c35d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypePolkadot); + + // https://polkadot.subscan.io/extrinsic/0x9fd06208a6023e489147d8d93f0182b0cb7e45a40165247319b87278e08362d8 + EXPECT_EQ(hex(output.encoded()), "3502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830073e59cef381aedf56d7af076bafff9857ffc1e3bd7d1d7484176ff5b58b73f1211a518e1ed1fd2ea201bd31869c0798bba4ffe753998c409d098b65d25dff801a5030c0005007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577"); +} + +} // 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..eddf587de1d --- /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_signatures_count); + } +} + +} // 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/Polymesh/TWAnyAddressTests.cpp b/tests/chains/Polymesh/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..1602ff3e1a0 --- /dev/null +++ b/tests/chains/Polymesh/TWAnyAddressTests.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +using namespace TW; + +namespace TW::Polymesh::tests { +extern uint32_t polymeshPrefix; + +TEST(TWPolymesh, Address) { + auto string = STRING("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePolymesh)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); +} + +TEST(PolymeshAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ").get(), TWCoinTypePolymesh)); + // Bitcoin + ASSERT_FALSE(TWAnyAddressIsValid(STRING("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA").get(), TWCoinTypePolymesh)); + // Kusama ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ").get(), TWCoinTypePolymesh)); + // Kusama secp256k1 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx").get(), TWCoinTypePolymesh)); + // Kusama sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe").get(), TWCoinTypePolymesh)); + + // Polkadot ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get(), TWCoinTypePolymesh)); + // Polkadot sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony").get(), TWCoinTypePolymesh)); + + // Polymesh + ASSERT_TRUE(TWAnyAddressIsValid(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolymesh)); + ASSERT_FALSE(TWAnyAddressIsValid(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolymesh)); + ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolymesh, polymeshPrefix)); + ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolymesh, polymeshPrefix)); +} + +TEST(PolymeshAddress, FromPrivateKey) { + // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get())); + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypePolymesh)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("2GmLy7KywpsV5fDpZfJMcgGgzoJWyrEA3Wc3fDmsoq5iqtBT").get())); +} + +TEST(PolymeshAddress, FromPublicKey) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908").get(), TWPublicKeyTypeED25519)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("2GmLy7KywpsV5fDpZfJMcgGgzoJWyrEA3Wc3fDmsoq5iqtBT").get())); +} + +TEST(PolymeshAddress, FromPublicKeyWithPrefix) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83").get(), TWPublicKeyTypeED25519)); + const auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolymesh, polymeshPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); + } +} + +TEST(PolymeshAddress, FromString) { + auto string = STRING("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePolymesh)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); +} + +TEST(PolymeshAddress, FromStringWithPrefix) { + // polymesh + auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(addressPolymesh.get(), TWCoinTypePolymesh, polymeshPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); + } +} + +} // namespace TW::Polymesh::tests \ No newline at end of file diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp new file mode 100644 index 00000000000..cd4937ae4da --- /dev/null +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AnyAddress.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polymesh.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "uint256.h" + +#include +#include +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +using namespace TW; +using namespace TW::Polymesh; + +namespace TW::Polymesh::tests { +uint32_t polymeshPrefix = 12; + +Data helper_encodeTransaction(TWCoinType coin, const Proto::SigningInput& input, const Data& pubKey, const Data& signature) { + auto txInputData = data(input.SerializeAsString()); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); + + Polymesh::Proto::SigningOutput output; + output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get())); + EXPECT_EQ(output.error(), Common::Proto::OK); + + return data(output.encoded()); +} + +TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypePolymesh; + + Polymesh::Proto::SigningInput input; + input.set_network(12); + 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_runtime_call()->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); + + /// Step 2: Obtain preimage hash + auto txInputData = data(input.SerializeAsString()); + 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 preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + const auto preImage = data(preSigningOutput.data()); + + ASSERT_EQ(hex(preImage), "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455300000000000000000025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + + auto pubKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); + + const auto ExpectedTx = + "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553000000000000000000"; + { + Polymesh::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + Polymesh::Proto::SigningInput input; + input.set_network(12); + 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_runtime_call()->mutable_identity_call()->mutable_add_authorization(); + addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); + auto* keyPerms = addAuthorization->mutable_authorization()->mutable_join_identity(); + // Set empty "These". + auto* assets = keyPerms->mutable_asset(); + assets->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + auto* extrinsics = keyPerms->mutable_extrinsic(); + extrinsics->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + auto* portfolios = keyPerms->mutable_portfolio(); + portfolios->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + + auto* callIndices = addAuthorization->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x0d); + + auto pubKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"); + auto encoded = helper_encodeTransaction(TWCoinTypePolymesh, input, pubKey, signature); + ASSERT_EQ(hex(encoded), "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + Polymesh::Proto::SigningInput input; + input.set_network(12); + 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_runtime_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 pubKey = parse_hex("d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"); + auto signature = parse_hex("7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"); + auto encoded = helper_encodeTransaction(TWCoinTypePolymesh, input, pubKey, signature); + ASSERT_EQ(hex(encoded), "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} + +} // namespace TW::Polymesh::tests diff --git a/tests/chains/Polymesh/TWCoinTypeTests.cpp b/tests/chains/Polymesh/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4271fff9bbe --- /dev/null +++ b/tests/chains/Polymesh/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWPolymeshCoinType, TWCoinType) { + const auto coin = TWCoinTypePolymesh; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "polymesh"); + assertStringsEqual(name, "Polymesh"); + assertStringsEqual(symbol, "POLYX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainPolymesh); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04"); + assertStringsEqual(accUrl, "https://polymesh.subscan.io/account/2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); +} 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 84% rename from tests/Ravencoin/TWRavencoinTransactionTests.cpp rename to tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp index 8a62bcb5abf..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) { /* @@ -61,37 +57,35 @@ TEST(RavencoinTransaction, SignTransaction) { plan.fee = fee; plan.change = utxo_amount - amount - fee; - auto &protoPlan = *input.mutable_plan(); + auto& protoPlan = *input.mutable_plan(); protoPlan = plan.proto(); // Sign - auto signer = TransactionSigner(std::move(input)); - auto result = signer.sign(); + auto result = TransactionSigner::sign(input); auto signedTx = result.payload(); - ASSERT_TRUE(result); - ASSERT_EQ(fee, signer.plan.fee); Data serialized; 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..f40629fd167 --- /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.smartscout.cash/tx/0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9"); + assertStringsEqual(accUrl, "https://www.smartscout.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/SolanaMessageSigner.cpp b/tests/chains/Solana/SolanaMessageSigner.cpp new file mode 100644 index 00000000000..a335ed7109e --- /dev/null +++ b/tests/chains/Solana/SolanaMessageSigner.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "Data.h" +#include "HexCoding.h" +#include "proto/Solana.pb.h" + +#include "TestUtilities.h" +#include +#include + +namespace TW::Solana { + +TEST(SolanaMessageSigner, Sign) { + const auto privateKey = parse_hex("44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d"); + const auto message = "Hello world"; + + Proto::MessageSigningInput input; + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_message(message); + + const auto inputData = data(input.SerializeAsString()); + const auto inputDataPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + Proto::MessageSigningOutput output; + const auto outputDataPtr = WRAPD(TWMessageSignerSign(TWCoinTypeSolana, inputDataPtr.get())); + output.ParseFromArray(TWDataBytes(outputDataPtr.get()), static_cast(TWDataSize(outputDataPtr.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(output.signature(), "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG"); +} + +TEST(SolanaMessageSigner, Verify) { + const auto publicKey = parse_hex("ee6d61a89fc8f9909585a996bb0d2b2ac69ae23b5acf39a19f32631239ba06f9"); + const auto message = "Hello world"; + const auto signature = "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG"; + + Proto::MessageVerifyingInput input; + input.set_public_key(publicKey.data(), publicKey.size()); + input.set_message(message); + input.set_signature(signature); + + const auto inputData = data(input.SerializeAsString()); + const auto inputDataPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + EXPECT_TRUE(TWMessageSignerVerify(TWCoinTypeSolana, inputDataPtr.get())); +} + +} // namespace TW::Solana 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..f562dcfb8b4 --- /dev/null +++ b/tests/chains/Solana/TWSolanaTransaction.cpp @@ -0,0 +1,194 @@ +// 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 "HexCoding.h" +#include "TransactionCompiler.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"); +} + +TEST(TWSolanaTransaction, SetPriorityFee) { + // base64 encoded + const auto privateKey = parse_hex("baf2b2dbbbad7ca96c1fa199c686f3d8fbd2c7b352f307e37e04f33df6741f18"); + const auto originalTx = STRING("AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAEDODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG6GdPcA92ORzVJe2jfG8KQqqMHr9YTLu30oM4i7MFEoBAgIAAQwCAAAA6AMAAAAAAAA="); + + // Step 1 - Check if there are no price and limit instructions in the original transaction. + + EXPECT_EQ(TWSolanaTransactionGetComputeUnitPrice(originalTx.get()), nullptr); + EXPECT_EQ(TWSolanaTransactionGetComputeUnitLimit(originalTx.get()), nullptr); + + // Step 2 - Set price and limit instructions. + + const auto txWithPrice = WRAPS(TWSolanaTransactionSetComputeUnitPrice(originalTx.get(), STRING("1000").get())); + const auto updatedTx = WRAPS(TWSolanaTransactionSetComputeUnitLimit(txWithPrice.get(), STRING("10000").get())); + + assertStringsEqual(updatedTx, "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA"); + + // Step 3 - Check if price and limit instructions are set successfully. + + assertStringsEqual(WRAPS(TWSolanaTransactionGetComputeUnitPrice(updatedTx.get())), "1000"); + assertStringsEqual(WRAPS(TWSolanaTransactionGetComputeUnitLimit(updatedTx.get())), "10000"); + + // Step 4 - Decode transaction into a `RawMessage` Protobuf. + + const std::string updateTxDataB64 {TWStringUTF8Bytes(updatedTx.get()) }; + const auto updatedTxData = Base64::decode(updateTxDataB64); + const auto updatedTxRef = WRAPD(TWDataCreateWithBytes(updatedTxData.data(), updatedTxData.size())); + + const auto decodeOutputData = WRAPD(TWTransactionDecoderDecode(TWCoinTypeSolana, updatedTxRef.get())); + Proto::DecodingTransactionOutput decodeOutput; + decodeOutput.ParseFromArray(TWDataBytes(decodeOutputData.get()), static_cast(TWDataSize(decodeOutputData.get()))); + EXPECT_EQ(decodeOutput.error(), Common::Proto::SigningError::OK); + + // Step 5 - Sign the decoded `RawMessage` transaction. + + Proto::SigningInput input; + input.set_private_key(privateKey.data(), privateKey.size()); + *input.mutable_raw_message() = decodeOutput.transaction(); + input.set_tx_encoding(Proto::Encoding::Base64); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/2ho7wZUXbDNz12xGfsXg2kcNMqkBAQjv7YNXNcVcuCmbC4p9FZe9ELeM2gMjq9MKQPpmE3nBW5pbdgwVCfNLr1h8 + EXPECT_EQ(output.encoded(), "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA"); +} + + +TEST(TWSolanaTransaction, SetFeePayer) { + const auto coin = TWCoinTypeSolana; + + // base64 encoded + const auto originalTx = STRING("AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA=="); + + // Step 1 - Add fee payer to the transaction. + const auto feePayer = STRING("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); + const auto updatedTx = WRAPS(TWSolanaTransactionSetFeePayer(originalTx.get(), feePayer.get())); + + assertStringsEqual(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA=="); + + // Step 2 - Decode transaction into a `RawMessage` Protobuf. + const std::string updateTxDataB64 {TWStringUTF8Bytes(updatedTx.get()) }; + const auto updatedTxData = Base64::decode(updateTxDataB64); + const auto updatedTxRef = WRAPD(TWDataCreateWithBytes(updatedTxData.data(), updatedTxData.size())); + + const auto decodeOutputData = WRAPD(TWTransactionDecoderDecode(TWCoinTypeSolana, updatedTxRef.get())); + Proto::DecodingTransactionOutput decodeOutput; + decodeOutput.ParseFromArray(TWDataBytes(decodeOutputData.get()), static_cast(TWDataSize(decodeOutputData.get()))); + EXPECT_EQ(decodeOutput.error(), Common::Proto::SigningError::OK); + + // Step 3 - Obtain preimage hash. + Proto::SigningInput input; + *input.mutable_raw_message() = decodeOutput.transaction(); + input.set_tx_encoding(Proto::Encoding::Base64); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 2); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), "8002000104cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b576b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea64d77772adc14c8915f46cd8f05f7905bcc42119bcdaffe49fd3c7c96d6e7d29c00000000000000000000000000000000000000000000000000000000000000002a3e4116ef5d634aa0e7da38be1c4a97d8ae69ffd9357e74199cb7e1ec9a6c1d01030201020c02000000009c9f060000000000"); + + // Step 4 - Compile transaction info. + // Simulate signature, normally obtained from signature server. + const auto feePayerSignature = parse_hex("feb9f15cc345fa156450676100033860edbe80a6f61dab8199e94fdc47678ecfdb95e3bc10ec0a7f863ab8ef5c38edae72db7e5d72855db225fd935fd59b700a"); + const auto feePayerPublicKey = parse_hex("cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b57"); + const auto solSenderSignature = parse_hex("936cd6d176e701d1f748031925b2f029f6f1ab4b99aec76e24ccf05649ec269569a08ec0bd80f5fee1cb8d13ecd420bf50c5f64ae74c7afa267458cabb4e5804"); + const auto solSenderPublicKey = parse_hex("6b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea6"); + + auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {feePayerSignature, solSenderSignature}, {feePayerPublicKey, solSenderPublicKey}); + const auto expectedTx = "Av658VzDRfoVZFBnYQADOGDtvoCm9h2rgZnpT9xHZ47P25XjvBDsCn+GOrjvXDjtrnLbfl1yhV2yJf2TX9WbcAqTbNbRducB0fdIAxklsvAp9vGrS5mux24kzPBWSewmlWmgjsC9gPX+4cuNE+zUIL9QxfZK50x6+iZ0WMq7TlgEgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA=="; + Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded(), expectedTx); + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/66PAVjxFVGP4ctrkXmyNRhp6BdFT7gDe1k356DZzCRaBDTmJZF1ewGsbujWRjDTrt5utnz8oHZw3mg8qBNyct41w?cluster=devnet +} + +} // 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/Sonic/TWCoinTypeTests.cpp b/tests/chains/Sonic/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a1af7ec9d24 --- /dev/null +++ b/tests/chains/Sonic/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWSonicCoinType, TWCoinType) { + const auto coin = TWCoinTypeSonic; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x886c34de9e054c741b2bcb15c3a3e0382e3ed7a48f2c6f2a489f6d82abdd4a7c")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9c174f0e2d11559447e5fe2815d930475be19016")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "sonic"); + assertStringsEqual(name, "Sonic"); + assertStringsEqual(symbol, "S"); + 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://sonicscan.org/tx/0x886c34de9e054c741b2bcb15c3a3e0382e3ed7a48f2c6f2a489f6d82abdd4a7c"); + assertStringsEqual(accUrl, "https://sonicscan.org/address/0x9c174f0e2d11559447e5fe2815d930475be19016"); +} 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/MessageSignerTests.cpp b/tests/chains/Sui/MessageSignerTests.cpp new file mode 100644 index 00000000000..414cb6ee95d --- /dev/null +++ b/tests/chains/Sui/MessageSignerTests.cpp @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "Data.h" +#include "HexCoding.h" +#include "proto/Sui.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include "TestUtilities.h" +#include +#include + +namespace TW::Sui { + +TEST(SuiMessageSigner, Sign) { + const auto privateKey = parse_hex("44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d"); + const auto message = "Hello world"; + + Proto::MessageSigningInput input; + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_message(message); + + const auto inputData = data(input.SerializeAsString()); + const auto inputDataPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + Proto::MessageSigningOutput output; + const auto outputDataPtr = WRAPD(TWMessageSignerSign(TWCoinTypeSui, inputDataPtr.get())); + output.ParseFromArray(TWDataBytes(outputDataPtr.get()), static_cast(TWDataSize(outputDataPtr.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(output.signature(), "ABUNBl59ILPhyGpdgWpXJIQtEIMidR27As1771Hn7j9wVR/5IetQslRPMBrUC2THM+yGHw7h2N/Mr/0DMOpXLQ7ubWGon8j5kJWFqZa7DSsqxpriO1rPOaGfMmMSOboG+Q=="); +} + +TEST(SuiMessageSigner, Verify) { + const auto publicKey = parse_hex("ee6d61a89fc8f9909585a996bb0d2b2ac69ae23b5acf39a19f32631239ba06f9"); + const auto message = "Hello world"; + const auto signature = "ABUNBl59ILPhyGpdgWpXJIQtEIMidR27As1771Hn7j9wVR/5IetQslRPMBrUC2THM+yGHw7h2N/Mr/0DMOpXLQ7ubWGon8j5kJWFqZa7DSsqxpriO1rPOaGfMmMSOboG+Q=="; + + Proto::MessageVerifyingInput input; + input.set_public_key(publicKey.data(), publicKey.size()); + input.set_message(message); + input.set_signature(signature); + + const auto inputData = data(input.SerializeAsString()); + const auto inputDataPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + EXPECT_TRUE(TWMessageSignerVerify(TWCoinTypeSui, inputDataPtr.get())); +} + +TEST(SuiMessageSigner, PreImageHashes) { + const auto publicKey = parse_hex("ee6d61a89fc8f9909585a996bb0d2b2ac69ae23b5acf39a19f32631239ba06f9"); + const auto message = "Hello world"; + + Proto::MessageSigningInput input; + input.set_message(message); + + const auto inputData = data(input.SerializeAsString()); + const auto inputDataPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + const auto outputDataPtr = WRAPD(TWMessageSignerPreImageHashes(TWCoinTypeSui, inputDataPtr.get())); + + TxCompiler::Proto::PreSigningOutput output; + output.ParseFromArray(TWDataBytes(outputDataPtr.get()), static_cast(TWDataSize(outputDataPtr.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + ASSERT_EQ(hex(output.data()), "6b27c39ed22f5346dbce4eca17640e1d139012768746aaa42eafe103f2f9ede2"); + ASSERT_EQ(hex(output.data_hash()), "6b27c39ed22f5346dbce4eca17640e1d139012768746aaa42eafe103f2f9ede2"); +} + +} // namespace TW::Sui 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..37255efa4f5 --- /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("68wBKsZyYXmCUydDmabQ71kTcFWTfDG7tFmTLk1HgNdN")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x54e80d76d790c277f5a44f3ce92f53d26f5894892bf395dee6375988876be6b2")); + 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://suiscan.xyz/mainnet/tx/68wBKsZyYXmCUydDmabQ71kTcFWTfDG7tFmTLk1HgNdN"); + assertStringsEqual(accUrl, "https://suiscan.xyz/mainnet/account/0x54e80d76d790c277f5a44f3ce92f53d26f5894892bf395dee6375988876be6b2"); +} 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..56f046c2f8a --- /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"), TWCurveED25519); + 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()), TWCurveED25519); + + 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..76139adb715 --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" + +#include "proto/TheOpenNetwork.pb.h" +#include + +#include "TestUtilities.h" +#include "uint256.h" + +#include + +namespace TW::TheOpenNetwork::tests { + +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV4R2) { + Proto::SigningInput input; + + auto& transfer = *input.add_messages(); + transfer.set_dest("EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0"); + + Data amountData = store(uint256_t(10)); + std::string amountStr(amountData.begin(), amountData.end()); + transfer.set_amount(amountStr); + 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"); + Data amountData = store(uint256_t(10)); + std::string amountStr(amountData.begin(), amountData.end()); + transfer.set_amount(amountStr); + 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..ec93a9dc78d --- /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 + +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..778a21cb3de --- /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"), TWCurveSECP256k1); + 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..43e58849c4e --- /dev/null +++ b/tests/chains/Tron/SignerTests.cpp @@ -0,0 +1,584 @@ +// 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, SignDirectRawJsonTransferAsset) { + auto input = Proto::SigningInput(); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto rawJson = 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 + }, + "visible":false, + "txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb" +})"; + input.set_raw_json(rawJson); + 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, SignTransferWithMemo) { + // Successfully broadcasted https://tronscan.org/#/transaction/20321755964d6ec5bcfc9ebfb15faeb043787ae599fff44442962e12e1c357f1 + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer(); + transfer.set_owner_address("TFnYQCt892UNjn67pjAULTSTkB7YvqsnPp"); + transfer.set_to_address("TBUCzgc29vykkvFaEG2mgRtxKvaKe6skwX"); + transfer.set_amount(100000); + + transaction.set_timestamp(1730827017000); + transaction.set_expiration(1730827017000 + 10 * 60 * 60 * 1000); + transaction.set_memo("Test memo"); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1730827017000); + const auto txTrieRoot = parse_hex("a94f115089893f37336baf32dbf6cb7d06adc13cf6bf046d9bc22748bd72e7a6"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("0000000003fa27db7d67f93920f64733532412ab6a71eb4089dc48c8ff5e182c"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(66725852); + const auto witnessAddress = parse_hex("4167e39013be3cdd3814bed152d7439fb5b6791409"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(30); + + const auto privateKey = PrivateKey(parse_hex("7c2108a30f6f69f8dce72a7df897eabadfe9810eee6976b43bdf8c0b0d35337d")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + EXPECT_EQ(hex(output.id()), "20321755964d6ec5bcfc9ebfb15faeb043787ae599fff44442962e12e1c357f1"); + EXPECT_EQ(hex(output.signature()), "6fcee79c61f660ec689299f77924f32b5020b4c41593056052ef07d640cc799325103fab130c8691e8a224c96cd0704a698ac356ff789a543c284605668bf38000"); +} + +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..df5fddcdc9d --- /dev/null +++ b/tests/chains/Tron/TransactionCompilerTests.cpp @@ -0,0 +1,183 @@ +// 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); + } +} + +TEST(TronCompiler, CompileWithSignaturesRawJson) { + const auto privateKey = + PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + constexpr auto coin = TWCoinTypeTron; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Tron::Proto::SigningInput(); + auto rawJson = 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 + }, + "visible":false, + "txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb" +})"; + input.set_raw_json(rawJson); + + 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(), static_cast(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 expectedTx = 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","visible":false})"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + + { + TW::Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(output.json(), expectedTx); + } + + { // Negative: invalid raw json + auto input = TW::Tron::Proto::SigningInput(); + auto invalidRawJson = "not valid json"; + input.set_raw_json(invalidRawJson); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, {publicKey.bytes}); + Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Tron/TronMessageSignerTests.cpp b/tests/chains/Tron/TronMessageSignerTests.cpp new file mode 100644 index 00000000000..548e44f394d --- /dev/null +++ b/tests/chains/Tron/TronMessageSignerTests.cpp @@ -0,0 +1,45 @@ +// 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); + auto pubKey = tronKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + ASSERT_EQ(signature, "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c"); + ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg, signature)); + + auto msg2 = "A much longer message to test the signing and verification process"; + auto signature2 = Tron::MessageSigner::signMessage(tronKey, msg2); + + ASSERT_EQ(signature2, "93aee5f753cf889e0749c74dd0c5996cce889883ae079e09ede462e16d65d06a4f43d1ed2745e9f3c1690695628269bd58f057a4a93953cc50e66b4a05bc0f451b"); + ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg2, signature2)); + } + + 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())), "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c"); + EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + + const auto message2 = STRING("A much longer message to test the signing and verification process"); + const auto signature2 = WRAPS(TWTronMessageSignerSignMessage(privateKey.get(), message2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature2.get())), "93aee5f753cf889e0749c74dd0c5996cce889883ae079e09ede462e16d65d06a4f43d1ed2745e9f3c1690695628269bd58f057a4a93953cc50e66b4a05bc0f451b"); + EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message2.get(), signature2.get())); + } +} diff --git a/tests/chains/VeChain/SignerTests.cpp b/tests/chains/VeChain/SignerTests.cpp new file mode 100644 index 00000000000..5b8d0135fee --- /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"), TWCurveSECP256k1); + 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..414ea7bfef6 --- /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, TWCurveSECP256k1).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..ab012c1497d --- /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"), TWCurveCurve25519); + 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"), TWCurveCurve25519); + 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"), TWCurveCurve25519); + 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"), TWCurveCurve25519); + 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..c4e6f61e263 --- /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"), TWCurveCurve25519); + 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..5597f37259b --- /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"), TWCurveCurve25519); + 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"), TWCurveCurve25519); + 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"), TWCurveCurve25519); + 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/TWAnySignerTests.cpp b/tests/chains/XRP/TWAnySignerTests.cpp new file mode 100644 index 00000000000..171e77ab690 --- /dev/null +++ b/tests/chains/XRP/TWAnySignerTests.cpp @@ -0,0 +1,504 @@ +// 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"); +} + +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, SignTrustSetPaymentNonStandardCurrencyCode) { + // https://livenet.xrpl.org/transactions/31ABD41ECAD459BCD008DBA4377047413AEE7A965517DB240016B66A3F4A97E1 + auto key = parse_hex("574e99f7946cfa2a6ca9368ca72fd37e42583cddb9ecc746aa4cb194ef4b2480"); + Proto::SigningInput input; + + input.mutable_op_trust_set()->mutable_limit_amount()->set_currency("524C555344000000000000000000000000000000"); + input.mutable_op_trust_set()->mutable_limit_amount()->set_value("1000000000"); + input.mutable_op_trust_set()->mutable_limit_amount()->set_issuer("rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De"); + input.set_fee(500); + input.set_sequence(93674950); + input.set_last_ledger_sequence(187349950); + input.set_account("rDgEGKXWkHHr1HYq2ETnNAs9MdV4R8Gyt"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12001422000000002405955dc6201b0b2abbbe63d6c38d7ea4c68000524c555344000000000000000000000000000000e5e961c6a025c9404aa7b662dd1df975be75d13e6840000000000001f47321039c77e9329017ced5f8673ebafcd29687a1fff181140c030062fa77865688fc5d74473045022100aa5f7ffc2e11008a3fe98173c66360937cd3a72cb0951aa1b46ba32675c36b2d02206bc02de3a609e5c4b9e1510a6431a7d7efc0fba4ab9586d6595b86047e46bac281140265c09d122fab2a261a80ee59f1f4cd8fba8cf8"); +} + +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, SignTokenPaymentNonStandardCurrencyCode) { + // https://livenet.xrpl.org/transactions/6A1229450BB795E450C4AFAA7B72B58962621C0B8760372634796B3941718BFB + auto key = parse_hex("574e99f7946cfa2a6ca9368ca72fd37e42583cddb9ecc746aa4cb194ef4b2480"); + Proto::SigningInput input; + + input.mutable_op_payment()->mutable_currency_amount()->set_currency("524C555344000000000000000000000000000000"); + input.mutable_op_payment()->mutable_currency_amount()->set_value("1"); + input.mutable_op_payment()->mutable_currency_amount()->set_issuer("rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De"); + input.mutable_op_payment()->set_destination("r4oPb529jpRA1tVTDARmBuZPYB2CJjKFac"); + input.set_fee(12); + input.set_sequence(93674951); + input.set_last_ledger_sequence(187349950); + input.set_account("rDgEGKXWkHHr1HYq2ETnNAs9MdV4R8Gyt"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12000022000000002405955dc7201b0b2abbbe61d4838d7ea4c68000524c555344000000000000000000000000000000e5e961c6a025c9404aa7b662dd1df975be75d13e68400000000000000c7321039c77e9329017ced5f8673ebafcd29687a1fff181140c030062fa77865688fc5d744630440220552e90f417c2cabe39368bb45cf7495ba6ebe395f259a6509c9f3a7296e76a0d02201b37dae0c4c77fa70a451cd4a61c10575c8b052c282c082a32c229e7624a05e381140265c09d122fab2a261a80ee59f1f4cd8fba8cf88314ef20a3d93b00cc729eec11a3058d3d1feb4465e0"); +} + +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()); + // Set `SELL_NFTOKEN_FLAG` flag. + input.set_flags(0x00000001); + + 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/TransactionCompilerTests.cpp b/tests/chains/XRP/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..0849aa99e20 --- /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, TWCurveSECP256k1); + 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.sign(TW::data(preImageHash), TWCurveSECP256k1); + // 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}, {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_signatures_count); + } +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/Zcash/AddressTests.cpp b/tests/chains/Zcash/AddressTests.cpp new file mode 100644 index 00000000000..a29a988ce7a --- /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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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/TWAnySignerTests.cpp b/tests/chains/Zcash/TWAnySignerTests.cpp new file mode 100644 index 00000000000..f6ca5dcf2c8 --- /dev/null +++ b/tests/chains/Zcash/TWAnySignerTests.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "TestUtilities.h" +#include "Zcash/Transaction.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/BitcoinV2.pb.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace TW::Zcash { + +// Tests the BitcoinV2 API through the legacy `SigningInput`. +// Successfully broadcasted: https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 +TEST(TWAnySignerZcash, SignSapplingV2) { + auto privateKey = parse_hex("a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559"); + auto txId = parse_hex("3a19dd44032dfed61bfca5ba5751aab8a107b30609cbd5d70dc5ef09885b6853"); + std::reverse(txId.begin(), txId.end()); + int64_t inAmount = 494'000; + int64_t outAmount = 488'000; + auto senderAddress = "t1gWVE2uyrET2CxSmCaBiKzmWxQdHhnvMSz"; + auto toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS"; + + BitcoinV2::Proto::SigningInput signing; + signing.add_private_keys(privateKey.data(), privateKey.size()); + + auto& chainInfo = *signing.mutable_chain_info(); + chainInfo.set_p2pkh_prefix(184); + chainInfo.set_p2sh_prefix(189); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::UseDefault); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); + + auto& extraData = *builder.mutable_zcash_extra_data(); + extraData.set_branch_id(SaplingBranchID.data(), SaplingBranchID.size()); + + auto& in = *builder.add_inputs(); + auto& inOutPoint = *in.mutable_out_point(); + inOutPoint.set_hash(txId.data(), txId.size()); + inOutPoint.set_vout(0); + in.set_value(inAmount); + in.set_receiver_address(senderAddress); + in.set_sighash_type(TWBitcoinSigHashTypeAll); + + auto& out = *builder.add_outputs(); + out.set_value(outAmount); + out.set_to_address(toAddress); + + Bitcoin::Proto::SigningInput legacy; + legacy.set_coin_type(TWCoinTypeZcash); + *legacy.mutable_signing_v2() = signing; + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeZcash); + + 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()), "0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000"); + EXPECT_EQ(hex(output.signing_result_v2().txid()), "ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256"); +} + +} 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..7126da30ce1 --- /dev/null +++ b/tests/chains/Zcash/TWZcashTransactionTests.cpp @@ -0,0 +1,285 @@ + +// 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"), TWCurveSECP256k1); + 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, Zip0317Fee) { + // tx on mainnet + // https://blockchair.com/zcash/transaction/092379d65d9b33be1322b2833e20cb573f87e49f73a3537c172354453dcee3a4 + + const auto myAddress = "t1Nx4n8MXhXVTZMY6Vx2zbxsCz5VstD9nuv"; + const auto myPrivateKey = parse_hex("5313c6cb5767fac88a303dab4f5d96ee55b547ec99da0db7a20694ac9e395668"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_coin_type(TWCoinTypeZcash); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_zip_0317(true); + input.set_to_address("t1S3JTzDWR7FzANsn3erXRPms2BfWVQgH9T"); + input.set_use_max_amount(true); + input.add_private_key(myPrivateKey.data(), myPrivateKey.size()); + + auto txHash = parse_hex("f8a8bdcd4b1b3c6b69b50ebbb26921c43583bb93f20e3ccf3c650791ef969b4e"); + std::reverse(txHash.begin(), txHash.end()); + auto redeemScript = Bitcoin::Script::lockScriptForAddress(myAddress, TWCoinTypeZcash).bytes; + + auto addUtxo = [&txHash, &redeemScript, &input](const uint32_t vout, const int64_t amount) { + auto utxo = input.add_utxo(); + utxo->mutable_out_point()->set_hash(txHash.data(), txHash.size()); + utxo->mutable_out_point()->set_index(vout); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + utxo->set_script(redeemScript.data(), redeemScript.size()); + utxo->set_amount(amount); + }; + + addUtxo(0, 7000); + addUtxo(1, 1'505'490); + addUtxo(2, 7100); + addUtxo(3, 7200); + addUtxo(4, 7300); + addUtxo(5, 7400); + addUtxo(6, 7500); + addUtxo(7, 7600); + addUtxo(8, 7700); + addUtxo(9, 7800); + addUtxo(10, 7900); + addUtxo(11, 8000); + addUtxo(12, 8001); + addUtxo(13, 8002); + addUtxo(14, 8003); + addUtxo(15, 8004); + + auto plan = Zcash::TransactionBuilder::plan(input); + plan.branchId = Data(Zcash::Nu6BranchID.begin(), Zcash::Nu6BranchID.end()); + *input.mutable_plan() = 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), "0400008085202f89104e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8000000006b4830450221008697d7c738af36b6c2009eee98ab8d10356168cdab1ad3499a993e55ecf5ab56022011762fd1b95abcc55b04a13b395f00d131d2588b29cbb892fa0438920f5bc151012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8010000006b483045022100eb066fc7ab4cbdd42e6e50479bc3e4a5717f0d2c29626831b649d86d8e204df40220333b886a0eb196055f22e19dc9f01c46c57258e91b150cd5587cda1b707a1056012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8020000006b483045022100a1d2744150254ae05942c42721d89e02d0c9992b75d7db2bce3ebfe8e2e6a0e902200abe593108cf1cdddeb02403c15dc087d2dd274c2f85a63bac2248ab2ce3ef34012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8030000006b483045022100f791d7d491a20b7ebd31e0465b9adb83e5994d0fa092c4c213d1a9d97ad2fb3b02207223f97c35cd3f482ff93bdae55a4d7c3087cffd790d689777a9a32271e835c7012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8040000006a47304402204f8fa75701453de79dde52936d2526c6bd31d98d45cbe481df25fcd482054620022056221c611c6af5c66bb302ebadabe76c158aa83c47b4927e90182e6fea0bb392012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8050000006b483045022100d28ed7ea432c2d122815be053c25a044e9d02a8dc5f52e12c58d7a833627a9a90220575fa325028e0abecc2be8c40db5fd8552337dc62d3acb9a8e919dd597927b81012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8060000006b48304502210082dc355620bb855e4fd04984054858376bb28d07f97b149ab49cb7ec6c42559c022005ce1af01f00d452afbc51b8a3c1f14e681f93552e94d66906a71f1ba1c00e3c012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8070000006b483045022100d06a9e04bc6be40913fda047ba19ed24f9a4a8cbd5e338994e22609d6a1a11b202207bf5fee15e9a8c1b17095f7f804d16ba02cba5071bda3383de3ee0a46d3b1dd5012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8080000006a4730440220617f682e60ff8f7fa4784b4d318891cdbac461a99f48087034064ed813d2063f022060cb338a8ee49898ec774d431d0867b5a15382be90c685f39fde4a41af8ff0a7012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f8090000006b4830450221008e4f66cb5c69d98cc9a4f1e895fe3c645d4640f4a5f7e8337c3beea34915ab170220320e8d14cd3dbd26eab1c41eca2146089a59dafde04334cd321554183e809417012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f80a0000006b483045022100c4bbecaecdf6a9eb4a776b4f99541659dc73b8f2c28937e34e7cb637b5105d8302200092a7ae0eee8b4925e8c207c057f43f705b94e468053d4028a785f4652bc2b1012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f80b0000006b4830450221008f2228ac57a30d07cbfed7b0d39977e563d23f4f4776451f76e8b401c618f0710220095a73c8bef932d1865e55656620d3071221be279afd66f0827e39ca4eaa26d3012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f80c0000006b483045022100a52c7692a09c308ac9cd87c85afeaa37d69c661b8f7b6cdf8c02876037359cb8022006a3da236a86466add64fa6a38655d2a2b6fba05b84e25fa2583210a435be858012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f80d0000006a47304402202ce1f193c23e0262fdf62cb74c1669fa7c9e9de5a801434df43c0dc69b1d6aa1022048641ab533f539a5185136a6b2d933944703fa83ddae233297b98d6f89845792012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f80e0000006b483045022100db80a6d02c5cc9c21e94868654be891102a4e664ceab29edbd6ebc9106fc27290220509ddb845a48c2f94f4ec7995d12b01305ecc98eb49dd5b26826f6e4bd1ceaec012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff4e9b96ef9107653ccf3c0ef293bb8335c42169b2bb0eb5696b3c1b4bcdbda8f80f0000006b483045022100e40aba96f9dcaafb1ce43acf2cfec44f3c2c59340c8d0fa3cc46c6249efb27ca0220184c20c35ffd585efbb9d36049bcf60670f0120968c435dde2333631b5e1b102012103b6ced6ffee0d78974da26d910c8b36781e8598019a3982a04286384452418405ffffffff01a07f1700000000001976a914599686197c40d39a8e6272355f206a9523fab00288ac00000000000000000000000000000000000000"); +} + +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..183b26e6233 --- /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"), TWCurveSECP256k1); + 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..c0f4595c24f --- /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"), TWCurveSECP256k1); + 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..5aa159115c0 --- /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"), TWCoinTypeCurve(TWCoinTypeZen)); + 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..26861fb8ca7 --- /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"), TWCurveSECP256k1); + 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..dec0e6f67fe --- /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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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 78% rename from tests/Bech32AddressTests.cpp rename to tests/common/Bech32AddressTests.cpp index ec29247288d..27a54f35d4e 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" @@ -101,36 +99,36 @@ TEST(Bech32Address, FromKeyHash) { TEST(Bech32Address, FromPublicKey) { { - auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")); + auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); - auto address = Bech32Address("bnb", HASHER_SHA2_RIPEMD, publicKey); + 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 privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); - auto address = Bech32Address("cosmos", HASHER_SHA2_RIPEMD, publicKey); + ASSERT_EQ(hex(publicKey.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); + auto address = Bech32Address("cosmos", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); } { - auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); + auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - auto address = Bech32Address("one", HASHER_SHA3K, publicKey); + auto address = Bech32Address("one", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); } { - auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - auto address = Bech32Address("io", HASHER_SHA3K, publicKey); + auto address = Bech32Address("io", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); } { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); - const auto address = Bech32Address("zil", HASHER_SHA2, publicKey); + ASSERT_EQ(hex(publicKey.bytes), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); + const auto address = Bech32Address("zil", Hash::HasherSha256, publicKey); ASSERT_EQ("zil", address.getHrp()); ASSERT_EQ("zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", address.string()); } @@ -138,37 +136,37 @@ TEST(Bech32Address, FromPublicKey) { // From same public key, but different hashes: different results TEST(Bech32Address, Hashes) { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); 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", HASHER_SHA2_RIPEMD, publicKey1); + const auto address1 = Bech32Address("hrp", Hash::HasherSha256ripemd, publicKey1); ASSERT_EQ("hrp186zwn9h0z9fyvwfqs4jl92cw3kexusm4xw6ptp", address1.string()); - const auto address2 = Bech32Address("hrp", HASHER_SHA2, publicKey1); + const auto address2 = Bech32Address("hrp", Hash::HasherSha256, publicKey1); ASSERT_EQ("hrp1j8xae6lggm8y63m3y2r7aefu797ze7mhgfetvu", address2.string()); auto publicKey2 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); ASSERT_EQ( "04b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d83c307736082c09f1f22328e0fbeab40ddd198cf0f70fcdaa1e5969ca400c098", - hex(publicKey2.bytes.begin(), publicKey2.bytes.end())); + hex(publicKey2.bytes)); - const auto address3 = Bech32Address("hrp", HASHER_SHA3K, publicKey2); + const auto address3 = Bech32Address("hrp", Hash::HasherKeccak256, publicKey2); ASSERT_EQ("hrp17hff3s97m5uxpjcdq3nzqxxatt8cmumnsf03su", address3.string()); } // From same public key, but different prefixes: different results (checksum) TEST(Bech32Address, Prefixes) { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); 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", HASHER_SHA2_RIPEMD, publicKey); + const auto address1 = Bech32Address("hrpone", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrpone186zwn9h0z9fyvwfqs4jl92cw3kexusm47das6p", address1.string()); - const auto address2 = Bech32Address("hrptwo", HASHER_SHA2_RIPEMD, publicKey); + const auto address2 = Bech32Address("hrptwo", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrptwo186zwn9h0z9fyvwfqs4jl92cw3kexusm4qzr8p7", address2.string()); - const auto address3 = Bech32Address("hrpthree", HASHER_SHA2_RIPEMD, publicKey); + const auto address3 = Bech32Address("hrpthree", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrpthree186zwn9h0z9fyvwfqs4jl92cw3kexusm4wuqkvd", address3.string()); } diff --git a/tests/common/Bech32Tests.cpp b/tests/common/Bech32Tests.cpp new file mode 100644 index 00000000000..c081fd9cf6c --- /dev/null +++ b/tests/common/Bech32Tests.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bech32.h" +#include "HexCoding.h" + +#include + +using namespace TW; + +struct DecodeTestData { + std::string encoded; + bool isValid; + bool isValidM; + std::string hrp; + std::string dataHex; +}; + +std::vector testData = { + /// valid + {"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", true, false, "bnb", "080301090f051414170f04160200111d0314131b1c1b1e041a080d091f1a1f06"}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true, false, "bc", "010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16"}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", false, true, "bc", "010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16"}, + /// invalid + {"bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2", false, false, "", ""}, // 1-char diff + + /// valid test vectors (BIP173) + {"A12UEL5L", true, false, "a", ""}, + {"a12uel5l", true, false, "a", ""}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true, false, "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio", ""}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true, false, "abcdef", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true, false, "1", "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true, false, "split", "18171918161c01100b1d0819171d130d10171d16191c01100b03191d1b1903031d130b190303190d181d01190303190d"}, + {"?1ezyfcl", true, false, "?", ""}, + + /// valid bech32m test vectors (BIP350) + {"A1LQFN3A", false, true, "a", ""}, + {"a1lqfn3a", false, true, "a", ""}, + {"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", false, true, "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber1", ""}, + {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", false, true, "abcdef", "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}, + {"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", false, true, "1", "1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f"}, + {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", false, true, "split", "18171918161c01100b1d0819171d130d10171d16191c01100b03191d1b1903031d130b190303190d181d01190303190d"}, + {"?1v759aa", false, true, "?", ""}, + + /// invalid test vectors (BIP173) + {"\x20""1nwldj5", false, false, "", ""}, // HRP character out of range + {"\x7F""1axkwrx", false, false, "", ""}, // HRP character out of range + {"\x80""1eym55h", false, false, "", ""}, // HRP character out of range + {"pzry9x0s0muk", false, false, "", ""}, // No separator character + {"1pzry9x0s0muk", false, false, "", ""}, // Empty HRP + {"x1b4n0q5v", false, false, "", ""}, // Invalid data character + {"lt1igcx5c0", false, false, "", ""}, // Invalid data character + {"li1dgmt3", false, false, "", ""}, // Too short checksum + {"de1lg7wt""\xFF", false, false, "", ""}, // Invalid character in checksum + {"A1G7SGD8", false, false, "", ""}, // checksum calculated with uppercase form of HRP + {"10a06t8", false, false, "", ""}, // empty HRP + {"1qzzfhee", false, false, "", ""}, // empty HRP + + /// invalid bech32m test vectors (BIP350) + {"\x20""1xj0phk", false, false, "", ""}, // HRP character out of range + {"\x7F""1g6xzxy", false, false, "", ""}, // HRP character out of range + {"\x80""1vctc34", false, false, "", ""}, // HRP character out of range + {"qyrz8wqd2c9m", false, false, "", ""}, // No separator character + {"1qyrz8wqd2c9m", false, false, "", ""}, // Empty HRP + {"y1b0jsk6g", false, false, "", ""}, // Invalid data character + {"lt1igcx5c0", false, false, "", ""}, // Invalid data character + {"in1muywd", false, false, "", ""}, // Too short checksum + {"mm1crxm3i", false, false, "", ""}, // Invalid character in checksum + {"au1s5cgom", false, false, "", ""}, // Invalid character in checksum + {"M1VUXWEZ", false, false, "", ""}, // checksum calculated with uppercase form of HRP + {"16plkw9", false, false, "", ""}, // empty HRP + {"1p2gdwpf", false, false, "", ""}, // empty HRP +}; + +TEST(Bech32, decode) { + for (auto& td: testData) { + ASSERT_FALSE(td.isValid && td.isValidM); // a string cannot be valid under both + + auto res = Bech32::decode(td.encoded); + if (!td.isValid && !td.isValidM) { + EXPECT_EQ(std::get<0>(res), ""); + EXPECT_EQ(std::get<1>(res).size(), 0ul); + } else { + if (td.isValid) { + EXPECT_EQ(std::get<2>(res), Bech32::ChecksumVariant::Bech32); + } else if (td.isValidM) { + EXPECT_EQ(std::get<2>(res), Bech32::ChecksumVariant::Bech32M); + } + EXPECT_EQ(std::get<0>(res), td.hrp) << "Wrong hrp for " << td.encoded; + EXPECT_EQ(hex(std::get<1>(res)), td.dataHex) << "Wrong data for " << td.encoded; + } + } +} + +TEST(Bech32, encode) { + for (auto& td: testData) { + if (!td.isValid) { + continue; + } + auto res = Bech32::encode(td.hrp, parse_hex(td.dataHex), Bech32::ChecksumVariant::Bech32); + std::string encodedLow = td.encoded; + std::transform(encodedLow.begin(), encodedLow.end(), encodedLow.begin(), [](unsigned char c){ return std::tolower(c); }); + EXPECT_EQ(res, encodedLow); + } +} + +TEST(Bech32, encodeM) { + for (auto& td: testData) { + if (!td.isValidM) { + continue; + } + auto res = Bech32::encode(td.hrp, parse_hex(td.dataHex), Bech32::ChecksumVariant::Bech32M); + std::string encodedLow = td.encoded; + std::transform(encodedLow.begin(), encodedLow.end(), encodedLow.begin(), [](unsigned char c){ return std::tolower(c); }); + EXPECT_EQ(res, encodedLow); + } +} 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..fd35b42420c --- /dev/null +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -0,0 +1,444 @@ +// 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: + case TWCoinTypeSonic: + // 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; + case TWCoinTypePactus: + EXPECT_EQ(address, "pc1rehvlc6tfn79z0zjqqaj8zas5j5h9c2fe59a4ff"); + break; + case TWCoinTypePolymesh: + EXPECT_EQ(address, "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F"); + 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 83% rename from tests/CoinAddressValidationTests.cpp rename to tests/common/CoinAddressValidationTests.cpp index 5f61802c9f9..b02fe64d635 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/common/CoinAddressValidationTests.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 "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) { @@ -203,8 +204,8 @@ TEST(Coin, validateAddressTron) { } TEST(Coin, validateAddressZcoin) { - EXPECT_TRUE(validateAddress(TWCoinTypeZcoin, "aHzpPjmY132KseS4nkiQTbDahTEXqesY89")); - EXPECT_FALSE(validateAddress(TWCoinTypeZcoin, "xHzpPjmY132KseS4nkiQTbDahTEXqesY89")); + EXPECT_TRUE(validateAddress(TWCoinTypeFiro, "aHzpPjmY132KseS4nkiQTbDahTEXqesY89")); + EXPECT_FALSE(validateAddress(TWCoinTypeFiro, "xHzpPjmY132KseS4nkiQTbDahTEXqesY89")); } TEST(Coin, validateAddressLitecoin) { @@ -288,22 +289,6 @@ TEST(Coin, validateAddressFIO) { EXPECT_FALSE(validateAddress(TWCoinTypeFIO, "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2")); } -TEST(Coin, validateAddressTON) { - EXPECT_TRUE(validateAddress(TWCoinTypeTON, "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZT")); - // wrong length - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZ")); // shorter - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdZTz")); // longer - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "E")); - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "")); - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "EQCBVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODdAA")); // different - // Raw format - EXPECT_TRUE(validateAddress(TWCoinTypeTON, "0:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - // no colon - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "0 8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d")); - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "0:0")); - EXPECT_FALSE(validateAddress(TWCoinTypeTON, "")); -} - TEST(Coin, validateAddressAlgorand) { EXPECT_TRUE(validateAddress(TWCoinTypeAlgorand, "ADIYK65L3XR5ODNNCUIQVEET455L56MRKJHRBX5GU4TZI2752QIWK4UL5A")); @@ -330,9 +315,17 @@ TEST(Coin, ValidateAddresBand) { EXPECT_FALSE(validateAddress(TWCoinTypeBandChain, "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc8000")); } +TEST(Coin, ValidateAddressBluzelle) { + EXPECT_TRUE(validateAddress(TWCoinTypeBluzelle, "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund")); + // wrong prefix + EXPECT_FALSE(validateAddress(TWCoinTypeBluzelle, "cosmos1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund")); + // wrong checksum + EXPECT_FALSE(validateAddress(TWCoinTypeBluzelle, "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmunx")); +} + TEST(Coin, ValidateAddresCardano) { // valid V3 address - EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe")); + EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); // valid V2 address EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); // valid V1 address @@ -382,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) { @@ -392,5 +385,41 @@ TEST(Coin, ValidateAddressOasis) { EXPECT_FALSE(validateAddress(TWCoinTypeOasis, "oasi1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmueweh")); } +TEST(Coin, ValidateAddresTHORChain) { + EXPECT_TRUE(validateAddress(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r")); + // wrong prefix + EXPECT_FALSE(validateAddress(TWCoinTypeTHORChain, "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp")); + // wrong checksum + EXPECT_FALSE(validateAddress(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2s")); +} + +TEST(Coin, ValidateAddressECash) { + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "ecash:qruxj7zq6yzpdx8dld0e9hfvt7u47zrw9gswqul42q")); + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2")); + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "qq07l6rr5lsdm3m80qxw80ku2ex0tj76vvft48qjaw")); + EXPECT_TRUE(validateAddress(TWCoinTypeECash, "qqslmu0jxk4st3ldjyuazfpf5thd6vlgfu395x2elz")); + + ASSERT_EQ(normalizeAddress(TWCoinTypeECash, "qqslmu0jxk4st3ldjyuazfpf5thd6vlgfu395x2elz"), "ecash:qqslmu0jxk4st3ldjyuazfpf5thd6vlgfu395x2elz"); + 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/common/DataTests.cpp b/tests/common/DataTests.cpp new file mode 100644 index 00000000000..9d0a573ecc7 --- /dev/null +++ b/tests/common/DataTests.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "HexCoding.h" + +#include + +using namespace std; +using namespace TW; + +TEST(DataTests, fromVector) { + const Data data = {1, 2, 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(), 4ul); + EXPECT_EQ(hex(data), "01020304"); +} + +TEST(DataTests, fromString) { + const Data data = TW::data(std::string("ABC")); + 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(), 3ul); + EXPECT_EQ(hex(data), "010203"); +} + +TEST(DataTests, padLeft) { + Data data = parse_hex("01020304"); + pad_left(data, 10); + EXPECT_EQ(data.size(), 10ul); + EXPECT_EQ(hex(data), "00000000000001020304"); +} + +TEST(DataTests, append) { + Data data1 = parse_hex("01020304"); + const Data data2 = parse_hex("aeaf"); + append(data1, data2); + 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(), 5ul); + EXPECT_EQ(hex(data1), "0102030405"); +} + +TEST(DataTests, subData) { + const Data data = parse_hex("0102030405060708090a"); + EXPECT_EQ(data.size(), 10ul); + + EXPECT_EQ(hex(subData(data, 2, 3)), "030405"); + EXPECT_EQ(hex(subData(data, 0, 10)), "0102030405060708090a"); + EXPECT_EQ(hex(subData(data, 3, 1)), "04"); + EXPECT_EQ(hex(subData(data, 3, 0)), ""); + EXPECT_EQ(hex(subData(data, 200, 3)), ""); // index too big + EXPECT_EQ(hex(subData(data, 2, 300)), "030405060708090a"); // length too big + EXPECT_EQ(hex(subData(data, 200, 300)), ""); // index & length too big + + EXPECT_EQ(hex(subData(data, 3)), "0405060708090a"); + EXPECT_EQ(hex(subData(data, 0)), "0102030405060708090a"); + EXPECT_EQ(hex(subData(data, 200)), ""); // index too big +} + +TEST(DataTests, hasPrefix) { + const Data data = parse_hex("0102030405060708090a"); + + const Data prefix11 = parse_hex("010203"); + EXPECT_TRUE(has_prefix(data, prefix11)); + const Data prefix12 = parse_hex("01"); + EXPECT_TRUE(has_prefix(data, prefix12)); + const Data prefix13 = parse_hex("0102030405060708090a"); + EXPECT_TRUE(has_prefix(data, prefix13)); + + const Data prefix21 = parse_hex("020304"); + EXPECT_FALSE(has_prefix(data, prefix21)); + const Data prefix22 = parse_hex("02"); + EXPECT_FALSE(has_prefix(data, prefix22)); + const Data prefix23 = parse_hex("bb"); + EXPECT_FALSE(has_prefix(data, prefix23)); +} 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/common/HDWallet/HDWalletInternalTests.cpp b/tests/common/HDWallet/HDWalletInternalTests.cpp new file mode 100644 index 00000000000..6b0554a65d3 --- /dev/null +++ b/tests/common/HDWallet/HDWalletInternalTests.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HDWallet.h" +#include "Data.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include "TestUtilities.h" + +#include +#include +#include + +namespace TW::HDWalletInternalTests { + +const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + +std::string nodeToHexString(const HDNode& node) { + std::string s; + s += std::to_string(node.depth); + s += "-" + std::to_string(node.child_num); + s += "--" + hex(data(node.chain_code, 32)); + s += "--" + hex(data(node.private_key, 32)); + s += "--" + hex(data(node.private_key_extension, 32)); + s += "--" + hex(data(node.public_key, 33)); + return s; +} + +Data publicKeyFromPrivateKey(const Data& privateKey) { + return PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; +} + +TEST(HDWalletInternal, SquareDerivationRoutes) { + /* + Test 'square' derivation routes, result should be the same. + Performing private derivation, then taking the public key yields the same as + taking the public key first, and performing public derivation. + This makes XPUB schemes possible. + + priv_node --priv_deriv.--> priv_child_node + + | | + get_pub get_pub + | | + v v + + pub_node ---pub_deriv.---> pub_key + + */ + + HDWallet wallet = HDWallet(mnemonic1, ""); + const auto derivationPath = DerivationPath("m/84'/0'/0'/1"); + const auto dpLastIndex = 2; + const auto ExpectedFinalPublicKey = "02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"; + + // getMasterNode + auto masterNode = HDNode(); + hdnode_from_seed(wallet.getSeed().data(), HDWallet<>::mSeedSize, SECP256K1_NAME, &masterNode); + + auto node0 = masterNode; + // getNode + for (auto& index : derivationPath.indices) { + hdnode_private_ckd(&node0, index.derivationIndex()); + } + EXPECT_EQ(nodeToHexString(node0), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + + { + // Route 1 step 1: private node derivation + auto node11 = node0; + EXPECT_EQ(hdnode_private_ckd(&node11, dpLastIndex), 1); + EXPECT_EQ(nodeToHexString(node11), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + + // Route 1 step 2: public key from private + const auto publicKey = publicKeyFromPrivateKey(data(node11.private_key, 32)); + EXPECT_EQ(hex(publicKey), ExpectedFinalPublicKey); + } + + { + // Route 2 step 1: public node from private (extended public key) + auto node21 = node0; + const auto pub21 = publicKeyFromPrivateKey(data(node21.private_key, 32)); + ::memcpy(node21.public_key, pub21.data(), 33); + EXPECT_EQ(nodeToHexString(node21), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"); + + // Route 2 step 2: public node derivation + auto node22 = node21; + EXPECT_EQ(hdnode_public_ckd(&node22, dpLastIndex), 1); + EXPECT_EQ(nodeToHexString(node22), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"); + const auto publicKey = PublicKey(data(node22.public_key, 33), TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey.bytes), ExpectedFinalPublicKey); + } +} + +TEST(HDWalletInternal, PrivateAndPublicCkdDerivation) { + /* + + PrivateKey1 ----> PrivateKey2 + + | | + v v + + PublicKey1 ----> PublicKey2 + + */ + + const auto PrivateKey1 = "55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625"; + const auto PrivateKey2 = "512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d"; + const auto PublicKey1 = "026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"; + const auto PublicKey2 = "02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"; + const auto ChainCode0 = "8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095"; + const auto dpIndex = 2; + const auto curve = get_curve_by_name(SECP256K1_NAME); + + { // PrivateKey1 -> PublicKey1 + EXPECT_EQ(hex(publicKeyFromPrivateKey(parse_hex(PrivateKey1))), PublicKey1); + } + { // PrivateKey2 -> PublicKey2 + EXPECT_EQ(hex(publicKeyFromPrivateKey(parse_hex(PrivateKey2))), PublicKey2); + } + { // PrivateKey1 -> PrivateKey2 + auto node = HDNode(); + node.depth = 4; + node.child_num = 1; + node.curve = curve; + ::memcpy(node.chain_code, parse_hex(ChainCode0).data(), 32); + ::memcpy(node.private_key, parse_hex(PrivateKey1).data(), 32); + EXPECT_EQ(nodeToHexString(node), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + + EXPECT_EQ(hdnode_private_ckd(&node, dpIndex), 1); + + EXPECT_EQ(nodeToHexString(node), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(data(node.private_key, 32)), PrivateKey2); + } + { // PublicKey1 -> PublicKey2 + auto node = HDNode(); + node.depth = 4; + node.child_num = 1; + node.curve = curve; + ::memcpy(node.chain_code, parse_hex(ChainCode0).data(), 32); + ::memcpy(node.public_key, parse_hex(PublicKey1).data(), 33); + EXPECT_EQ(nodeToHexString(node), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"); + + EXPECT_EQ(hdnode_public_ckd(&node, dpIndex), 1); + + EXPECT_EQ(nodeToHexString(node), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"); + EXPECT_EQ(hex(data(node.public_key, 33)), PublicKey2); + } +} + +} // namespace diff --git a/tests/common/HDWallet/HDWalletTests.cpp b/tests/common/HDWallet/HDWalletTests.cpp new file mode 100644 index 00000000000..b795abe3334 --- /dev/null +++ b/tests/common/HDWallet/HDWalletTests.cpp @@ -0,0 +1,657 @@ +// 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/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 "TrustWalletCore/TWEthereum.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 std::string ethAddress = "0xA4864D977b944315389d1765Ffa7E66F74eE8cD7"; + HDWallet wallet = HDWallet(mnemonic, ""); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress.c_str()).get(), STRING("starkex").get(), STRING("starkdeployement").get(), STRING("0").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); + 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, ""); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress).get(), STRING("starkex").get(), STRING("immutablex").get(), STRING("1").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); + 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, ""); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress).get(), STRING("starkex").get(), STRING("immutablex").get(), STRING("1").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); + 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, ""); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress).get(), STRING("starkex").get(), STRING("immutablex").get(), STRING("1").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); + 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/common/HDWallet/bip39_vectors.json b/tests/common/HDWallet/bip39_vectors.json new file mode 100644 index 00000000000..c15add0e28e --- /dev/null +++ b/tests/common/HDWallet/bip39_vectors.json @@ -0,0 +1,148 @@ +{ + "english": [ + [ + "00000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + "xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + "xprv9s21ZrQH143K2gA81bYFHqU68xz1cX2APaSq5tt6MFSLeXnCKV1RVUJt9FWNTbrrryem4ZckN8k4Ls1H6nwdvDTvnV7zEXs2HgPezuVccsq" + ], + [ + "80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + "xprv9s21ZrQH143K2shfP28KM3nr5Ap1SXjz8gc2rAqqMEynmjt6o1qboCDpxckqXavCwdnYds6yBHZGKHv7ef2eTXy461PXUjBFQg6PrwY4Gzq" + ], + [ + "ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + "xprv9s21ZrQH143K2V4oox4M8Zmhi2Fjx5XK4Lf7GKRvPSgydU3mjZuKGCTg7UPiBUD7ydVPvSLtg9hjp7MQTYsW67rZHAXeccqYqrsx8LcXnyd" + ], + [ + "000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + "xprv9s21ZrQH143K3mEDrypcZ2usWqFgzKB6jBBx9B6GfC7fu26X6hPRzVjzkqkPvDqp6g5eypdk6cyhGnBngbjeHTe4LsuLG1cCmKJka5SMkmU" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + "xprv9s21ZrQH143K3Lv9MZLj16np5GzLe7tDKQfVusBni7toqJGcnKRtHSxUwbKUyUWiwpK55g1DUSsw76TF1T93VT4gz4wt5RM23pkaQLnvBh7" + ], + [ + "808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + "xprv9s21ZrQH143K3VPCbxbUtpkh9pRG371UCLDz3BjceqP1jz7XZsQ5EnNkYAEkfeZp62cDNj13ZTEVG1TEro9sZ9grfRmcYWLBhCocViKEJae" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + "xprv9s21ZrQH143K36Ao5jHRVhFGDbLP6FCx8BEEmpru77ef3bmA928BxsqvVM27WnvvyfWywiFN8K6yToqMaGYfzS6Db1EHAXT5TuyCLBXUfdm" + ], + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", + "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", + "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU" + ], + [ + "8080808080808080808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", + "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", + "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB" + ], + [ + "9e885d952ad362caeb4efe34a8e91bd2", + "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", + "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028", + "xprv9s21ZrQH143K2oZ9stBYpoaZ2ktHj7jLz7iMqpgg1En8kKFTXJHsjxry1JbKH19YrDTicVwKPehFKTbmaxgVEc5TpHdS1aYhB2s9aFJBeJH" + ], + [ + "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", + "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", + "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac", + "xprv9s21ZrQH143K3uT8eQowUjsxrmsA9YUuQQK1RLqFufzybxD6DH6gPY7NjJ5G3EPHjsWDrs9iivSbmvjc9DQJbJGatfa9pv4MZ3wjr8qWPAK" + ], + [ + "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", + "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", + "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440", + "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk" + ], + [ + "c0ba5a8e914111210f2bd131f3d5e08d", + "scheme spot photo card baby mountain device kick cradle pact join borrow", + "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612", + "xprv9s21ZrQH143K3FperxDp8vFsFycKCRcJGAFmcV7umQmcnMZaLtZRt13QJDsoS5F6oYT6BB4sS6zmTmyQAEkJKxJ7yByDNtRe5asP2jFGhT6" + ], + [ + "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", + "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", + "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d", + "xprv9s21ZrQH143K3R1SfVZZLtVbXEB9ryVxmVtVMsMwmEyEvgXN6Q84LKkLRmf4ST6QrLeBm3jQsb9gx1uo23TS7vo3vAkZGZz71uuLCcywUkt" + ], + [ + "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", + "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", + "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d", + "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems" + ], + [ + "23db8160a31d3e0dca3688ed941adbf3", + "cat swing flag economy stadium alone churn speed unique patch report train", + "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5", + "xprv9s21ZrQH143K4G28omGMogEoYgDQuigBo8AFHAGDaJdqQ99QKMQ5J6fYTMfANTJy6xBmhvsNZ1CJzRZ64PWbnTFUn6CDV2FxoMDLXdk95DQ" + ], + [ + "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", + "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", + "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02", + "xprv9s21ZrQH143K3wtsvY8L2aZyxkiWULZH4vyQE5XkHTXkmx8gHo6RUEfH3Jyr6NwkJhvano7Xb2o6UqFKWHVo5scE31SGDCAUsgVhiUuUDyh" + ], + [ + "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", + "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", + "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d", + "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm" + ], + [ + "f30f8c1da665478f49b001d94c5fc452", + "vessel ladder alter error federal sibling chat ability sun glass valve picture", + "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f", + "xprv9s21ZrQH143K2QWV9Wn8Vvs6jbqfF1YbTCdURQW9dLFKDovpKaKrqS3SEWsXCu6ZNky9PSAENg6c9AQYHcg4PjopRGGKmdD313ZHszymnps" + ], + [ + "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", + "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", + "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88", + "xprv9s21ZrQH143K4aERa2bq7559eMCCEs2QmmqVjUuzfy5eAeDX4mqZffkYwpzGQRE2YEEeLVRoH4CSHxianrFaVnMN2RYaPUZJhJx8S5j6puX" + ], + [ + "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", + "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", + "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998", + "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS" + ] + ] +} \ No newline at end of file diff --git a/tests/common/HashTests.cpp b/tests/common/HashTests.cpp new file mode 100644 index 00000000000..f1c1229e377 --- /dev/null +++ b/tests/common/HashTests.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Hash.h" +#include "HexCoding.h" + +#include + +using namespace std; +using namespace TW; + +const string brownFox = "The quick brown fox jumps over the lazy dog"; +const string brownFoxDot = "The quick brown fox jumps over the lazy dog."; + +TEST(HashTests, Blake2b) { + auto content = string("Hello world"); + auto hashed = Hash::blake2b(content, 64); + auto result = hex(hashed); + + ASSERT_EQ(result, string("6ff843ba685842aa82031d3f53c48b66326df7639a63d128974c5c14f31a0f33343a8c65551134ed1ae0f2b0dd2bb495dc81039e3eeb0aa1bb0388bbeac29183")); +} + +TEST(HashTests, Blake2bPersonal) { + auto personal_string = string("MyApp Files Hash"); + auto personal_data = Data(personal_string.begin(), personal_string.end()); + auto content = string("the same content"); + auto hashed = Hash::blake2b(content, 32, personal_data); + auto result = hex(hashed); + + ASSERT_EQ(result, string("20d9cd024d4fb086aae819a1432dd2466de12947831b75c5a30cf2676095d3b4")); +} + +TEST(HashTests, Sha512_256) { + auto tests = { + make_tuple(string(""), string("c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a")), + make_tuple(brownFox, string("dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d")), + make_tuple(brownFoxDot, string("1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3")), + }; + for (auto& test : tests) { + auto hashed = Hash::sha512_256(get<0>(test)); + ASSERT_EQ(hex(hashed), get<1>(test)); + } +} + +TEST(HashTests, Sha1) { + auto tests = { + make_tuple(string(""), string("da39a3ee5e6b4b0d3255bfef95601890afd80709")), + make_tuple(brownFox, string("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")), + make_tuple(brownFoxDot, string("408d94384216f890ff7a0c3528e8bed1e0b01621")), + }; + for (auto& test: tests) { + const auto hash = Hash::sha1(TW::data(get<0>(test))); + EXPECT_EQ(hex(hash), get<1>(test)); + } +} + +TEST(HashTests, hmac256) { + const Data key = parse_hex("531cbfcf12a168faff61af28bf437377397b4bf435ee732cf4ac95761a651f14"); + const Data data = parse_hex("f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224d"); + const auto expectedHmac = "a7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab"; + const Data hmac = Hash::hmac256(key, data); + EXPECT_EQ(hex(hmac), expectedHmac); +} + +TEST(HashTests, allHashEnum) { + const auto tests = { + make_tuple(Hash::HasherSha1, "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"), + make_tuple(Hash::HasherSha256, "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"), + make_tuple(Hash::HasherSha512, "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"), + make_tuple(Hash::HasherSha512_256, "dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d"), + make_tuple(Hash::HasherKeccak256, "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15"), + make_tuple(Hash::HasherKeccak512, "d135bb84d0439dbac432247ee573a23ea7d3c9deb2a968eb31d47c4fb45f1ef4422d6c531b5b9bd6f449ebcc449ea94d0a8f05f62130fda612da53c79659f609"), + make_tuple(Hash::HasherSha3_256, "69070dda01975c8c120c3aada1b282394e7f032fa9cf32f4cb2259a0897dfc04"), + make_tuple(Hash::HasherSha3_512, "01dedd5de4ef14642445ba5f5b97c15e47b9ad931326e4b0727cd94cefc44fff23f07bf543139939b49128caf436dc1bdee54fcb24023a08d9403f9b4bf0d450"), + make_tuple(Hash::HasherRipemd, "37f332f68db77bd9d7edd4969571ad671cf9dd3b"), + make_tuple(Hash::HasherBlake256, "7576698ee9cad30173080678e5965916adbb11cb5245d386bf1ffda1cb26c9d7"), + make_tuple(Hash::HasherGroestl512, "badc1f70ccd69e0cf3760c3f93884289da84ec13c70b3d12a53a7a8a4a513f99715d46288f55e1dbf926e6d084a0538e4eebfc91cf2b21452921ccde9131718d"), + make_tuple(Hash::HasherSha256d, "6d37795021e544d82b41850edf7aabab9a0ebe274e54a519840c4666f35b3937"), + make_tuple(Hash::HasherSha256ripemd, "0e3397b4abc7a382b3ea2365883c3c7ca5f07600"), + make_tuple(Hash::HasherSha3_256ripemd, "e70a0c74dd1b0c0d5af3c7ccbbe4b488d1b474b5"), + make_tuple(Hash::HasherBlake256d, "4511ab8713d8d580cae73061345df903f603b99e7ec699ddae63c56eea200059"), + make_tuple(Hash::HasherBlake256ripemd, "b4b44de1e854f7f3c0520b654204163f75f704e5"), + make_tuple(Hash::HasherGroestl512d, "1209d229cfc9d7d6711369e2d7f369b0efc1459a9d407cbfc7daf4f54209347f2ee7e3e7522ba5d5ac4e7365445739919e23e2917baee10f23557f3d3fbc696d"), + }; + + for (auto& test: tests) { + const auto hashFunc = get<0>(test); + const auto expected = get<1>(test); + EXPECT_EQ(hex(Hash::hash(hashFunc, brownFox)), expected); + } +} + +// More tests in TWHashTests 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 80% rename from tests/Keystore/Data/missing-address.json rename to tests/common/Keystore/Data/missing-address.json index f610e291e4a..46fd0c11005 100644 --- a/tests/Keystore/Data/missing-address.json +++ b/tests/common/Keystore/Data/missing-address.json @@ -1,9 +1,9 @@ { - "id": "629aad29-0b22-488e-a0e7-b4219d4f311c", + "id": "d9b1b9ef-d8b6-40e0-95f8-dd9e0a323c13", "crypto": { - "ciphertext": "64b5b416bb2bef882eb7cc63ed92c064e53c818ec46351e07ac140e5ba871596f1595fe6cad8333147fe68c031ba001b79b64dd1edd513043134217b7ffe1903ca23b1fbe823671827e3b2dff69bbd448d9cb79a3321ec8801f2a995", + "ciphertext": "3f6401e478074fc9c50a69dd88ea21baca70dd8064d8590b64f64b64d493e6e50bb6ff5ffc6aabcaac18c4aad25f29c53fe1029f8d6fa4ed24fc99938f27e38bea0b0cd7f8215f38d2526c655bff0b8f1638e948d8c1b9bdaa95ab0b", "cipherparams": { - "iv": "7aaf7eb6f4b0e7d995e8eac67e4d52eb" + "iv": "09246e7f7af92374eda0237da20c6696" }, "kdf": "scrypt", "kdfparams": { @@ -11,9 +11,9 @@ "p": 6, "n": 4096, "dklen": 32, - "salt": "80132842c6cde8f9d04582932ef92c3cad3ba6b41e1296ef681692372886db86" + "salt": "9cf4521a3543f0e116a86f188572f295a99081fd2e4143129cb5ee8760bec367" }, - "mac": "01816d0a5c31cd03b644f2d756ac8167c2498808040cbace8c35c46dcf06b7a1", + "mac": "67a8bf187bdeec076ac1e3647914e20b1dcbb15a5cb4643e6047fc2a07694055", "cipher": "aes-128-ctr" }, "type": "mnemonic", 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/common/Keystore/DerivationPathTests.cpp b/tests/common/Keystore/DerivationPathTests.cpp new file mode 100644 index 00000000000..3bdb3734f4d --- /dev/null +++ b/tests/common/Keystore/DerivationPathTests.cpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "DerivationPath.h" +#include + +#include + +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)); +} + +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.purpose(), 44); + ASSERT_EQ(path.coin(), 60ul); + ASSERT_EQ(path.account(), 0ul); + ASSERT_EQ(path.change(), 0ul); + ASSERT_EQ(path.address(), 0ul); +} + +TEST(DerivationPath, InitInvalid) { + ASSERT_THROW(DerivationPath("a/b/c"), std::invalid_argument); + ASSERT_THROW(DerivationPath("m/44'/60''/"), std::invalid_argument); +} + +TEST(DerivationPath, IndexOutOfBounds) { + DerivationPath path; + + EXPECT_EQ(path.indices.size(), 0ul); + + EXPECT_EQ(path.purpose(), TWPurposeBIP44); + EXPECT_EQ(path.coin(), TWCoinTypeBitcoin); + 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)); + ASSERT_NO_THROW(path.setAccount(0)); + ASSERT_NO_THROW(path.setChange(0)); + ASSERT_NO_THROW(path.setAddress(0)); +} + +TEST(DerivationPath, String) { + const auto path = DerivationPath("m/44'/60'/0'/0/0"); + ASSERT_EQ(path.string(), "m/44'/60'/0'/0/0"); +} + +TEST(DerivationPath, Equal) { + const auto path1 = DerivationPath("m/44'/60'/0'/0/0"); + const auto path2 = DerivationPath("44'/60'/0'/0/0"); + ASSERT_EQ(path1, path2); +} + +TEST(Derivation, derivationPath) { + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana).string(), "m/44'/501'/0'"); +} + +TEST(Derivation, alternativeDerivation) { + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationDefault).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationBitcoinSegwit).string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeBitcoin, TWDerivationBitcoinSegwit)), "segwit"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationBitcoinLegacy).string(), "m/44'/0'/0'/0/0"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeBitcoin, TWDerivationBitcoinLegacy)), "legacy"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeBitcoin, TWDerivationBitcoinTaproot).string(), "m/86'/0'/0'/0/0"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeBitcoin, TWDerivationBitcoinTaproot)), "taproot"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypeLitecoin, TWDerivationDefault).string(), "m/84'/2'/0'/0/0"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeLitecoin, TWDerivationLitecoinLegacy).string(), "m/44'/2'/0'/0/0"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana).string(), "m/44'/501'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationDefault).string(), "m/44'/501'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationSolanaSolana).string(), "m/44'/501'/0'/0'"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeSolana, TWDerivationSolanaSolana)), "solana"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus).string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus, TWDerivationPactusMainnet).string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus, TWDerivationPactusTestnet).string(), "m/44'/21777'/3'/0'"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypePactus, TWDerivationPactusMainnet)), "mainnet"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypePactus, TWDerivationPactusTestnet)), "testnet"); +} + +} // namespace TW diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp new file mode 100644 index 00000000000..12c7fc7a740 --- /dev/null +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -0,0 +1,749 @@ +// 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); + // Salt is 32 bytes, encoded as hex. + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); +} + +TEST(StoredKey, CreateWithMnemonicInvalid) { + try { + auto key = StoredKey::createWithMnemonic("name", gPassword, "_THIS_IS_NOT_A_VALID_MNEMONIC_", TWStoredKeyEncryptionLevelDefault); + } catch (std::invalid_argument&) { + // expected 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); + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); + + // 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); + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); + + // 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); + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateEncryptionParametersRandomSalt) { + const auto key1 = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelStandard); + const auto salt1 = parse_hex(key1.json()["crypto"]["kdfparams"]["salt"]); + + const auto key2 = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelStandard); + const auto salt2 = parse_hex(key2.json()["crypto"]["kdfparams"]["salt"]); + + EXPECT_NE(salt1, salt2) << "salt must be random on every StoredKey creation"; +} + +TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts from the same wallet + 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"); + } + + { // Create Pactus Accounts + const auto coin = TWCoinTypePactus; + + const auto pactusMainnet = key.account(coin, TWDerivationPactusMainnet, wallet); + const auto pactusTestnet = key.account(coin, TWDerivationPactusTestnet, wallet); + + const auto expectedMainnetAddr = "pc1rzuswvfwde5hleqfemvpz4swlh6uud6nkukumdu"; + const auto expectedTestnetAddr = "tpc1rxs9tperv58gvfwpn0vj5na7vrcffml40j2v6r9"; + + EXPECT_EQ(pactusMainnet.address, expectedMainnetAddr); + EXPECT_EQ(pactusTestnet.address, expectedTestnetAddr); + + EXPECT_EQ(pactusMainnet.derivationPath.string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(pactusTestnet.derivationPath.string(), "m/44'/21777'/3'/0'"); + + expectedAccounts += 2; + EXPECT_EQ(key.accounts.size(), expectedAccounts); + + EXPECT_EQ(key.account(coin)->address, expectedMainnetAddr); + EXPECT_EQ(key.account(coin, TWDerivationPactusMainnet, wallet).address, expectedMainnetAddr); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedMainnetAddr); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedTestnetAddr); + } +} + +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 91% rename from tests/MnemonicTests.cpp rename to tests/common/MnemonicTests.cpp index bd9737eaa0f..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" @@ -37,6 +35,10 @@ std::vector InvalidInput = { " credit expect life fade cover suit response wash pear what skull force ", // upper "CREDIT expect life fade cover suit response wash pear what skull force", + // back is invalid word + "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back", + // Spanish + "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut", }; TEST(Mnemonic, isValid) { @@ -59,6 +61,7 @@ TEST(Mnemonic, isValidWord) { EXPECT_FALSE(Mnemonic::isValidWord("hybridous")); EXPECT_FALSE(Mnemonic::isValidWord("CREDIT")); EXPECT_FALSE(Mnemonic::isValidWord("credit ")); + EXPECT_FALSE(Mnemonic::isValidWord("back")); } TEST(Mnemonic, suggest) { 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..fccfebcff6f --- /dev/null +++ b/tests/common/PrivateKeyTests.cpp @@ -0,0 +1,375 @@ +// 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, TWCurveSECP256k1); + 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"); + { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_EQ( + "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", + hex(publicKey.bytes)); + } + { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ( + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", + hex(publicKey.bytes)); + } + { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ( + "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", + hex(publicKey.bytes)); + } + { + const auto privateKey = PrivateKey(privKeyData, TWCurveNIST256p1); + 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); + } +} + +TEST(PrivateKey, SignWithDifferentCurveWorks) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + // Using the deprecated constructor without specifying a curve + auto privateKey = PrivateKey(privKeyData); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + + // Should be able to sign with any curve when using the normal constructor + try { + auto signatureSECP256k1 = privateKey.sign(hash, TWCurveSECP256k1); + EXPECT_FALSE(signatureSECP256k1.empty()); + EXPECT_EQ(signatureSECP256k1.size(), 65ul); + + auto signatureNIST256p1 = privateKey.sign(hash, TWCurveNIST256p1); + EXPECT_FALSE(signatureNIST256p1.empty()); + EXPECT_EQ(signatureNIST256p1.size(), 65ul); + + // Verify the signatures are different for different curves + EXPECT_NE(hex(signatureSECP256k1), hex(signatureNIST256p1)); + } catch (const std::exception& e) { + FAIL() << "Unexpected exception was thrown: " << e.what(); + } +} + +TEST(PrivateKey, SignWithDifferentCurveThrows) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + // Using the constructor that specifies a curve + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + + // Should throw an exception if signing with a different curve + try { + auto _ = privateKey.sign(hash, TWCurveNIST256p1); + FAIL() << "Expected exception was not thrown"; + } catch (const std::invalid_argument& e) { + EXPECT_STREQ("Specified curve is different from the curve of the private key", e.what()); + } + + // Check that signing with the same curve works fine + try { + auto signature = privateKey.sign(hash, TWCurveSECP256k1); + EXPECT_EQ(signature.size(), 65ul); + EXPECT_TRUE(!signature.empty()); + } catch (const std::exception& e) { + FAIL() << "Unexpected exception was thrown: " << e.what(); + } +} + +} // 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..d69e1bc7952 --- /dev/null +++ b/tests/common/PublicKeyTests.cpp @@ -0,0 +1,368 @@ +// 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, TWCurveSECP256k1); + 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), TWCurveED25519Blake2bNano).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, TWCurveSECP256k1); + 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, TWCurveNIST256p1); + 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, TWCurveED25519); + 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 char* message = "Hello"; + const Data messageData = TW::data(message); + const Data digest = Hash::sha256(messageData); + + { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto signature = privateKey.sign(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f01"); + } + { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519); + const auto signature = privateKey.sign(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); + } + { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519Blake2bNano); + const auto signature = privateKey.sign(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); + } + { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveNIST256p1); + const auto signature = privateKey.sign(digest); + 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"), TWCurveSECP256k1); + + 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"), TWCurveED25519ExtendedCardano); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + + const auto message = TW::data("Hello"); + const auto digest = Hash::sha256(message); + const auto signature = privateKey.sign(digest); + const auto valid = publicKey.verify(signature, digest); + + EXPECT_TRUE(valid); +} + +TEST(PublicKeyTests, VerifySchnorr) { + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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"), TWCurveSECP256k1); + 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); + 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"), TWCurveSECP256k1); + 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: "< +#include + +#include + +namespace TW { + +// Test data: uint256_t, hex binary representation, string representation +const std::vector> testData = { + {0, "00", "0"}, + {1, "01", "1"}, + {7, "07", "7"}, + {100, "64", "100"}, + {255, "ff", "255"}, + {256, "0100", "256"}, + {65535, "ffff", "65535"}, + {1'000'000, "0f4240", "1000000"}, + { + load(parse_hex("123456789abcdef0")), + "123456789abcdef0", + "1311768467463790320" + }, + { + load(parse_hex("123456789abcdef123456789abcdef")), + "123456789abcdef123456789abcdef", + "94522879700260683142460330790866415" + }, + { + load(parse_hex("1000000000000000000000000000000000000000")), + "1000000000000000000000000000000000000000", + "91343852333181432387730302044767688728495783936" + }, +}; + +TEST(Uint256, storeLoadBasic) { + EXPECT_EQ(hex(store(uint256_t(3))), "03"); + EXPECT_EQ(load(parse_hex("03")), uint256_t(3)); +} + +TEST(Uint256, storeLoadStore) { + for(const auto& testi: testData) { + const uint256_t dataI = std::get<0>(testi); + const char* dataD = std::get<1>(testi); + + const uint256_t i = dataI; + + const Data d = store(i); + EXPECT_EQ(hex(d), dataD); + + const uint256_t i2 = load(d); + EXPECT_EQ(i2, dataI); + + const Data d2 = store(i2); + EXPECT_EQ(hex(d2), dataD); + } +} + +TEST(Uint256, toString) { + for(const auto& testi: testData) { + const uint256_t dataI = std::get<0>(testi); + const char* dataS = std::get<2>(testi); + + EXPECT_EQ(toString(dataI), dataS); + } +} + +TEST(Uint256, storePadding) { + EXPECT_EQ(hex(store(uint256_t(3))), "03"); + EXPECT_EQ(hex(store(uint256_t(1'000'000))), "0f4240"); + + EXPECT_EQ(hex(store(uint256_t(3), 4)), "00000003"); + EXPECT_EQ(hex(store(uint256_t(3), 32)), "0000000000000000000000000000000000000000000000000000000000000003"); + EXPECT_EQ(hex(store(uint256_t(3), 64)), "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"); + + EXPECT_EQ(hex(store(uint256_t(1'000'000), 32)), "00000000000000000000000000000000000000000000000000000000000f4240"); + EXPECT_EQ(hex(store(uint256_t(1'000'000), 2)), "0f4240"); +} + +TEST(Uint256, loadWithLeadingZero) { + EXPECT_EQ(load(parse_hex("0f4240")), uint256_t(1'000'000)); + EXPECT_EQ(load(parse_hex("000f4240")), uint256_t(1'000'000)); + EXPECT_EQ(load(parse_hex("000000000f4240")), uint256_t(1'000'000)); + EXPECT_EQ(load(parse_hex("0000000000000000000000000000000000000000000000000000000000000003")), uint256_t(3)); + EXPECT_EQ(load(parse_hex("00000000000000000000000000000000000000000000000000000000000f4240")), uint256_t(1'000'000)); +} + +TEST(Uint256, LoadEmpty) { + EXPECT_EQ(load(parse_hex("")), uint256_t(0)); + EXPECT_EQ(load(parse_hex("00")), uint256_t(0)); + EXPECT_EQ(load(parse_hex("0000")), uint256_t(0)); +} + +TEST(Uint256, loadStringProtobuf) { + const Data data = parse_hex("03"); + const std::string str = std::string(reinterpret_cast(data.data()), data.size()); + EXPECT_EQ(load(str), uint256_t(3)); +} + +} // namespace diff --git a/tests/WalletConsoleTests.cpp b/tests/common/WalletConsoleTests.cpp similarity index 92% rename from tests/WalletConsoleTests.cpp rename to tests/common/WalletConsoleTests.cpp index 3241f2f78a3..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 eth"); + 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 new file mode 100644 index 00000000000..afc84a58498 --- /dev/null +++ b/tests/interface/TWAccountTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include + +#include + +TEST(TWAccount, Create) { + const auto addressAdd = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; + const auto coin = TWCoinTypeBitcoin; + const auto derivationPath = "m/84'/0'/0'/0/0"; + const auto extPubKeyAdd = "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"; + const auto pubKey = "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"; + + const auto account = WRAP( + TWAccount, + TWAccountCreate( + WRAPS(TWStringCreateWithUTF8Bytes(addressAdd)).get(), + coin, + TWDerivationDefault, + WRAPS(TWStringCreateWithUTF8Bytes(derivationPath)).get(), + WRAPS(TWStringCreateWithUTF8Bytes(pubKey)).get(), + WRAPS(TWStringCreateWithUTF8Bytes(extPubKeyAdd)).get() + ) + ); + + assertStringsEqual(WRAPS(TWAccountAddress(account.get())), addressAdd); + EXPECT_EQ(coin, TWAccountCoin(account.get())); + EXPECT_EQ(TWAccountDerivation(account.get()), TWDerivationDefault); + assertStringsEqual(WRAPS(TWAccountDerivationPath(account.get())), derivationPath); + assertStringsEqual(WRAPS(TWAccountExtendedPublicKey(account.get())), extPubKeyAdd); + assertStringsEqual(WRAPS(TWAccountPublicKey(account.get())), pubKey); +} diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index ab90a46891c..212a1055c07 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"); @@ -32,6 +31,20 @@ TEST(AnyAddress, Data) { auto keyHash = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(keyHash, "4e5b2e1dc63f6b91cb6cd759936495434c7e972f"); } + // smartBCH + { + auto string = STRING("0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeSmartBitcoinCash)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4e5b2e1dc63f6b91cb6cd759936495434c7e972f"); + } + // KuCoin Community Chain + { + auto string = STRING("0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeKuCoinCommunityChain)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4e5b2e1dc63f6b91cb6cd759936495434c7e972f"); + } // bnb address key hash { auto string = STRING("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5"); @@ -39,14 +52,21 @@ TEST(AnyAddress, Data) { auto keyHash = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(keyHash, "bffe47abfaede50419c577f1074fee6dd1535cd1"); } - // segwit witness program + // bitcoin segwit witness program { auto string = STRING("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoin)); auto witness = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(witness, "751e76e8199196d454941c45d1b3a323f1433bd6"); } - // cashaddr + // bitcoin taproot (segwit v1, bech32m) + { + auto string = STRING("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoin)); + auto witness = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(witness, "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"); + } + // bitcoincashaddr { auto string = STRING("bitcoincash:qzxf0wl63ahx6jsxu8uuldcw7n5aatwppvnteraqaw"); auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinCash)); @@ -107,10 +127,10 @@ TEST(AnyAddress, Data) { } // cardano { - auto string = STRING("addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j"); + auto string = STRING("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCardano)); auto pubkey = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(pubkey, "0104204dc3393959139f00bc88d33d4b2582fecc237238f2269fc094b286acdb892295206d0b99fdb1cc851fbc8dbcb7d6710fde28df46360c259d482db679c24c4e50b2"); + assertHexEqual(pubkey, "01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); } // neo { @@ -119,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"); } @@ -133,4 +153,89 @@ TEST(AnyAddress, Data) { auto pubkey = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(pubkey, "3b83b07cab54824a59c3d3f2e203a7cd913b7fcdc4439595983e2402c2cf791d"); } + // ecashaddr + { + auto string = STRING("ecash:qzxf0wl63ahx6jsxu8uuldcw7n5aatwppv2xdgx6me"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeECash)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8c97bbfa8f6e6d4a06e1f9cfb70ef4e9deadc10b"); + } + // solana + { + auto string = STRING("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeSolana)); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + 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, createFromPubKeyDerivationBitcoin) { + 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, createFromPubKeyDerivationPactus) { + constexpr auto pubkey = "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa"; + 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(), TWPublicKeyTypeED25519)); + + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationDefault)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationPactusMainnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg"); + } +} + +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/TWBech32Tests.cpp b/tests/interface/TWBech32Tests.cpp new file mode 100644 index 00000000000..00c1b3b6d98 --- /dev/null +++ b/tests/interface/TWBech32Tests.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include + +#include + +TEST(TWBech32, Encode) { + const auto hrp = STRING("abcdef"); + const auto data = DATA("00443214c74254b635cf84653a56d7c675be77df"); + const auto result = WRAPS(TWBech32Encode(hrp.get(), data.get())); + assertStringsEqual(result, "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"); +} + +TEST(TWBech32, Decode) { + const auto input = STRING("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"); + const auto result = WRAPD(TWBech32Decode(input.get())); + assertHexEqual(result, "00443214c74254b635cf84653a56d7c675be77df"); +} + +TEST(TWBech32, Decode_WrongChecksumVariant) { + // This is a Bech32m variant, not Bech32 variant. So it should fail using Bech32 decoder. + const auto input = STRING("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"); + const auto result = WRAPD(TWBech32Decode(input.get())); + ASSERT_EQ(result.get(), nullptr); +} + +TEST(TWBech32, EncodeM) { + const auto hrp = STRING("abcdef"); + const auto data = DATA("ffbbcdeb38bdab49ca307b9ac5a928398a418820"); + const auto result = WRAPS(TWBech32EncodeM(hrp.get(), data.get())); + assertStringsEqual(result, "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"); +} + +TEST(TWBech32, DecodeM) { + const auto input = STRING("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"); + auto result = WRAPD(TWBech32DecodeM(input.get())); + assertHexEqual(result, "ffbbcdeb38bdab49ca307b9ac5a928398a418820"); +} + +TEST(TWBech32, DecodeM_WrongChecksumVariant) { + // This is a Bech32 variant, not Bech32m variant. So it should fail using Bech32M decoder. + const auto input = STRING("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"); + const auto result = WRAPD(TWBech32DecodeM(input.get())); + ASSERT_EQ(result.get(), nullptr); +} diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..8d19ec12f86 --- /dev/null +++ b/tests/interface/TWCoinTypeTests.cpp @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include + +#include + +TEST(TWCoinType, TWPurpose) { + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeBitcoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBitcoinCash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBinance)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCosmos)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeDigiByte)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeLitecoin)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeGroestlcoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeIoTeX)); + ASSERT_EQ(TWPurposeBIP84, TWCoinTypePurpose(TWCoinTypeViacoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeQtum)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeZilliqa)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTerra)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeMonacoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeKava)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBandChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBluzelle)); + ASSERT_EQ(TWPurposeBIP1852, TWCoinTypePurpose(TWCoinTypeCardano)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeMultiversX)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOasis)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTHORChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCryptoOrg)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOsmosis)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeECash)); + + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeAion)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCallisto)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeDash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeDecred)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeDogecoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeEOS)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeEthereum)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeEthereumClassic)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeGoChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeICON)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeKin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNULS)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNano)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNimiq)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOntology)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypePOANetwork)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeXRP)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeStellar)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTezos)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTheta)); + 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)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeZcash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeFiro)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeZelcash)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeRavencoin)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWaves)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNEO)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNebl)); +} + +TEST(TWCoinType, TWHDVersion) { + ASSERT_EQ(TWHDVersionZPUB, TWCoinTypeXpubVersion(TWCoinTypeBitcoin)); + ASSERT_EQ(TWHDVersionZPRV, TWCoinTypeXprvVersion(TWCoinTypeBitcoin)); + + ASSERT_EQ(TWHDVersionXPUB, TWCoinTypeXpubVersion(TWCoinTypeBitcoinCash)); + ASSERT_EQ(TWHDVersionXPRV, TWCoinTypeXprvVersion(TWCoinTypeBitcoinCash)); + + ASSERT_EQ(TWHDVersionDPUB, TWCoinTypeXpubVersion(TWCoinTypeDecred)); + ASSERT_EQ(TWHDVersionDPRV, TWCoinTypeXprvVersion(TWCoinTypeDecred)); + + ASSERT_EQ(TWHDVersionDGUB, TWCoinTypeXpubVersion(TWCoinTypeDogecoin)); + ASSERT_EQ(TWHDVersionDGPV, TWCoinTypeXprvVersion(TWCoinTypeDogecoin)); +} + +TEST(TWCoinType, TWPublicKeyType) { + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBitcoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBitcoinCash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBinance)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeCosmos)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDigiByte)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeLitecoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeGroestlcoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeIoTeX)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeViacoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeQtum)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZilliqa)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeTerra)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeMonacoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeKava)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBandChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBluzelle)); + 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)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeOsmosis)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeECash)); + + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeAion)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeCallisto)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDecred)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeDogecoin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeEOS)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeEthereum)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeEthereumClassic)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeGoChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeICON)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeKin)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeNULS)); + ASSERT_EQ(TWPublicKeyTypeED25519Blake2b, TWCoinTypePublicKeyType(TWCoinTypeNano)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeNimiq)); + ASSERT_EQ(TWPublicKeyTypeNIST256p1, TWCoinTypePublicKeyType(TWCoinTypeOntology)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypePOANetwork)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeXRP)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeStellar)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeTezos)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTheta)); + 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)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZcash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeFiro)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZelcash)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeRavencoin)); + ASSERT_EQ(TWPublicKeyTypeCURVE25519, TWCoinTypePublicKeyType(TWCoinTypeWaves)); + ASSERT_EQ(TWPublicKeyTypeNIST256p1, TWCoinTypePublicKeyType(TWCoinTypeNEO)); + 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); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathPactus) { + auto res = TWCoinTypeDerivationPath(TWCoinTypePactus); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21888'/3'/0'"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationPactusMainnet) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypePactus, TWDerivationPactusMainnet); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21888'/3'/0'"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationPactusTestnet) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypePactus, TWDerivationPactusTestnet); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21777'/3'/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 new file mode 100644 index 00000000000..670d50edeba --- /dev/null +++ b/tests/interface/TWDataVectorTests.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" +#include "TestUtilities.h" + +#include + +using namespace TW; + + +TEST(TWDataVector, CreateDelete) { + auto vec = TWDataVectorCreate(); + + ASSERT_TRUE(vec != nullptr); + EXPECT_EQ(TWDataVectorSize(vec), 0ul); + + TWDataVectorDelete(vec); +} + +TEST(TWDataVector, CreateWrapAutoDelete) { + auto vec = WRAP(TWDataVector, TWDataVectorCreate()); + + ASSERT_TRUE(vec.get() != nullptr); + EXPECT_EQ(TWDataVectorSize(vec.get()), 0ul); +} + +TEST(TWDataVector, CreateWithData) { + const auto elem1d = parse_hex("deadbeef"); + const auto elem1 = WRAPD(TWDataCreateWithBytes(elem1d.data(), elem1d.size())); + const auto vec = WRAP(TWDataVector, TWDataVectorCreateWithData(elem1.get())); + + ASSERT_TRUE(vec.get() != nullptr); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); + + const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); + EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); +} + +TEST(TWDataVector, Add) { + const auto vec = WRAP(TWDataVector, TWDataVectorCreate()); + + ASSERT_TRUE(vec.get() != nullptr); + 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()), 1ul); + const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); + EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); + + const auto elem2d = parse_hex("0202"); + const auto elem2 = WRAPD(TWDataCreateWithBytes(elem2d.data(), elem2d.size())); + TWDataVectorAdd(vec.get(), elem2.get()); + + ASSERT_EQ(TWDataVectorSize(vec.get()), 2ul); + const auto readElem2 = WRAPD(TWDataVectorGet(vec.get(), 1)); + EXPECT_EQ(hex(*static_cast(readElem2.get())), "0202"); +} + +TEST(TWDataVector, Get) { + const auto elem1d = parse_hex("deadbeef"); + const auto elem1 = WRAPD(TWDataCreateWithBytes(elem1d.data(), elem1d.size())); + const auto vec = WRAP(TWDataVector, TWDataVectorCreateWithData(elem1.get())); + + ASSERT_TRUE(vec.get() != nullptr); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); + + { // Get element + const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); + EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); + } + { // Get with bad index + const auto readElem = TWDataVectorGet(vec.get(), 666); + EXPECT_EQ(readElem, nullptr); + } +} 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 cd46abac290..cff8a206c30 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -1,19 +1,24 @@ -// 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" #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include #include "HexCoding.h" @@ -21,10 +26,13 @@ #include #include +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 gWords = STRING(wordsStr); +const auto gPassphrase = STRING("TREZOR"); const auto seedHex = "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"; -const auto passphrase = STRING("TREZOR"); +const auto entropyHex = "ba5821e8c356c05ba5f025d9532fe0f21f65d594"; inline void assertSeedEq(const std::shared_ptr& wallet, const char* expected) { @@ -37,35 +45,57 @@ inline void assertMnemonicEq(const std::shared_ptr& wallet, const ch assertStringsEqual(mnemonic, expected); } -TEST(HDWallet, Mnemonic) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); - assertSeedEq(wallet, seedHex); - assertMnemonicEq(wallet, wordsStr); +inline void assertEntropyEq(const std::shared_ptr& wallet, const char* expected) { + const auto entropy = WRAPD(TWHDWalletEntropy(wallet.get())); + assertHexEqual(entropy, expected); } -TEST(HDWallet, Seed) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithData(DATA("ba5821e8c356c05ba5f025d9532fe0f21f65d594").get(), passphrase.get())); +TEST(HDWallet, CreateFromMnemonic) { + 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(), gPassphrase.get())); assertMnemonicEq(wallet, wordsStr); + assertSeedEq(wallet, seedHex); + assertEntropyEq(wallet, entropyHex); } -TEST(HDWallet, IsValid) { - EXPECT_TRUE(TWHDWalletIsValid(STRING("credit expect life fade cover suit response wash pear what skull force").get())); - EXPECT_FALSE(TWHDWalletIsValid(STRING("ripple scissors hisc mammal hire column oak again sun offer wealth tomorrow").get())); // invalid word +TEST(HDWallet, Generate) { + 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, SeedNoPassword) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); +TEST(HDWallet, CreateFromMnemonicNoPassword) { + 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"); } +TEST(HDWallet, CreateFromStrengthInvalid) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreate(64, STRING("").get())); + ASSERT_EQ(wallet.get(), nullptr); +} + +TEST(HDWallet, CreateFromMnemonicInvalid) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING("THIS IS INVALID MNEMONIC").get(), STRING("").get())); + ASSERT_EQ(wallet.get(), nullptr); +} + 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())); @@ -79,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)); @@ -89,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())); @@ -99,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())); @@ -110,14 +140,51 @@ 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"); + } + { + auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyDerivation(wallet.get(), TWCoinTypeBitcoin, TWDerivationBitcoinTaproot)); + assertHexEqual(WRAPD(TWPrivateKeyData(key.get())), "a2c4d6df786f118f20330affd65d248ffdc0750ae9cbc729d27c640302afd030"); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + assertHexEqual(publicKeyData, "026acc6c37789625ecd5ad4ebb7631249dfb9ebc3f82386f187325f54881557b9f"); + } +} + 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)); @@ -134,7 +201,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"); } @@ -156,7 +223,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())); @@ -165,7 +232,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())); @@ -174,7 +241,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())); @@ -189,7 +256,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())); @@ -221,7 +288,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())); @@ -229,27 +296,49 @@ 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(), TWCoinTypeSmartChainLegacy)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); auto keyData2 = WRAPD(TWPrivateKeyData(key2.get())); auto expected = "ca81b1b0974aa063de2f74c17b9dc364a8208d105659f4f900c121fb170922fe"; + auto expectedETH = "c4f77b4a9f5a0db3a7ffc3599e61bef986037ae9a7cc1972a10d55c030270020"; assertHexEqual(keyData, expected); - assertHexEqual(keyData2, expected); + assertHexEqual(keyData2, expectedETH); +} + +TEST(HDWallet, DeriveAvalancheCChain) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeAvalancheCChain)); + auto keyData = WRAPD(TWPrivateKeyData(key.get())); + + auto expectedETH = "c4f77b4a9f5a0db3a7ffc3599e61bef986037ae9a7cc1972a10d55c030270020"; + + assertHexEqual(keyData, expectedETH); +} + +TEST(HDWallet, DeriveCardano) { + 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()), 192ul); + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); + + assertHexEqual(privateKeyData, "f8a3b8ad30e62c369b939336c2035aba26d1ffad135e6f346f2a370517a14952e73d20aeadf906bc8b531900fb6c3ed4a05b16973c10ae24650b68b26fae4ee5d97418ba7f3b2707fae963041ff5f174195d1578da09478ad2d17a1ecc00cad478a8ca3be214870accd41f008d70e3b4b59b5981ca933d6d3f389ad317a14952166d8fd329ae3fab4712da739efc2ded9b3eef2b1a8e225dd3dddeb4f065a729b297d9fa76b8852eef235c25aac8f0ff6209ab7251f2a84c83b3b5f1161f7c59"); + assertStringsEqual(address, "addr1q9zz5nj4rqdvteauvdtc834f2vtzyrdrrdmnwdyp4s6huuz5fp33p9cq4xwpqtgguaxknzurmglv58yn9wrv6angpvvq9u36ya"); } TEST(HDWallet, ExtendedKeys) { @@ -278,6 +367,25 @@ TEST(HDWallet, ExtendedKeys) { assertStringsEqual(emptyPub, ""); } +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, TWDerivationBitcoinSegwit, TWHDVersionZPRV, 0)); + assertStringsEqual(zprv0, "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE"); + auto zprvTaproot = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP86, TWCoinTypeBitcoin, TWDerivationBitcoinTaproot, TWHDVersionZPRV, 0)); + assertStringsEqual(zprvTaproot, "zprvAcMMthTpHWStuMM6r6wLFq5uiZhLN8sZCYyTCQLEF5XWgMtAgobYEqc95nLcWB43pCvLjbEu5HCEa8D5MjxcUdLdaeyQfojS5zpovJT6Swy"); + auto zprv1 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPRV, 1)); + assertStringsEqual(zprv1, "zprvAdG4iTXWBoAS2cCGuaGevCvH54GCunrvLJb2hoWCSuE3D9LS42XVg3c6sPm64w6VMq3w18vJf8nF3cBA2kUMkyWHsq6enWVXivzw42UrVHG"); + + auto zpub0 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPUB, 0)); + assertStringsEqual(zpub0, "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"); + auto zpubTaproot = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP86, TWCoinTypeBitcoin, TWDerivationBitcoinTaproot, TWHDVersionZPUB, 0)); + assertStringsEqual(zpubTaproot, "zpub6qLiJCzi7t1C7qRZx8ULcy2eGbXpmbbQZmu3znjqoR4VZADKELunndvcw4iSVaGx2KhviLLXuB3vcyGcdd2q8XXBQJc5T5qrtorRowa1zfs"); + auto zpub1 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPUB, 1)); + assertStringsEqual(zpub1, "zpub6rFR7y4Q2AijF6Gk1bofHLs1d66hKFamhXWdWBup1Em25wfabZqkDqvaieV63fDQFaYmaatCG7jVNUpUiM2hAMo6SAVHcrUpSnHDpNzucB7"); +} + TEST(HDWallet, PublicKeyFromX) { auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); auto xpubAddr2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get())); @@ -324,6 +432,15 @@ TEST(HDWallet, PublicKeyFromZ) { assertStringsEqual(address4, "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"); } +TEST(HDWallet, PublicKeyFromExtended_Ethereum) { + const auto xpub = STRING("xpub6C7LtZJgtz1BKXG9mExKUxYvX7HSF38UMMmGbpqNQw3DfYwAw8E6sH7VSVxFipvEEm2afSqTjoRgcLmycXX4zfxCWJ4HY73a9KdgvfHEQGB"); + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeEthereum, STRING("m/44'/60'/0'/0/1").get())); + ASSERT_NE(xpubAddr.get(), nullptr); + auto data = WRAPD(TWPublicKeyData(xpubAddr.get())); + ASSERT_NE(data.get(), nullptr); + assertHexEqual(data, "044516c4aa5352035e1bb5be132694e1389a4ac37d32e5e717d35ee4c4dfab513226a9d14ea37a55962ad3644a08e2ce551b4495beabb9b09e688c7b92eba18acc"); +} + TEST(HDWallet, PublicKeyFromExtended_NIST256p1) { const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeNEO, STRING("m/44'/888'/0'/0/0").get())); // Neo @@ -379,9 +496,117 @@ TEST(HDWallet, MultipleThreads) { th3.join(); } -TEST(HDWallet, GetKeyBIP44) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); - const auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyBIP44(wallet.get(), TWCoinTypeBitcoin, 0, 0, 0)); +TEST(HDWallet, GetDerivedKey) { + 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(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"); + const auto expectedPublicKey1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; + const auto expectedPublicKey2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; + const auto expectedAddress1 = "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"; + const auto expectedAddress2 = "bc1q7zddsunzaftf4zlsg9exhzlkvc5374a6v32jf6"; + + // -> privateKey -> publicKey + { + const auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), coin, derivPath1.get())); + const auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey1.get(), true)); + const auto publicKey1Data = WRAPD(TWPublicKeyData(publicKey1.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey1Data.get()), TWDataSize(publicKey1Data.get()))), expectedPublicKey1); + const auto address1 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey1.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address1.get())).get())), expectedAddress1); + } + { + const auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), coin, derivPath2.get())); + const auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey2.get(), true)); + const auto publicKey2Data = WRAPD(TWPublicKeyData(publicKey2.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey2Data.get()), TWDataSize(publicKey2Data.get()))), expectedPublicKey2); + const auto address2 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address2.get())).get())), expectedAddress2); + } + + // zpub -> publicKey + const auto zpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP84, coin, TWHDVersionZPUB)); + EXPECT_EQ(std::string(TWStringUTF8Bytes(zpub.get())), "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); + + { + const auto publicKey1 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), coin, derivPath1.get())); + EXPECT_NE(publicKey1.get(), nullptr); + const auto publicKey1Data = WRAPD(TWPublicKeyData(publicKey1.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey1Data.get()), TWDataSize(publicKey1Data.get()))), expectedPublicKey1); + const auto address1 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey1.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address1.get())).get())), expectedAddress1); + } + { + const auto publicKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), coin, derivPath2.get())); + EXPECT_NE(publicKey2.get(), nullptr); + const auto publicKey2Data = WRAPD(TWPublicKeyData(publicKey2.get())); + EXPECT_EQ(hex(TW::data(TWDataBytes(publicKey2Data.get()), TWDataSize(publicKey2Data.get()))), expectedPublicKey2); + const auto address2 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWSegwitAddressDescription(address2.get())).get())), expectedAddress2); + } +} diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 44a62cb77d3..d99e81a6bb8 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 @@ -28,9 +26,15 @@ TEST(TWHRP, StringForHRP) { ASSERT_STREQ(stringForHRP(TWHRPMonacoin), "mona"); ASSERT_STREQ(stringForHRP(TWHRPKava), "kava"); 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"); + ASSERT_STREQ(stringForHRP(TWHRPPactus), "pc"); } TEST(TWHRP, HRPForString) { @@ -51,8 +55,15 @@ 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); + ASSERT_EQ(hrpForString("pc"), TWHRPPactus); } TEST(TWHPR, HPRByCoinType) { @@ -71,9 +82,16 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPMonacoin, TWCoinTypeHRP(TWCoinTypeMonacoin)); ASSERT_EQ(TWHRPKava, TWCoinTypeHRP(TWCoinTypeKava)); 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(TWHRPPactus, TWCoinTypeHRP(TWCoinTypePactus)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeAion)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeCallisto)); @@ -93,18 +111,18 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypePOANetwork)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeXRP)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeStellar)); - ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTON)); 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)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeZcash)); - ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeZcoin)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeFiro)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeZelcash)); 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 5ead9c659fd..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; @@ -176,38 +174,6 @@ TEST(TWHashTests, Groestl512) { } } -TEST(TWHashTests, XXHash64) { - auto tests = { - make_tuple(string(""), string("99e9d85137db46ef"), 0), - make_tuple(brownFox, string("bc71da1f362d240b"), 0), - make_tuple(brownFoxDot, string("73ad51577033ad44"), 0), - make_tuple(string("123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF"), - string("fd84b6962fcb8d09"), 0), - make_tuple(string("123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF"), - string("7ea0f7af4b4f9cf4"), 1), - }; - for (auto &test: tests) { - const auto inData = WRAPD(TWDataCreateWithBytes(reinterpret_cast(get<0>(test).c_str()), get<0>(test).length())); - const auto hash = WRAPD(TWHashXXHash64(inData.get(), get<2>(test))); - EXPECT_EQ(hex(data(TWDataBytes(hash.get()), TWDataSize(hash.get()))), get<1>(test)); - } -} - -TEST(TWHashTests, XXHash64Concat) { - auto tests = { - make_tuple(string(""), string("99e9d85137db46ef4bbea33613baafd5")), - make_tuple(brownFox, string("bc71da1f362d240bdbc6d2dab69150df")), - make_tuple(brownFoxDot, string("73ad51577033ad44269e8e5ef42d32d2")), - make_tuple(string("Balances"), string("c2261276cc9d1f8598ea4b6a74b15c2f")), - make_tuple(string("FreeBalance"), string("6482b9ade7bc6657aaca787ba1add3b4")), - }; - for (auto &test: tests) { - const auto inData = WRAPD(TWDataCreateWithBytes(reinterpret_cast(get<0>(test).c_str()), get<0>(test).length())); - const auto hash = WRAPD(TWHashTwoXXHash64Concat(inData.get())); - EXPECT_EQ(hex(data(TWDataBytes(hash.get()), TWDataSize(hash.get()))), get<1>(test)); - } -} - TEST(TWHashTests, SHA256SHA256) { auto tests = { make_tuple(string(""), string("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456")), 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 new file mode 100644 index 00000000000..dbd22ca6fa4 --- /dev/null +++ b/tests/interface/TWPBKDF2Tests.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include + +#include + +TEST(TWPBKDF2, Sha256_sha512) { + auto password = DATA("50617373776f7264"); // Password + auto salt = DATA("4e61436c"); // NaCl + + auto sha256Result = WRAPD(TWPBKDF2HmacSha256(password.get(), salt.get(), 80000, 128)); + assertHexEqual(sha256Result, "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d62aae85a11cdde829d89cb6ffd1ab0e63a981f8747d2f2f9fe5874165c83c168d2eed1d2d5ca4052dec2be5715623da019b8c0ec87dc36aa751c38f9893d15c3"); + + auto sha512Result = WRAPD(TWPBKDF2HmacSha512(password.get(), salt.get(), 80000, 128)); + assertHexEqual(sha512Result, "e6337d6fbeb645c794d4a9b5b75b7b30dac9ac50376a91df1f4460f6060d5addb2c1fd1f84409abacc67de7eb4056e6bb06c2d82c3ef4ccd1bded0f675ed97c65c33d39f81248454327aa6d03fd049fc5cbb2b5e6dac08e8ace996cdc960b1bd4530b7e754773d75f67a733fdb99baf6470e42ffcb753c15c352d4800fb6f9d6"); +} 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 b0ec026e983..90b060da2ad 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.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 "PublicKey.h" #include "PrivateKey.h" @@ -49,26 +47,26 @@ 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)); } TEST(TWPublicKeyTests, Verify) { - const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); const char* message = "Hello"; @@ -81,6 +79,23 @@ TEST(TWPublicKeyTests, Verify) { ASSERT_TRUE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); } +TEST(TWPublicKeyTests, VerifyAsDER) { + const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); + + const char* message = "Hello"; + auto messageData = WRAPD(TWDataCreateWithBytes((const uint8_t*)message, strlen(message))); + auto digest = WRAPD(TWHashKeccak256(messageData.get())); + + auto signature = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), digest.get())); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); + + ASSERT_TRUE(TWPublicKeyVerifyAsDER(publicKey.get(), signature.get(), digest.get())); + + ASSERT_FALSE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); +} + TEST(TWPublicKeyTests, VerifyEd25519) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index dda1426ff93..587d3e652e4 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 @@ -14,6 +12,7 @@ #include "../src/HexCoding.h" #include +#include #include @@ -24,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"); @@ -56,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, TWStoredKeyCreate(name.get(), password.get())); + 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())); @@ -81,6 +80,205 @@ 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, importPrivateKeyAes256Legacy) { + const auto privateKeyHex = "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"; + 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, TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr, TWDerivationBitcoinLegacy)); + 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); + + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccount(key.get(),0)); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); +} + +TEST(TWStoredKey, importPrivateKeyAes256Taproot) { + const auto privateKeyHex = "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"; + 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, TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr, TWDerivationBitcoinSegwit)); + 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); + + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccount(key.get(),0)); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qlp5hssx3qstf3m0mt7fd6tzlh90ssm32u2llf4"); +} + +TEST(TWStoredKey, importPrivateKeyHexButDecryptEncoded) { + 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, TWStoredKeyImportPrivateKey(privateKey.get(), name.get(), password.get(), coin)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + EXPECT_FALSE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.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, importPrivateKeyEncodedHex) { + const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)); + 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, TWStoredKeyImportPrivateKeyEncoded(privateKey.get(), name.get(), password.get(), coin)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.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, importPrivateKeyEncodedHexLegacy) { + const auto privateKeyHex = "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)); + 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, TWStoredKeyImportPrivateKeyEncodedWithEncryptionAndDerivation(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes128Ctr, TWDerivationBitcoinLegacy)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.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); + + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccount(key.get(),0)); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); +} + +TEST(TWStoredKey, importPrivateKeyEncodedStellar) { + const auto privateKeyEncoded = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7"; + const auto decodedPrivateKeyHex = "2bff5257425c161217781b47419e2f56874d6f1a0758a4252934c1a6e6d72607"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyEncoded)); + 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 = TWCoinTypeStellar; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(privateKey.get(), name.get(), password.get(), coin)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), decodedPrivateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.get())), privateKeyEncoded); + + 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()))), decodedPrivateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + +TEST(TWStoredKey, importPrivateKeyEncodedSolana) { + const auto solanaPrivateKey = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"; + const auto decodedSolanaPrivateKeyHex = "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"; + 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 solanaKey = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(WRAPS(TWStringCreateWithUTF8Bytes(solanaPrivateKey)).get(), name.get(), password.get(), TWCoinTypeSolana)); + const auto solanaPrivateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(solanaKey.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(solanaPrivateKey2.get()), TWDataSize(solanaPrivateKey2.get()))), decodedSolanaPrivateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(solanaKey.get())); + const auto solanaPrivateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(solanaKey.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(solanaPrivateKey2Encoded.get())), solanaPrivateKey); + + const auto solanaPrivateKey3 = TWStoredKeyPrivateKey(solanaKey.get(), TWCoinTypeSolana, password.get()); + const auto solanaPkData3 = WRAPD(TWPrivateKeyData(solanaPrivateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(solanaPkData3.get()), TWDataSize(solanaPkData3.get()))), decodedSolanaPrivateKeyHex); + TWPrivateKeyDelete(solanaPrivateKey3); +} + +TEST(TWStoredKey, importPrivateKeyHexEncodedSolana) { + const auto solanaPrivateKey = "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"; + 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 solanaKey = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(WRAPS(TWStringCreateWithUTF8Bytes(solanaPrivateKey)).get(), name.get(), password.get(), TWCoinTypeSolana)); + const auto solanaPrivateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(solanaKey.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(solanaPrivateKey2.get()), TWDataSize(solanaPrivateKey2.get()))), solanaPrivateKey); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(solanaKey.get())); + const auto solanaPrivateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(solanaKey.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(solanaPrivateKey2Encoded.get())), solanaPrivateKey); + + const auto solanaPrivateKey3 = TWStoredKeyPrivateKey(solanaKey.get(), TWCoinTypeSolana, password.get()); + const auto solanaPkData3 = WRAPD(TWPrivateKeyData(solanaPrivateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(solanaPkData3.get()), TWDataSize(solanaPkData3.get()))), solanaPrivateKey); + TWPrivateKeyDelete(solanaPrivateKey3); +} + +TEST(TWStoredKey, importPrivateKeyEncodedAes256) { + const auto privateKeyEncoded = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7"; + const auto decodedPrivateKeyHex = "2bff5257425c161217781b47419e2f56874d6f1a0758a4252934c1a6e6d72607"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyEncoded)); + 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 = TWCoinTypeStellar; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncodedWithEncryption(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()))), decodedPrivateKeyHex); + + 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()))), decodedPrivateKeyHex); + 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")); @@ -96,6 +294,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()))); @@ -108,27 +321,77 @@ 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"; const auto extPubKeyAdd = "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"; + const auto pubKey = "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"; TWStoredKeyAddAccount(key.get(), WRAPS(TWStringCreateWithUTF8Bytes(addressAdd)).get(), TWCoinTypeBitcoin, 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); } +TEST(TWStoredKey, addressAddRemoveDerivationPath) { + 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 = createAStoredKey(coin, password.get()); + + const auto wallet = WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())); + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), coin, wallet.get())); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + + 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()), 1ul); + + TWStoredKeyRemoveAccountForCoinDerivationPath(key.get(), coin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath0)).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0ul); +} + +TEST(TWStoredKey, addressAddDerivation) { + 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 = createAStoredKey(coin, password.get()); + const auto wallet = WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())); + + const auto accountCoin1 = WRAP(TWAccount, TWStoredKeyAccountForCoinDerivation(key.get(), coin, TWDerivationDefault, wallet.get())); + const auto accountAddress1 = WRAPS(TWAccountAddress(accountCoin1.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress1.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + + const auto accountCoin2 = WRAP(TWAccount, TWStoredKeyAccountForCoinDerivation(key.get(), coin, TWDerivationBitcoinLegacy, wallet.get())); + const auto accountAddress2 = WRAPS(TWAccountAddress(accountCoin2.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress2.get())), "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz"); + + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); + + const auto accountCoin3 = WRAP(TWAccount, TWStoredKeyAccountForCoinDerivation(key.get(), coin, TWDerivationBitcoinTaproot, wallet.get())); + const auto accountAddress3 = WRAPS(TWAccountAddress(accountCoin3.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress3.get())), "bc1pyqkqf20fmmwmcxf98tv6k63e2sgnjy4zne6d0r32vxwm3au0hnksq6ec57"); +} + TEST(TWStoredKey, exportJSON) { const auto key = createDefaultStoredKey(); const auto json = WRAPD(TWStoredKeyExportJSON(key.get())); @@ -137,6 +400,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"); @@ -155,7 +442,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())); @@ -177,6 +464,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())); @@ -196,17 +557,18 @@ TEST(TWStoredKey, removeAccountForCoin) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - auto key = WRAP(TWStoredKey, TWStoredKeyCreate("Test KeyStore", password.get())); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("Test KeyStore")); + auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); auto wallet = WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())); 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); @@ -220,7 +582,41 @@ TEST(TWStoredKey, getWalletPasswordInvalid) { const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); - auto key = WRAP(TWStoredKey, TWStoredKeyCreate (name.get(), password.get())); + auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); ASSERT_NE(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())).get(), nullptr); ASSERT_EQ(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), passwordInvalid.get())).get(), nullptr); } + +TEST(TWStoredKey, encryptionParameters) { + const auto key = createDefaultStoredKey(); + const auto params = WRAPS(TWStoredKeyEncryptionParameters(key.get())); + + nlohmann::json jsonParams = nlohmann::json::parse(string(TWStringUTF8Bytes(params.get()))); + + // compare some specific parameters + EXPECT_EQ(jsonParams["kdfparams"]["n"], 16384); + EXPECT_EQ(std::string(jsonParams["cipherparams"]["iv"]).length(), 32ul); + + // compare all keys, except dynamic ones (like cipherparams/iv) + jsonParams["cipherparams"] = {}; + jsonParams["ciphertext"] = ""; + jsonParams["kdfparams"]["salt"] = ""; + jsonParams["mac"] = ""; + const auto params2 = jsonParams.dump(); + assertJSONEqual(params2, R"( + { + "cipher": "aes-128-ctr", + "cipherparams": null, + "ciphertext": "", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 16384, + "p": 4, + "r": 8, + "salt": "" + }, + "mac": "" + } + )"); +} 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 d49b9b335e9..00000000000 --- a/tests/interface/TWTestUtilities.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 - -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); -} diff --git a/tests/interface/TWTestUtilities.h b/tests/interface/TWTestUtilities.h deleted file mode 100644 index e71190d8f37..00000000000 --- a/tests/interface/TWTestUtilities.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 -#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 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 "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "NULS/Address.h" +#include + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "uint256.h" +#include + +#include "TestUtilities.h" +#include + +#include +#include +#include + +using namespace TW; + +TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { + /// 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()); + + { + // Check, by parsing + EXPECT_EQ((int)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 + 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 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 PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5" + "366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + + // Verify signature (pubkey & hash & signature) + { EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); } + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedTx = + "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001" + "121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35" + "920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c" + "253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a5" + "04f9e8310679"; + { + Binance::Proto::SigningOutput output; + 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. + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputDataPtr.get()), + (int)TWDataSize(txInputDataPtr.get()))); + 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(TWTransactionCompiler, ExternalSignatureSignEthereum) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEthereum; + 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)); + 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(signingInput.SerializeAsString()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); + EXPECT_EQ((int)TWDataSize(txInputData.get()), 75); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + 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("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, preImageHashData)); } + + /// 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 = + "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0" + "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db4" + "58893b928f3efbfee90c9febf51ab84c97966779"; + { + EXPECT_EQ(TWDataSize(outputData.get()), 217ul); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + + 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 input; + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + 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); + } +} + +// data conversion utility +Data dataFromTWData(std::shared_ptr data) { + return TW::data(TWDataBytes(data.get()), TWDataSize(data.get())); +} + +TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { + // 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 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 signingInput + const auto txInputDataData = data(signingInput.SerializeAsString()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + Bitcoin::Proto::PreSigningOutput preOutput; + ASSERT_TRUE(preOutput.ParseFromArray(preImageHash.data(), (int)preImageHash.size())); + ASSERT_EQ(preOutput.hash_public_keys_size(), 3); + auto hashes = preOutput.hash_public_keys(); + EXPECT_EQ(hex(hashes[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); + EXPECT_EQ(hex(hashes[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); + EXPECT_EQ(hex(hashes[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); + EXPECT_EQ(hex(hashes[0].public_key_hash()), hex(inPubKeyHash1)); + EXPECT_EQ(hex(hashes[1].public_key_hash()), hex(inPubKeyHash0)); + EXPECT_EQ(hex(hashes[2].public_key_hash()), hex(inPubKeyHash0)); + + // Simulate signatures, normally obtained from signature server. + 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& 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; + + 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_)); + } + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + 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()), 786ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + + 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(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + + // 2 private keys are needed (despite >2 UTXOs) + auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); + EXPECT_EQ(hex(PrivateKey(key0, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1, TWCurveSECP256k1).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); + } +} + +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..20ffbbdd6bf --- /dev/null +++ b/tests/interface/TWTransactionUtilTests.cpp @@ -0,0 +1,89 @@ +// 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"); +} + +TEST(TWTransactionUtil, CalcTxHashPolkadot) { + constexpr auto coin = TWCoinTypePolkadot; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("3d02849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783009025843bc49c1c4fbc99dbbd290c92f9879665d55b02f110abfb4800f0e7630877d2cffd853deae7466c22fbc8616a609e1b92615bb365ea8adccba5ef7624050503000007009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830700aea68f0201")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0x8da66d3fe0f592cff714ec107289370365117a1abdb72a19ac91181fdcf62bba"); +} + +TEST(TWTransactionUtil, CalcTxHashAcala) { + constexpr auto coin = TWCoinTypeAcala; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0xb2450990defef55f075f41969c8ae7965ddf8446a0aae9510b8bbdbacb4ff344"); +} 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: "<&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 083f8f56724..fd6a4a49a2d 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 -s) == "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/tools/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 d06dcc21733..9d8d952b60c 100755 --- a/tools/coverage +++ b/tools/coverage @@ -1,18 +1,34 @@ #!/bin/bash # -# This script captures test code coverage information. +# This script processes captured test code coverage information, +# generates HTML report (if html argument is given), +# and compares current value to previously saved value (stored in coverage.stats). +# +# To generate coverage info: +# - run cmake, to enable coverage measurement +# cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug +# - build tester +# make -Cbuild -j12 tests TrezorCryptoTests +# - Remove any old coverage files +# find . -name "*.gcda" -exec rm \{\} \; +# - run unit tests +# ./build/trezor-crypto/crypto/tests/TrezorCryptoTests +# ./build/tests/tests tests --gtest_output=xml +# - generate coverage info (slow). Optionally generate report in HTML format: +# ./tools/coverage html + set -e function install_llvm_gcov() { cat << EOF > /tmp/llvm-gcov.sh #!/bin/bash -exec /usr/bin/llvm-cov gcov "\$@" +exec /usr/bin/llvm-cov-14 gcov "\$@" EOF sudo chmod 755 /tmp/llvm-gcov.sh } -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then echo "gcov is llvm-cov on macOS" lcov --capture --directory . --output-file coverage.info else @@ -22,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 @@ -29,19 +46,7 @@ lcov --remove coverage.info '*.pb.h' --output-file coverage.info lcov --remove coverage.info '*/tests/*' --output-file coverage.info lcov --remove coverage.info '*/samples/*' --output-file coverage.info -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 - -# Generate HTML report +# Generate HTML report if requested if [[ "$1" == "html" ]]; then genhtml --output-directory coverage \ --demangle-cpp --num-spaces 4 --sort \ @@ -49,3 +54,5 @@ if [[ "$1" == "html" ]]; then --function-coverage --branch-coverage --legend \ coverage.info fi + +tools/check-coverage coverage.stats coverage.info diff --git a/tools/dependencies-version b/tools/dependencies-version new file mode 100755 index 00000000000..a18fbc3eaf3 --- /dev/null +++ b/tools/dependencies-version @@ -0,0 +1,7 @@ +#!/bin/bash + +export GTEST_VERSION=1.16.0 +export CHECK_VERSION=0.15.2 +export JSON_VERSION=3.11.3 +export PROTOBUF_VERSION=3.20.3 +export SWIFT_PROTOBUF_VERSION=1.18.0 diff --git a/tools/download-dependencies b/tools/download-dependencies new file mode 100755 index 00000000000..2d674ab60c1 --- /dev/null +++ b/tools/download-dependencies @@ -0,0 +1,61 @@ +#!/bin/bash + +set -e + +ROOT="$PWD" +PREFIX="${PREFIX:-$ROOT/build/local}" + +# Load dependencies version +BASE_DIR=$(cd `dirname $0`; pwd) +source ${BASE_DIR}/dependencies-version + +function download_gtest() { + echo "Downloading gtest..." + GTEST_DIR="$ROOT/build/local/src/gtest" + mkdir -p "$GTEST_DIR" + cd "$GTEST_DIR" + if [ ! -f release-$GTEST_VERSION.tar.gz ]; then + curl -fSsOL https://github.com/google/googletest/releases/download/v$GTEST_VERSION/googletest-$GTEST_VERSION.tar.gz + fi + tar xzf googletest-$GTEST_VERSION.tar.gz +} + +function download_libcheck() { + echo "Downloading libcheck..." + CHECK_DIR="$ROOT/build/local/src/check" + mkdir -p "$CHECK_DIR" + cd "$CHECK_DIR" + if [ ! -f check-$CHECK_VERSION.tar.gz ]; then + curl -fSsOL https://github.com/libcheck/check/releases/download/$CHECK_VERSION/check-$CHECK_VERSION.tar.gz + fi + tar xzf check-$CHECK_VERSION.tar.gz +} + +function download_nolhmann_json() { + echo "Downloading nolhmann_json..." + JSON_DIR="$ROOT/build/local/json" + mkdir -p "$JSON_DIR" + cd "$JSON_DIR" + if [ ! -f include.zip ]; then + curl -fSsOL https://github.com/nlohmann/json/releases/download/v$JSON_VERSION/include.zip + fi + unzip -qq -d "$PREFIX" -o include.zip +} + +function download_protobuf() { + echo "Downloading protobuf..." + PROTOBUF_DIR="$ROOT/build/local/src/protobuf" + mkdir -p "$PROTOBUF_DIR" + cd "$PROTOBUF_DIR" + if [ ! -f protobuf-java-$PROTOBUF_VERSION.tar.gz ]; then + curl -fSsOL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-java-$PROTOBUF_VERSION.tar.gz + fi + tar xzf protobuf-java-$PROTOBUF_VERSION.tar.gz +} + +download_gtest +download_libcheck +download_nolhmann_json +download_protobuf + +echo "done." 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/flutter-build b/tools/flutter-build new file mode 100755 index 00000000000..fee0babbb85 --- /dev/null +++ b/tools/flutter-build @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +mkdir -p build + +pushd build +cmake .. -DFLUTTER=ON +make -j12 +popd + +if [ "$(uname -s)" == "Darwin" ]; then + cp build/libTrustWalletCore.dylib flutter/lib/ +elif [ "$(uname -s)" == "Linux" ]; then + cp build/libTrustWalletCore.so flutter/lib/ +elif [ "$(uname -s)" == "Windows" ]; then + cp build/libTrustWalletCore.dll flutter/lib/ +fi + +pushd flutter + +echo "Generating bindings..." +dart run ffigen --config config.yaml + + +echo "Verifying the build..." +dart run + +echo "Done" + +popd diff --git a/tools/generate-files b/tools/generate-files index 523835e157c..319b4c49779 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -10,6 +10,16 @@ set -e +source tools/parse_args "$@" + +if isHelp; then + echo "usage: generate-files [-h | --help] [all | native | wasm | android | ios]" + echo "" + echo "Generate files and bindings for target platforms specified in arguments" + echo "You can specify multiple targets at once" + exit 0 +fi + # This script works in both Docker and normal build environments. # Protobuf and co. tools are taken from: $PREFIX if provided, or from $PWD/build/local if exists, or from /usr/bin if [ -z $PREFIX ] @@ -39,37 +49,48 @@ $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 coins.json +# Generate coins info from registry.json codegen/bin/coins -# Generate interface code +# Generate rust bindgen +tools/rust-bindgen "$@" + +# Generate interface code, Swift bindings excluded. codegen/bin/codegen +# Convert doxygen comments to appropriate format +tools/doxygen_convert_comments + # 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 + +if [ -x "$(command -v protoc-gen-swift)" ] && isTargetSpecified "ios"; 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 + # Replace SwiftProtobuf with WalletCoreSwiftProtobuf + find swift/Sources/Generated/Protobuf -name "*.swift" -exec sed -i '' 's/import SwiftProtobuf/import WalletCoreSwiftProtobuf/g' {} \; + find swift/Sources/Generated/Protobuf -name "*.swift" -exec sed -i '' 's/SwiftProtobuf./WalletCoreSwiftProtobuf./g' {} \; 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 -- not every time +# 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/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 - pushd swift - xcodegen - pod install - popd -else +if [ -x "$(command -v xcodegen)" ] && isTargetSpecified "ios"; then + tools/xcodegen +elif isTargetSpecified "ios"; then echo -e "\nWARNING: Skipped generating Xcode project because the xcodegen tool is not installed." +else + echo -e "\nWARNING: skipping xcodegen generation" fi diff --git a/tools/gtest.patch b/tools/gtest.patch deleted file mode 100644 index 83138001530..00000000000 --- a/tools/gtest.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- CMakeLists.txt 2019-10-03 23:52:15.000000000 +0900 -+++ CMakeLists.txt.patched 2021-01-29 10:16:41.000000000 +0900 -@@ -1,7 +1,7 @@ - # Note: CMake support is community-based. The maintainers do not use CMake - # internally. - --cmake_minimum_required(VERSION 2.8.8) -+cmake_minimum_required(VERSION 2.8.12) - - if (POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) diff --git a/tools/gtest_mock.patch b/tools/gtest_mock.patch deleted file mode 100644 index 2b9dfb81c62..00000000000 --- a/tools/gtest_mock.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- googlemock/CMakeLists.txt 2019-10-03 23:52:15.000000000 +0900 -+++ googlemock/CMakeLists.txt.patched 2021-01-29 10:28:32.000000000 +0900 -@@ -42,7 +42,7 @@ - cmake_policy(SET CMP0048 NEW) - project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) - endif() --cmake_minimum_required(VERSION 2.6.4) -+cmake_minimum_required(VERSION 2.8.12) - - if (COMMAND set_up_hermetic_build) - set_up_hermetic_build() diff --git a/tools/gtest_test.patch b/tools/gtest_test.patch deleted file mode 100644 index 09b0d659c79..00000000000 --- a/tools/gtest_test.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- googletest/CMakeLists.txt 2019-10-03 23:52:15.000000000 +0900 -+++ googletest/CMakeLists.txt.patched 2021-01-29 10:24:45.000000000 +0900 -@@ -53,7 +53,7 @@ - cmake_policy(SET CMP0048 NEW) - project(gtest VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) - endif() --cmake_minimum_required(VERSION 2.6.4) -+cmake_minimum_required(VERSION 2.8.12) - - if (POLICY CMP0063) # Visibility - cmake_policy(SET CMP0063 NEW) diff --git a/tools/install-android-dependencies b/tools/install-android-dependencies new file mode 100755 index 00000000000..667e8c4342d --- /dev/null +++ b/tools/install-android-dependencies @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +source "$(dirname $0)/android-sdk" + +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 08dc61c6d88..e59287ba45a 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -6,6 +6,16 @@ ROOT="$PWD" PREFIX="${PREFIX:-$ROOT/build/local}" echo "PREFIX: $PREFIX" +CMAKE=cmake +MAKE=make + +# Load dependencies version +BASE_DIR=$( + cd $(dirname $0) + pwd +) +source ${BASE_DIR}/dependencies-version + # Setup up folders export PATH="$PREFIX/bin":$PATH export LDFLAGS="-L$PREFIX/lib" @@ -14,78 +24,57 @@ export DYLD_LIBRARY_PATH="$PREFIX/lib" export LD_LIBRARY_PATH="$PREFIX/lib" export LD_RUN_PATH="$PREFIX/lib" -# Download Google Test -export GTEST_VERSION=1.10.0 -GTEST_DIR="$ROOT/build/local/src/gtest" -mkdir -p "$GTEST_DIR" -cd "$GTEST_DIR" -if [ ! -f release-$GTEST_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/google/googletest/archive/release-$GTEST_VERSION.tar.gz -fi -tar xzf release-$GTEST_VERSION.tar.gz - -# Build gtest -cd googletest-release-$GTEST_VERSION -echo "patching cmake minimum version" -cp $ROOT/tools/*.patch . -patch CMakeLists.txt gtest.patch -patch googletest/CMakeLists.txt gtest_test.patch -patch googlemock/CMakeLists.txt gtest_mock.patch - -cmake -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. -make -j4 -make install -make clean - -# Download Check -export CHECK_VERSION=0.15.2 -CHECK_DIR="$ROOT/build/local/src/check" -mkdir -p "$CHECK_DIR" -cd "$CHECK_DIR" -if [ ! -f check-$CHECK_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/libcheck/check/releases/download/$CHECK_VERSION/check-$CHECK_VERSION.tar.gz -fi -tar xzf check-$CHECK_VERSION.tar.gz - -# Build Check -cd check-$CHECK_VERSION -./configure --prefix="$PREFIX" -make -j4 -make install -make clean - -# Download Nlohmann JSON -export JSON_VERSION=3.9.1 -JSON_DIR="$ROOT/build/local/json" -mkdir -p "$JSON_DIR" -cd "$JSON_DIR" -if [ ! -f include.zip ]; then - curl -fSsOL https://github.com/nlohmann/json/releases/download/v$JSON_VERSION/include.zip -fi -unzip -d "$PREFIX" -o include.zip - -# Download Protobuf sources -export PROTOBUF_VERSION=3.14.0 -PROTOBUF_DIR="$ROOT/build/local/src/protobuf" -mkdir -p "$PROTOBUF_DIR" -cd "$PROTOBUF_DIR" -if [ ! -f protobuf-java-$PROTOBUF_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-java-$PROTOBUF_VERSION.tar.gz -fi -tar xzf protobuf-java-$PROTOBUF_VERSION.tar.gz - -# Build Protobuf -cd protobuf-$PROTOBUF_VERSION -./configure --prefix="$PREFIX" -make -j4 -make install -# after install, cleanup to save space (docker) -make clean -"$PREFIX/bin/protoc" --version - -if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then +function download_dependencies() { + ${BASE_DIR}/download-dependencies +} + +function build_gtest() { + # Build gtest + GTEST_DIR="$ROOT/build/local/src/gtest" + cd ${GTEST_DIR}/googletest-$GTEST_VERSION + $CMAKE -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. + $MAKE -j4 + $MAKE install + $MAKE clean +} + +function build_libcheck() { + # Build Check + CHECK_DIR="$ROOT/build/local/src/check" + cd ${CHECK_DIR}/check-$CHECK_VERSION + $CMAKE -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. + $MAKE -j4 + $MAKE install + $MAKE clean +} + +function build_protobuf() { + # Build Protobuf + PROTOBUF_DIR="$ROOT/build/local/src/protobuf" + cd ${PROTOBUF_DIR}/protobuf-$PROTOBUF_VERSION + + $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) + $CMAKE --build . --target clean + "$PREFIX/bin/protoc" --version + + # Protobuf plugins + cd "$ROOT/protobuf-plugin" + $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 -s) == "Darwin" ]]; then + build_swift_plugin + fi +} + +function build_swift_plugin() { # Download Swift Protobuf sources - export SWIFT_PROTOBUF_VERSION=1.13.0 SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" @@ -99,13 +88,12 @@ if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then swift build --static-swift-stdlib -c release cp -f "$SWIFT_PROTOBUF_DIR/swift-protobuf-$SWIFT_PROTOBUF_VERSION/.build/release/protoc-gen-swift" "$PREFIX/bin" | true $PREFIX/bin/protoc-gen-swift --version -fi - -# Protobuf plugins -cd "$ROOT/protobuf-plugin" -cmake -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX -make -Cbuild -j12 -make -Cbuild install -rm -rf build +} + +download_dependencies + +build_gtest +build_libcheck +build_protobuf cd "$ROOT" 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..5249f333ddf --- /dev/null +++ b/tools/install-rust-dependencies @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +NIGHTLY="nightly-2025-01-16" + +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 -s) == "Linux" ]]; then + rustup component add rust-src --toolchain $NIGHTLY-$(uname -m)-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..3a2c786eff9 --- /dev/null +++ b/tools/install-sys-dependencies-linux @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# build-essential clang-14 libc++-dev libc++abi-dev ruby-full cmake +sudo apt-get update && sudo apt-get install ninja-build llvm-14 clang-tidy-14 libboost-all-dev rustc --fix-missing gcc-multilib g++-multilib + +# As of now, Ubuntu 24.04 has lcov 2.0-4ubuntu2 only, but we don't support it yet. +# Install lcov 1.15-1 manually. +LCOV_TEMP="$(mktemp -d)" +LCOV_DEB="$LCOV_TEMP/lcov.deb" +wget -O "$LCOV_DEB" http://mirrors.kernel.org/ubuntu/pool/universe/l/lcov/lcov_1.15-1_all.deb +sudo apt-get install "$LCOV_DEB" +rm -rf $LCOV_TEMP diff --git a/tools/install-sys-dependencies-mac b/tools/install-sys-dependencies-mac new file mode 100755 index 00000000000..1edd26eca34 --- /dev/null +++ b/tools/install-sys-dependencies-mac @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +brew install boost ninja xcodegen xcbeautify + +if command -v cmake &> /dev/null +then + echo "Skip installing CMake." +else + brew install cmake +fi + +if [[ "$BOOST_ROOT" == "" ]]; then + echo "export BOOST_ROOT=$(brew --prefix boost)" >> ~/.zprofile +fi + +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 new file mode 100755 index 00000000000..d19611616ce --- /dev/null +++ b/tools/install-wasm-dependencies @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +emsdk_version=3.1.33 + +git clone https://github.com/emscripten-core/emsdk.git + +cd emsdk + + +./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 975ee575d39..6a7b73eb96e 100755 --- a/tools/ios-build +++ b/tools/ios-build @@ -1,66 +1,62 @@ #!/bin/bash # -# This script builds a fat static library for iOS devices and simulators. +# This script builds a static wallet core common xcframework for iOS devices and simulators. # +set -o pipefail set -e -BUILD_FOLDER_DEVICE="build/ios-dev" -BUILD_FOLDER_SIMULATOR="build/ios-sim" -BUILD_FOLDER_CATALYST="build/ios-mac-catalyst" -TARGETS="TrezorCrypto protobuf TrustWalletCore" +FRAMEWORK=WalletCoreCommon +BUILD_FOLDER=build/ios-frameworks -function buildDevices() { - echo -e "\n\nBuilding for iOS Device..." - mkdir -p "$BUILD_FOLDER_DEVICE" - cmake -H. -B "$BUILD_FOLDER_DEVICE" -DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \ - -DPLATFORM=OS64 \ - -DDEPLOYMENT_TARGET=12.0 \ - -DCMAKE_BUILD_TYPE=Release - make -C "$BUILD_FOLDER_DEVICE" $TARGETS +init() { + echo "Cleanup and init..." + pushd swift + xcodegen -s common-xcframework.yml + popd + + rm -rf ${BUILD_FOLDER}/*.xcarchive + rm -rf ${BUILD_FOLDER}/${FRAMEWORK}.xcframework + rm -rf ${BUILD_FOLDER}/${FRAMEWORK}.xcframework.* +} + +# 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 | xcbeautify +} + +build_ios_arm64() { + build "${FRAMEWORK}_iOS" "generic/platform=iOS" "ios-arm64" +} + +build_ios_simulator() { + build "${FRAMEWORK}_iOS" "generic/platform=iOS Simulator" "ios-arm64_x86_64-simulator" } -function buildSimulators() { - echo -e "\nBuilding for iOS Simulator..." - mkdir -p "$BUILD_FOLDER_SIMULATOR" - cmake -H. -B "$BUILD_FOLDER_SIMULATOR" -DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \ - -DPLATFORM=SIMULATOR64 \ - -DDEPLOYMENT_TARGET=12.0 \ - -DCMAKE_BUILD_TYPE=Release - make -C "$BUILD_FOLDER_SIMULATOR" $TARGETS +build_ios_mac_catalyst() { + build "${FRAMEWORK}_iOS" "platform=macOS,arch=x86_64,variant=Mac Catalyst" "ios-x86_64_arm64-maccatalyst" } -function buildCatalyst() { - echo -e "\nBuilding for Mac Catalyst..." - CATALYST_FLAG="-target x86_64-apple-ios13.2-macabi" - - mkdir -p "$BUILD_FOLDER_CATALYST" - cmake -H. -B "$BUILD_FOLDER_CATALYST" -DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \ - -DPLATFORM=MAC_CATALYST \ - -DDEPLOYMENT_TARGET=10.15 \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF \ - -DCMAKE_CXX_FLAGS_MAC_CATALYST:STRING="$CATALYST_FLAG" \ - -DCMAKE_C_FLAGS_MAC_CATALYST:STRING="$CATALYST_FLAG" \ - -DCMAKE_BUILD_TYPE=MAC_CATALYST - sed -i '' "s/-mmacosx-version-min=10.15//g" "$BUILD_FOLDER_CATALYST"/trezor-crypto/CMakeFiles/TrezorCrypto.dir/flags.make - sed -i '' "s/-mmacosx-version-min=10.15//g" "$BUILD_FOLDER_CATALYST"/CMakeFiles/protobuf.dir/flags.make - sed -i '' "s/-mmacosx-version-min=10.15//g" "$BUILD_FOLDER_CATALYST"/CMakeFiles/TrustWalletCore.dir/flags.make - make -C "$BUILD_FOLDER_CATALYST" $TARGETS +build_mac_x64_arm64() { + build "${FRAMEWORK}_macOS" "generic/platform=macOS" "macos-arm64_x86_64" } -function fattenBinary() { - echo -e "\n\nFattenning binary..." - mkdir -p build/ios - xcrun lipo -create -output build/ios/libprotobuf.a "$BUILD_FOLDER_DEVICE"/libprotobuf.a "$BUILD_FOLDER_SIMULATOR"/libprotobuf.a - xcrun lipo -create -output build/ios/libTrezorCrypto.a "$BUILD_FOLDER_DEVICE"/trezor-crypto/libTrezorCrypto.a "$BUILD_FOLDER_SIMULATOR"/trezor-crypto/libTrezorCrypto.a - xcrun lipo -create -output build/ios/libTrustWalletCore.a "$BUILD_FOLDER_DEVICE"/libTrustWalletCore.a "$BUILD_FOLDER_SIMULATOR"/libTrustWalletCore.a +create_xc_framework() { + 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 } -function main() { - buildDevices - buildSimulators - fattenBinary +main() { + init + build_mac_x64_arm64 + build_ios_mac_catalyst + build_ios_arm64 && build_ios_simulator + create_xc_framework } main diff --git a/tools/ios-doc b/tools/ios-doc new file mode 100755 index 00000000000..2edbefe7ea2 --- /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 16' -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 6d332afc9ff..a8462222e82 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -1,116 +1,94 @@ -#!/usr/bin/env ruby +#!/bin/bash # -# Bundles all files required to build for iOS in a zip archive. +# This script compress and upload the built static wallet core common xcframework to github release # -require 'open3' -require 'tempfile' -require 'json' +set -o pipefail +set -e -version = ARGV[0] || `git describe --long --tags | cut -f 1 -d "-"`.strip +# load release library code +base_dir=$(cd `dirname $0`; pwd) -puts "Processing version: #{version}" +source ${base_dir}/library +src_dir=${base_dir}/.. +built_dir=${base_dir}/../build/ios-frameworks -# Get release by tag -release = `curl -u #{ENV['GITHUB_USER']}:#{ENV['GITHUB_TOKEN']} https://api.github.com/repos/trustwallet/wallet-core/releases/tags/#{version}` -release_hash = JSON.parse(release) -upload_url = release_hash['upload_url'] -puts "asset upload url: #{upload_url}" -upload_url.slice!('{?name,label}') +# version to release +version=$(wc_read_version $1) +echo "release version ${version}" -# First build -puts 'Building...' -_, stderr, status = Open3.capture3('tools/ios-build') -if status != 0 - STDERR.puts stderr - exit 1 -end +pushd ${built_dir} +# archive headers, swift code and xcframework -# Make archive -puts 'Archiving...' -includes = Dir.glob('include/**/*.h') + Dir.glob('lib/**/*.{h,hpp}') -sources = Dir.glob('swift/Sources/**/*.{swift,h,m}') -# Rename libTrustWalletCore.a to avoid conflicts with cocoapods static lib -File.rename('build/ios/libTrustWalletCore.a', 'build/ios/libCore.a') -libs = Dir.glob('build/ios/*.a') -files = includes + sources + libs + ['LICENSE'] -file_name = "TrustWalletCore-iOS-#{version}.zip" -_, stderr, status = Open3.capture3('zip', file_name, *files) -if status != 0 - STDERR.puts stderr - exit 1 -end +rm -rf release && mkdir -p release + +cp -R WalletCoreCommon.xcframework release +cp -R ${src_dir}/include release +cp -R ${src_dir}/swift/Sources release +rm release/Sources/Dummy.cpp +cp ${src_dir}/License release -# Upload archive -puts 'Uploading...' -upload_url = "#{upload_url}?name=#{file_name}" -puts "asset upload url: #{upload_url}" -upload_output = `curl -u #{ENV['GITHUB_USER']}:#{ENV['GITHUB_TOKEN']} -X POST -H 'Content-Type: application/octet-stream' --data-binary @#{file_name} #{upload_url}` -download_url = JSON.parse(upload_output)['browser_download_url'] -puts "final download url: #{download_url}" +filename="TrustWalletCore-${version}.tar.xz" +pushd release +tar -cJvf ../${filename} . +popd -# Upload to Cocoapod -puts 'Publishing...' -podspec = <<-PODSPEC +# upload to release +echo "Upload asset $filename" +download_url=$(wc_upload_asset ${version} ${filename}) +echo "download_url is $download_url" +download_url=$(tr -d '"' <<< $download_url) +echo "trimmed download_url is ${download_url}" + +# create podspec +podspec_name=TrustWalletCore +podspec=${podspec_name}.podspec +cat > $podspec < 'al@isaza.ca' } s.module_name = 'WalletCore' s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.14' s.swift_version = '5.1' - s.source = { - http: "#{download_url}" + http: '${download_url}' } s.default_subspec = 'Core' - s.subspec 'Types' do |ss| ss.source_files = - 'swift/Sources/Types/*.swift', - 'swift/Sources/Generated/Enums/*.swift', - 'swift/Sources/Generated/Protobuf/*.swift' - ss.dependency 'SwiftProtobuf' + 'Sources/Types/*.swift', + 'Sources/Generated/Enums/*.swift', + 'Sources/Generated/Protobuf/*.swift' + ss.dependency 'WalletCoreSwiftProtobuf' end - s.subspec 'Core' do |ss| - ss.preserve_paths = 'build/ios/*.a' - ss.vendored_libraries = 'build/ios/*.a' - ss.exclude_files = 'swift/Sources/Generated/WalletCore.h' + ss.vendored_frameworks = '*.xcframework' + ss.exclude_files = 'Sources/Generated/WalletCore.h' ss.source_files = 'include/**/*.h', - 'swift/Sources/*.{swift,h,m,cpp}', - 'swift/Sources/Extensions/*.swift', - 'swift/Sources/Generated/*.{swift,h}' + 'Sources/*.{swift,h,m,cpp}', + 'Sources/Extensions/*.swift', + 'Sources/Generated/*.{swift,h}' ss.public_header_files = 'include/**/*.h', - 'swift/Sources/*.h' + 'Sources/*.h' ss.libraries = 'c++' - ss.xcconfig = { - 'OTHER_LDFLAGS' => '$(inherited) -fprofile-instr-generate' - } - ss.pod_target_xcconfig = { - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' - } - ss.user_target_xcconfig = { - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' - } ss.dependency 'TrustWalletCore/Types' end end -PODSPEC -file = File.new('build/TrustWalletCore.podspec', 'w') -file.write(podspec) -file.close +EOF -puts "Done. running 'pod trunk push --allow-warnings #{file.path}'" +echo "generated podspec: " +cat ${podspec} -stdout, stderr, status = Open3.capture3("pod trunk push --verbose --allow-warnings #{file.path}") -if status != 0 - STDERR.puts stdout - STDERR.puts stderr - exit 1 -end +# pod trunk push +echo "Done. running: pod trunk push --allow-warnings ${podspec}" + +pod trunk push --verbose --allow-warnings ${podspec} + +popd diff --git a/tools/ios-test b/tools/ios-test index 26489644a12..3c95f4ec0bb 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -3,9 +3,16 @@ # This script runs the iOS tests. set -e +set -o pipefail + +tools/xcodegen pushd swift -xcodegen -pod install -fastlane scan --workspace TrustWalletCore.xcworkspace --scheme TrustWalletCore --sdk iphonesimulator --device='iPhone 11' --clean + +xcodebuild -workspace TrustWalletCore.xcworkspace \ + -scheme WalletCore \ + -sdk iphonesimulator \ + -destination "platform=iOS Simulator,name=iPhone 16,OS=18.5" \ + test | xcbeautify + popd diff --git a/tools/ios-xcframework b/tools/ios-xcframework new file mode 100755 index 00000000000..87acf8bc772 --- /dev/null +++ b/tools/ios-xcframework @@ -0,0 +1,14 @@ +#!/bin/bash +# +# This script builds dynamic WalletCore and WalletCoreSwiftProtobuf xcframework for SPM and CocoaPods +# + +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 new file mode 100755 index 00000000000..3097364c046 --- /dev/null +++ b/tools/ios-xcframework-release @@ -0,0 +1,92 @@ +#!/bin/bash + +set -o pipefail +set -e + +# load release library code +base_dir=$(dirname $0) +source ${base_dir}/library + +# version to release +version=$(wc_read_version $1) +echo "release version ${version}" + +pushd ${base_dir}/../swift/build + +# archive xcframeworks +core_zip_filename="WalletCore.xcframework.zip" +core_dsyms_filename="WalletCore.xcframework.dSYM.zip" +protobuf_zip_filename="WalletCoreSwiftProtobuf.xcframework.zip" +protobuf_dsyms_filename="WalletCoreSwiftProtobuf.xcframework.dSYM.zip" + +rm -rf ${core_zip_filename} ${core_dsyms_filename} ${protobuf_zip_filename} ${protobuf_dsyms_filename} + +zip -r ${core_dsyms_filename} WalletCore.dSYMs + +zip -r ${core_zip_filename} WalletCore.xcframework +core_hash=$(/usr/bin/shasum -a 256 ${core_zip_filename} | awk '{printf $1}') + +zip -r ${protobuf_dsyms_filename} WalletCoreSwiftProtobuf.dSYMs + +zip -r ${protobuf_zip_filename} WalletCoreSwiftProtobuf.xcframework +protobuf_hash=$(/usr/bin/shasum -a 256 ${protobuf_zip_filename} | awk '{printf $1}') + +# upload to release +echo "Upload asset $core_zip_filename" +core_download_url=$(wc_upload_asset ${version} ${core_zip_filename}) +echo "wallet core url is: ${core_download_url}" + +echo "Upload asset $protobuf_zip_filename" +protobuf_download_url=$(wc_upload_asset ${version} ${protobuf_zip_filename}) +echo "swift protobuf url is: ${protobuf_download_url}" + +# create Package.swift + +package_swift="Package.swift" +cat > $package_swift < 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..1586f8fc356 --- /dev/null +++ b/tools/rust-bindgen @@ -0,0 +1,152 @@ +#!/bin/bash + +set -e + +source "$(dirname $0)/parse_args" "$@" +source "$(dirname $0)/android-sdk" + +if isHelp; then + echo "usage: rust-bindgen [-h | --help] [all | native | wasm | android | ios]" + echo "" + echo "Generate Rust bindings for target platforms specified in arguments" + echo "You can specify multiple targets at once" + exit 0 +fi + +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 + +if isTargetSpecified "native"; then + echo "Generating Native target" + cargo build --release --lib +fi + +export RUSTFLAGS="-Zlocation-detail=none" + +if isTargetSpecified "wasm"; then + echo "Generating WASM target" + + source ../emsdk/emsdk_env.sh + cargo build -Z build-std=std,panic_abort --target wasm32-unknown-emscripten --release --lib +fi + +if isTargetSpecified "android"; 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 isTargetSpecified "ios" && [[ $(uname -s) == "Darwin" ]]; 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/ + +echo "Generating C++ files..." +pushd codegen-v2 && cargo run -- cpp && popd + +if isTargetSpecified "ios" && [[ $(uname -s) == "Darwin" ]]; 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..e27775e761e --- /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 lcov.info +else + cargo llvm-cov test --workspace --exclude wallet_core_bin --lcov --output-path lcov.info +fi + +popd + +tools/check-coverage rust/coverage.stats rust/lcov.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..05cb5f47638 --- /dev/null +++ b/tools/rust-test @@ -0,0 +1,28 @@ +#!/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 +elif [[ "$1" == "doc" ]]; then + cargo test --doc +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 new file mode 100755 index 00000000000..2fa483726a2 --- /dev/null +++ b/tools/wasm-build @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +base_dir=$(cd `dirname $0`; pwd) +src_dir=${base_dir}/.. + +if [[ -z ${EMSDK} ]]; then + source ${src_dir}/emsdk/emsdk_env.sh +fi + +build_folder=${src_dir}/wasm-build +boost_dir=${EMSDK}/upstream/emscripten/system/include +pushd ${src_dir} + +# cmake +cmake -Bwasm-build -DBoost_INCLUDE_DIR=${boost_dir} -DTW_COMPILE_WASM=ON -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake + +# make +make -j8 -Cwasm-build + +popd diff --git a/tools/wasm-set-version b/tools/wasm-set-version new file mode 100755 index 00000000000..ca3828fd0c1 --- /dev/null +++ b/tools/wasm-set-version @@ -0,0 +1,13 @@ +#!/bin/bash + +base_dir=$(cd `dirname $0`; pwd) +src_dir=${base_dir}/.. +package_json=${src_dir}/wasm/package.json +version="$1" + +if [ -z "${version}" ]; then + version=`git describe --long --tags | cut -f 1 -d "-"` +fi + +new_package_json=$(jq --arg tag "${version}" '.version = $tag' ${package_json}) +echo ${new_package_json} | jq . > ${package_json} diff --git a/tools/xcodegen b/tools/xcodegen new file mode 100755 index 00000000000..9271e9dce20 --- /dev/null +++ b/tools/xcodegen @@ -0,0 +1,23 @@ +#!/bin/bash +# +# This script generates the Xcode project using xcodegen. + +pushd swift + +xcodegen + +# Update project version from 70 to 56 +# This is a workaround for a bug in xcodegen 2.44.0 that causes pod install will fail with error: +# ArgumentError - [Xcodeproj] Unable to find compatibility version string for object version `70`. +echo "Updating project version from 70 to 56..." +find . -name "project.pbxproj" -type f | while read -r file; do + if grep -q "objectVersion = 70;" "$file" || grep -q "preferredProjectObjectVersion = 70;" "$file"; then + sed -i '' 's/objectVersion = 70;/objectVersion = 56;/g' "$file" + sed -i '' 's/preferredProjectObjectVersion = 70;/preferredProjectObjectVersion = 56;/g' "$file" + echo "✓ project.pbxproj objectVersion in $file to 56" + fi +done + +pod install + +popd diff --git a/trezor-crypto/CMakeLists.txt b/trezor-crypto/CMakeLists.txt index 1bceb18e6aa..414c6a618e2 100644 --- a/trezor-crypto/CMakeLists.txt +++ b/trezor-crypto/CMakeLists.txt @@ -1,5 +1,32 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + +set(TW_WARNING_FLAGS + -W + -Wall + -Wextra + -Wimplicit-function-declaration + -Wredundant-decls + -Wstrict-prototypes + -Wundef + -Wshadow + -Wpointer-arith + -Wformat + -Wreturn-type + -Wsign-compare + -Wmultichar + -Wformat-nonliteral + -Winit-self + -Wuninitialized + -Wformat-security + -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 @@ -29,32 +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 ) -target_compile_options(TrezorCrypto - PRIVATE - -W - -Wall - -Wextra - -Wimplicit-function-declaration - -Wredundant-decls - -Wstrict-prototypes - -Wundef - -Wshadow - -Wpointer-arith - -Wformat - -Wreturn-type - -Wsign-compare - -Wmultichar - -Wformat-nonliteral - -Winit-self - -Wuninitialized - -Wformat-security - -Wno-missing-braces - -Werror -) +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 AND NOT TW_COMPILE_JAVA AND NOT FLUTTER) + add_subdirectory(crypto/tests) + endif() +endif() + +target_compile_options(TrezorCrypto PRIVATE ${TW_WARNING_FLAGS} -Werror PUBLIC -Wno-deprecated-volatile) target_include_directories(TrezorCrypto PUBLIC @@ -64,13 +80,10 @@ target_include_directories(TrezorCrypto src ) -if(NOT ANDROID AND NOT IOS_PLATFORM) - add_subdirectory(crypto/tests) -endif() - -install(TARGETS TrezorCrypto - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) +install( + TARGETS TrezorCrypto + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 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 02edc629981..4d821a8cf69 100644 --- a/trezor-crypto/crypto/bip32.c +++ b/trezor-crypto/crypto/bip32.c @@ -48,33 +48,12 @@ #include "nem.h" #endif #if USE_CARDANO -#include +#include #endif #include -#define CARDANO_MAX_NODE_DEPTH 1048576 - -// [wallet-core] -const curve_info ed25519_hd_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_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, @@ -213,42 +192,6 @@ int hdnode_from_seed(const uint8_t *seed, int seed_len, const char *curve, return 1; } -// [wallet-core] -int hdnode_from_seed_hd(const uint8_t *seed, int seed_len, const char* curve, HDNode *out) -{ - CONFIDENTIAL uint8_t I[32 + 32]; - uint8_t tmp = 0x01; - memzero(out, sizeof(HDNode)); - out->depth = 0; - out->child_num = 0; - out->curve = get_curve_by_name(curve); - if (out->curve == 0) { - return 0; - } - CONFIDENTIAL HMAC_SHA256_CTX ctxs256; - hmac_sha256_Init(&ctxs256, (const uint8_t*) out->curve->bip32_name, strlen(out->curve->bip32_name)); - hmac_sha256_Update(&ctxs256, &tmp, 1); - hmac_sha256_Update(&ctxs256, seed, seed_len); - hmac_sha256_Final(&ctxs256, out->chain_code); - CONFIDENTIAL HMAC_SHA512_CTX ctx; - hmac_sha512_Init(&ctx, (const uint8_t*) out->curve->bip32_name, strlen(out->curve->bip32_name)); - hmac_sha512_Update(&ctx, seed, seed_len); - hmac_sha512_Final(&ctx, I); - while ((I[31] & 0b00100000) != 0) { - hmac_sha512_Init(&ctx, (const uint8_t*) out->curve->bip32_name, strlen(out->curve->bip32_name)); - hmac_sha512_Update(&ctx, I, sizeof(I)); - hmac_sha512_Final(&ctx, I); - } - I[0] &= 248; - I[31] &= 127; - I[31] |= 64; - memcpy(out->private_key, I, 32); - memcpy(out->private_key_extension, I + 32, 32); - hdnode_fill_public_key(out); - memzero(I, sizeof(I)); - return 1; -} - uint32_t hdnode_fingerprint(HDNode *node) { uint8_t digest[32] = {0}; uint32_t fingerprint = 0; @@ -261,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); @@ -273,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); @@ -327,155 +278,16 @@ 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, @@ -576,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) { @@ -643,32 +462,38 @@ 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) { ed25519_publickey(node->private_key, node->public_key + 1); - } else if (node->curve == &ed25519_hd_info) { // [wallet-core] - ed25519_publickey_ext(node->private_key, node->private_key_extension, node->public_key + 1); } else if (node->curve == &ed25519_sha3_info) { ed25519_publickey_sha3(node->private_key, node->public_key + 1); #if USE_KECCAK @@ -679,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 @@ -697,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); @@ -717,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); } @@ -826,19 +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); - } else if (node->curve == &ed25519_hd_info) { // [wallet-core] - 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 @@ -986,9 +812,6 @@ const curve_info *get_curve_by_name(const char *curve_name) { return &ed25519_info; } // [wallet-core] - if (strcmp(curve_name, ED25519_HD_NAME) == 0) { - return &ed25519_hd_info; - } if (strcmp(curve_name, ED25519_CARDANO_NAME) == 0) { return &ed25519_cardano_info; } diff --git a/trezor-crypto/crypto/bip39.c b/trezor-crypto/crypto/bip39.c index 9f2728f7d4a..fc61539c938 100644 --- a/trezor-crypto/crypto/bip39.c +++ b/trezor-crypto/crypto/bip39.c @@ -44,25 +44,37 @@ 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 -const char *mnemonic_generate(int strength) { +// [wallet-core] Added output buffer +const char *mnemonic_generate(int strength, char *buf, int buflen) { if (strength % 32 || strength < 128 || strength > 256) { return 0; } uint8_t data[32] = {0}; random_buffer(data, 32); - const char *r = mnemonic_from_data(data, strength / 8); + const char *r = mnemonic_from_data(data, strength / 8, buf, buflen); memzero(data, sizeof(data)); return r; } -CONFIDENTIAL char mnemo[24 * 10]; +// [wallet-core] Global buffer no longer used +//CONFIDENTIAL char mnemo[24 * 10]; -const char *mnemonic_from_data(const uint8_t *data, int len) { +// [wallet-core] Added output buffer +const char *mnemonic_from_data(const uint8_t *data, int len, char *buf, int buflen) { if (len % 4 || len < 16 || len > 32) { return 0; } + // [wallet-core] Check provided buffer validity, size + if (!buf || buflen < (BIP39_MAX_WORDS * (BIP39_MAX_WORD_LENGTH + 1))) { + return 0; + } uint8_t bits[32 + 1] = {0}; @@ -75,7 +87,7 @@ const char *mnemonic_from_data(const uint8_t *data, int len) { int mlen = len * 3 / 4; int i = 0, j = 0, idx = 0; - char *p = mnemo; + char *p = buf; // [wallet-core] for (i = 0; i < mlen; i++) { idx = 0; for (j = 0; j < 11; j++) { @@ -89,10 +101,11 @@ const char *mnemonic_from_data(const uint8_t *data, int len) { } memzero(bits, sizeof(bits)); - return mnemo; + return buf; // [wallet-core] } -void mnemonic_clear(void) { memzero(mnemo, sizeof(mnemo)); } +// [wallet-core] No longer used +//void mnemonic_clear(void) { memzero(mnemo, sizeof(mnemo)); } int mnemonic_to_bits(const char *mnemonic, uint8_t *bits) { if (!mnemonic) { @@ -241,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]); @@ -269,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/blake2b.c b/trezor-crypto/crypto/blake2b.c index 5ec488bfe00..5b71c58de65 100644 --- a/trezor-crypto/crypto/blake2b.c +++ b/trezor-crypto/crypto/blake2b.c @@ -93,7 +93,7 @@ static void blake2b_init0( blake2b_state *S ) } /* init xors IV with input parameter block */ -int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) +static int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) { const uint8_t *p = ( const uint8_t * )( P ); size_t i = 0; @@ -110,7 +110,7 @@ int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) /* Sequential blake2b initialization */ -int blake2b_Init( blake2b_state *S, size_t outlen ) +int tc_blake2b_Init( blake2b_state *S, size_t outlen ) { blake2b_param P[1] = {0}; @@ -131,7 +131,7 @@ int blake2b_Init( blake2b_state *S, size_t outlen ) return blake2b_init_param( S, P ); } -int blake2b_InitPersonal( blake2b_state *S, size_t outlen, const void *personal, size_t personal_len) +int tc_blake2b_InitPersonal( blake2b_state *S, size_t outlen, const void *personal, size_t personal_len) { blake2b_param P[1] = {0}; @@ -153,7 +153,7 @@ int blake2b_InitPersonal( blake2b_state *S, size_t outlen, const void *personal, return blake2b_init_param( S, P ); } -int blake2b_InitKey( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) +int tc_blake2b_InitKey( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) { blake2b_param P[1] = {0}; @@ -180,7 +180,7 @@ int blake2b_InitKey( blake2b_state *S, size_t outlen, const void *key, size_t ke uint8_t block[BLAKE2B_BLOCKBYTES] = {0}; memzero( block, BLAKE2B_BLOCKBYTES ); memcpy( block, key, keylen ); - blake2b_Update( S, block, BLAKE2B_BLOCKBYTES ); + tc_blake2b_Update( S, block, BLAKE2B_BLOCKBYTES ); memzero( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ } return 0; @@ -254,7 +254,7 @@ static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOC #undef G #undef ROUND -int blake2b_Update( blake2b_state *S, const void *pin, size_t inlen ) +int tc_blake2b_Update( blake2b_state *S, const void *pin, size_t inlen ) { const unsigned char * in = (const unsigned char *)pin; if( inlen > 0 ) @@ -281,7 +281,7 @@ int blake2b_Update( blake2b_state *S, const void *pin, size_t inlen ) return 0; } -int blake2b_Final( blake2b_state *S, void *out, size_t outlen ) +int tc_blake2b_Final( blake2b_state *S, void *out, size_t outlen ) { uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; size_t i = 0; @@ -305,30 +305,30 @@ int blake2b_Final( blake2b_state *S, void *out, size_t outlen ) return 0; } -int blake2b(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen) +int tc_blake2b(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen) { BLAKE2B_CTX ctx; - if (0 != blake2b_Init(&ctx, outlen)) return -1; - if (0 != blake2b_Update(&ctx, msg, msg_len)) return -1; - if (0 != blake2b_Final(&ctx, out, outlen)) return -1; + if (0 != tc_blake2b_Init(&ctx, outlen)) return -1; + if (0 != tc_blake2b_Update(&ctx, msg, msg_len)) return -1; + if (0 != tc_blake2b_Final(&ctx, out, outlen)) return -1; return 0; } // [wallet-core] -int blake2b_Personal(const uint8_t *msg, uint32_t msg_len, const void *personal, size_t personal_len, void *out, size_t outlen) +int tc_blake2b_Personal(const uint8_t *msg, uint32_t msg_len, const void *personal, size_t personal_len, void *out, size_t outlen) { BLAKE2B_CTX ctx; - if (0 != blake2b_InitPersonal(&ctx, outlen, personal, personal_len)) return -1; - if (0 != blake2b_Update(&ctx, msg, msg_len)) return -1; - if (0 != blake2b_Final(&ctx, out, outlen)) return -1; + if (0 != tc_blake2b_InitPersonal(&ctx, outlen, personal, personal_len)) return -1; + if (0 != tc_blake2b_Update(&ctx, msg, msg_len)) return -1; + if (0 != tc_blake2b_Final(&ctx, out, outlen)) return -1; return 0; } -int blake2b_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen) +int tc_blake2b_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen) { BLAKE2B_CTX ctx; - if (0 != blake2b_InitKey(&ctx, outlen, key, keylen)) return -1; - if (0 != blake2b_Update(&ctx, msg, msg_len)) return -1; - if (0 != blake2b_Final(&ctx, out, outlen)) return -1; + if (0 != tc_blake2b_InitKey(&ctx, outlen, key, keylen)) return -1; + if (0 != tc_blake2b_Update(&ctx, msg, msg_len)) return -1; + if (0 != tc_blake2b_Final(&ctx, out, outlen)) return -1; return 0; } 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 0e9b511e4e6..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,11 +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_HD_NAME[] = "ed25519-hd"; // [wallet-core] +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/hasher.c b/trezor-crypto/crypto/hasher.c index d6dd613e905..59c10181497 100644 --- a/trezor-crypto/crypto/hasher.c +++ b/trezor-crypto/crypto/hasher.c @@ -50,10 +50,10 @@ void hasher_InitParam(Hasher *hasher, HasherType type, const void *param, groestl512_Init(&hasher->ctx.groestl); break; case HASHER_BLAKE2B: - blake2b_Init(&hasher->ctx.blake2b, 32); + tc_blake2b_Init(&hasher->ctx.blake2b, 32); break; case HASHER_BLAKE2B_PERSONAL: - blake2b_InitPersonal(&hasher->ctx.blake2b, 32, hasher->param, + tc_blake2b_InitPersonal(&hasher->ctx.blake2b, 32, hasher->param, hasher->param_size); break; } @@ -90,7 +90,7 @@ void hasher_Update(Hasher *hasher, const uint8_t *data, size_t length) { break; case HASHER_BLAKE2B: case HASHER_BLAKE2B_PERSONAL: - blake2b_Update(&hasher->ctx.blake2b, data, length); + tc_blake2b_Update(&hasher->ctx.blake2b, data, length); break; } } @@ -132,7 +132,7 @@ void hasher_Final(Hasher *hasher, uint8_t hash[HASHER_DIGEST_LENGTH]) { break; case HASHER_BLAKE2B: case HASHER_BLAKE2B_PERSONAL: - blake2b_Final(&hasher->ctx.blake2b, hash, 32); + tc_blake2b_Final(&hasher->ctx.blake2b, hash, 32); break; } } diff --git a/trezor-crypto/crypto/nano.c b/trezor-crypto/crypto/nano.c index 15dc643fa13..54f4968d1ef 100644 --- a/trezor-crypto/crypto/nano.c +++ b/trezor-crypto/crypto/nano.c @@ -66,9 +66,9 @@ size_t nano_get_address( uint8_t checksum[NANO_CHECKSUM_LEN]; blake2b_state hash; - blake2b_Init(&hash, NANO_CHECKSUM_LEN); - blake2b_Update(&hash, public_key, sizeof(ed25519_public_key)); - blake2b_Final(&hash, checksum, NANO_CHECKSUM_LEN); + tc_blake2b_Init(&hash, NANO_CHECKSUM_LEN); + tc_blake2b_Update(&hash, public_key, sizeof(ed25519_public_key)); + tc_blake2b_Final(&hash, checksum, NANO_CHECKSUM_LEN); for (int i = 0; i < NANO_CHECKSUM_LEN; i++) { raw.data.checksum[NANO_CHECKSUM_LEN - (i + 1)] = checksum[i]; @@ -132,9 +132,9 @@ bool nano_validate_address( // Validate the checksum uint8_t checksum[NANO_CHECKSUM_LEN]; blake2b_state hash; - blake2b_Init(&hash, NANO_CHECKSUM_LEN); - blake2b_Update(&hash, raw.data.public_key, sizeof(ed25519_public_key)); - blake2b_Final(&hash, checksum, NANO_CHECKSUM_LEN); + tc_blake2b_Init(&hash, NANO_CHECKSUM_LEN); + tc_blake2b_Update(&hash, raw.data.public_key, sizeof(ed25519_public_key)); + tc_blake2b_Final(&hash, checksum, NANO_CHECKSUM_LEN); for (int i = 0; i < NANO_CHECKSUM_LEN; i++) { if (raw.data.checksum[NANO_CHECKSUM_LEN - (i + 1)] != checksum[i]) { 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/rand.c b/trezor-crypto/crypto/rand.c index 1b429432b13..9f22a0a5812 100644 --- a/trezor-crypto/crypto/rand.c +++ b/trezor-crypto/crypto/rand.c @@ -29,7 +29,7 @@ #include // [wallet-core] -uint32_t __attribute__((weak)) random32() { +uint32_t __attribute__((weak)) random32(void) { int randomData = open("/dev/urandom", O_RDONLY); if (randomData < 0) { return 0; 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/sha3.c b/trezor-crypto/crypto/sha3.c index a2563450dcb..0b01bfe3a14 100644 --- a/trezor-crypto/crypto/sha3.c +++ b/trezor-crypto/crypto/sha3.c @@ -26,7 +26,7 @@ #define I64(x) x##LL #define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) #define le2me_64(x) (x) -#define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) +#define IS_ALIGNED_64(p) (0 == (7 & ((long)(p)))) // [wallet-core] pointer/numerical type, for MacOS SDK 12.3 # define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) /* constants */ 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 53420c90f7d..0765cae1f44 100644 --- a/trezor-crypto/crypto/tests/CMakeLists.txt +++ b/trezor-crypto/crypto/tests/CMakeLists.txt @@ -1,3 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + enable_testing() find_library(check PATH ${CMAKE_SOURCE_DIR}/build/local/lib/pkgconfig NO_DEFAULT_PATH) @@ -5,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 307a67076dd..138dd253fa0 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, @@ -1837,6 +1837,99 @@ START_TEST(test_bip32_vector_3) { } END_TEST +// test vector 4 from +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-4 +START_TEST(test_bip32_vector_4) { + HDNode node, node2, node3; + uint32_t fingerprint; + char str[XPUB_MAXLEN]; + int r; + + // init m + hdnode_from_seed( + fromhex( + "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678"), + 32, SECP256K1_NAME, &node); + + // [Chain m] + fingerprint = 0; + ck_assert_int_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, + "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7f" + "RDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv"); + 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, + "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuy" + "u5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa"); + 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)); + + // [Chain m/0'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 0); + 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, + "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaAL" + "mPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G"); + 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, + "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xf" + "FEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m"); + 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)); + + // [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 + START_TEST(test_bip32_compare) { HDNode node1, node2, node3; int i, r; @@ -1850,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( @@ -1876,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); } @@ -1886,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); @@ -1898,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 @@ -1911,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; @@ -2045,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( @@ -2069,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( @@ -2088,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( @@ -2099,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( @@ -2109,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( @@ -2120,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( @@ -2130,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( @@ -2141,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( @@ -2151,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( @@ -2162,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( @@ -2172,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( @@ -2183,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( @@ -2193,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( @@ -2204,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( @@ -2227,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( @@ -2238,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( @@ -2249,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( @@ -2260,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( @@ -2271,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( @@ -2282,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( @@ -2293,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( @@ -2304,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( @@ -2315,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( @@ -2326,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( @@ -2337,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( @@ -2348,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( @@ -2367,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( @@ -2399,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( @@ -2425,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); } @@ -2445,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( @@ -2456,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( @@ -2467,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( @@ -2478,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( @@ -2489,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( @@ -2523,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( @@ -2542,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( @@ -2561,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( @@ -2580,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( @@ -2599,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( @@ -2618,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( @@ -2650,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( @@ -2670,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( @@ -2690,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( @@ -2710,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( @@ -2730,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( @@ -2750,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( @@ -2777,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( @@ -2788,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( @@ -2803,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)); @@ -2820,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( @@ -2831,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( @@ -2845,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)); @@ -2862,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( @@ -2873,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( @@ -2887,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)); @@ -2904,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( @@ -2915,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( @@ -2929,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)); @@ -2946,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( @@ -2957,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( @@ -2971,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)); @@ -2988,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( @@ -2999,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( @@ -3013,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)); @@ -3050,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( @@ -3061,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( @@ -3075,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)); @@ -3093,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( @@ -3104,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( @@ -3118,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)); @@ -3136,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( @@ -3147,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( @@ -3161,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)); @@ -3179,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( @@ -3190,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( @@ -3204,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)); @@ -3222,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( @@ -3233,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( @@ -3247,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)); @@ -3265,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( @@ -3276,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( @@ -3290,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)); @@ -3315,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( @@ -3338,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( @@ -3373,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" @@ -3386,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" @@ -3399,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" @@ -3412,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" @@ -3425,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, @@ -3432,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" @@ -3445,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" @@ -3458,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" @@ -3472,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" @@ -3485,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" @@ -3500,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" @@ -3515,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" @@ -3523,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" @@ -3531,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" @@ -3539,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" @@ -3547,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); \ @@ -3597,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) { @@ -3606,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", @@ -3643,7 +3873,7 @@ START_TEST(test_aes) { } // CBC - const char *cbc_vector[] = { + static const char *cbc_vector[] = { // iv plain cipher "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", @@ -3689,7 +3919,7 @@ START_TEST(test_aes) { } // CFB - const char *cfb_vector[] = { + static const char *cfb_vector[] = { "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", "DC7E84BFDA79164B7ECD8486985D3860", @@ -3734,7 +3964,7 @@ START_TEST(test_aes) { } // OFB - const char *ofb_vector[] = { + static const char *ofb_vector[] = { "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", "dc7e84bfda79164b7ecd8486985d3860", @@ -3779,7 +4009,7 @@ START_TEST(test_aes) { } // CTR - const char *ctr_vector[] = { + static const char *ctr_vector[] = { // plain cipher "6bc1bee22e409f96e93d7e117393172a", "601ec313775789a5b7a7f504bbf3d228", @@ -4104,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; @@ -4423,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 @@ -4430,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[] = { @@ -4519,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); } } @@ -4528,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( @@ -4536,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; + tc_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(tc_blake2b_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(tc_blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(tc_blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(tc_blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(tc_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( + tc_blake2b_InitPersonal(&ctx, sizeof(digest), tests[i].personal, + strlen(tests[i].personal)), + 0); + ck_assert_int_eq(tc_blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(tc_blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(tc_blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(tc_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, @@ -4592,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 @@ -4786,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", @@ -4923,8 +5281,10 @@ START_TEST(test_mnemonic) { a = vectors; b = vectors + 1; c = vectors + 2; + int buf_size = 308; + char buf[buf_size]; while (*a && *b && *c) { - m = mnemonic_from_data(fromhex(*a), strlen(*a) / 2); + 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); @@ -4936,12 +5296,13 @@ START_TEST(test_mnemonic) { a += 3; b += 3; c += 3; + memzero(buf, buf_size); } } 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 " @@ -4997,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 " @@ -5120,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", @@ -5211,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; @@ -5222,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); @@ -5230,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"}, @@ -5249,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; @@ -5261,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[] = { @@ -5290,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[] = { @@ -5331,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]; @@ -5863,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; @@ -5923,6 +6282,11 @@ START_TEST(test_ecdsa_der) { "00000000000000000000000000000000000000000000000000000000000000ff", "3008020200ee020200ff", }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "3006020100020100", + }, }; uint8_t sig[64]; @@ -6072,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 @@ -6103,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)); @@ -6135,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" @@ -6211,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); @@ -6227,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; @@ -6429,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); @@ -6454,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; @@ -6661,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", @@ -6785,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 } @@ -6859,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", @@ -6878,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; @@ -6887,6 +7253,7 @@ START_TEST(test_output_script) { END_TEST #if USE_ETHEREUM + START_TEST(test_ethereum_pubkeyhash) { uint8_t pubkeyhash[20]; int res; @@ -6988,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++; @@ -7018,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; @@ -7182,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; @@ -7356,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; @@ -7617,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); } } @@ -8327,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[] = { @@ -8651,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", @@ -8669,256 +9037,6 @@ START_TEST(test_compress_coords) { } END_TEST -// [wallet-core] -START_TEST(test_bip32_hd_hdnode_vector_1) -{ - HDNode node; - - uint8_t seed[66]; - mnemonic_to_seed("ring crime symptom enough erupt lady behave ramp apart settle citizen junk", "", seed, 0); - hdnode_from_seed_hd(seed, 64, ED25519_HD_NAME, &node); - - ck_assert_mem_eq(node.chain_code, fromhex("683eabed80a3fa7a0255f6aa411577340c8a5bfeac3b763cf410f397fb1e8082"), 32); - ck_assert_mem_eq(node.private_key, fromhex("d05fbe16dc827df7401690268928789886d8c401f5715a1664f3c48197707659"), 32); - ck_assert_mem_eq(node.private_key_extension, fromhex("598b50eb5883cab909add281bb89186c5c5a5c3a953f8d92382590777597a1aa"), 32); - hdnode_fill_public_key(&node); - ck_assert_mem_eq(node.public_key + 1, fromhex("ace6781a4165fdc8c582cf2dcd037faae746bc6ac3d071a263df7dbd0a528251"), 32); -} -END_TEST - -// [wallet-core] -START_TEST(test_bip32_hd_hdnode_vector_2) -{ - HDNode node; - - uint8_t seed[66]; - mnemonic_to_seed("ring crime symptom enough erupt lady behave ramp apart settle citizen junk", "", seed, 0); - hdnode_from_seed_hd(seed, 64, ED25519_HD_NAME, &node); - - uint32_t index = 44 | 0x80000000; - hdnode_private_ckd_cardano(&node, index); - - ck_assert_mem_eq(node.chain_code, fromhex("cd46ff18d03a37e28352ba0bd01b91327734aabebf658ae8a87dd216d99c0fef"), 32); - ck_assert_mem_eq(node.private_key, fromhex("a04d196954e1a544f31c5aed15a2ecc7c7905cfb9021f0e979771d3799707659"), 32); - ck_assert_mem_eq(node.private_key_extension, fromhex("ff2545c22345d40034af9b6dc1cada9b9d843cd6f9a6d8d7b05a45ba15ad2d59"), 32); - hdnode_fill_public_key(&node); - ck_assert_mem_eq(node.public_key + 1, fromhex("5155f089bdb44b6c8df78a3bab2ca53897bc5ad3d349e4f1c7e1c1c4e74458ce"), 32); -} -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) { @@ -8935,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 @@ -8998,13 +9117,14 @@ Suite *test_suite(void) { tcase_add_test(tc, test_bip32_vector_1); tcase_add_test(tc, test_bip32_vector_2); tcase_add_test(tc, test_bip32_vector_3); + tcase_add_test(tc, test_bip32_vector_4); tcase_add_test(tc, test_bip32_compare); tcase_add_test(tc, test_bip32_optimized); #if USE_BIP32_CACHE tcase_add_test(tc, test_bip32_cache_1); tcase_add_test(tc, test_bip32_cache_2); - suite_add_tcase(s, tc); #endif + suite_add_tcase(s, tc); tc = tcase_create("bip32-nist"); tcase_add_test(tc, test_bip32_nist_seed); @@ -9031,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"); @@ -9086,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); @@ -9109,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); @@ -9117,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); @@ -9184,12 +9309,6 @@ Suite *test_suite(void) { tcase_add_test(tc, test_output_script); suite_add_tcase(s, tc); - // [wallet-core] - tc = tcase_create("bip32-hd"); - tcase_add_test(tc, test_bip32_hd_hdnode_vector_1); - tcase_add_test(tc, test_bip32_hd_hdnode_vector_2); - suite_add_tcase(s, tc); - #if USE_ETHEREUM tc = tcase_create("ethereum_pubkeyhash"); tcase_add_test(tc, test_ethereum_pubkeyhash); @@ -9251,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 @@ -9293,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 d80850a49c3..4b698bcc600 100644 --- a/trezor-crypto/include/TrezorCrypto/bip32.h +++ b/trezor-crypto/include/TrezorCrypto/bip32.h @@ -74,22 +74,11 @@ int hdnode_from_xprv(uint32_t depth, uint32_t child_num, int hdnode_from_seed(const uint8_t *seed, int seed_len, const char *curve, HDNode *out); -// [wallet-core] -int hdnode_from_seed_hd(const uint8_t *seed, int seed_len, const char *curve, HDNode *out); - #define hdnode_private_ckd_prime(X, I) \ hdnode_private_ckd((X), ((I) | 0x80000000)) 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); @@ -104,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); @@ -154,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 8f2a07efbed..46ec5b0229c 100644 --- a/trezor-crypto/include/TrezorCrypto/bip39.h +++ b/trezor-crypto/include/TrezorCrypto/bip39.h @@ -24,18 +24,29 @@ #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 -const char *mnemonic_generate(int strength); // strength in bits -const char *mnemonic_from_data(const uint8_t *data, int len); +#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 +const char *mnemonic_from_data(const uint8_t *data, int datalen, char *buf, int buflen); void mnemonic_clear(void); int mnemonic_check(const char *mnemonic); @@ -48,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/blake2b.h b/trezor-crypto/include/TrezorCrypto/blake2b.h index c002dd7afc5..c4e20bb7c34 100644 --- a/trezor-crypto/include/TrezorCrypto/blake2b.h +++ b/trezor-crypto/include/TrezorCrypto/blake2b.h @@ -33,16 +33,16 @@ typedef struct __blake2b_state #define BLAKE2B_DIGEST_LENGTH BLAKE2B_OUTBYTES #define BLAKE2B_KEY_LENGTH BLAKE2B_KEYBYTES -int blake2b_Init(blake2b_state *S, size_t outlen); -int blake2b_InitKey(blake2b_state *S, size_t outlen, const void *key, size_t keylen); -int blake2b_InitPersonal(blake2b_state *S, size_t outlen, const void *personal, size_t personal_len); -int blake2b_Update(blake2b_state *S, const void *pin, size_t inlen); -int blake2b_Final(blake2b_state *S, void *out, size_t outlen); +int tc_blake2b_Init(blake2b_state *S, size_t outlen); +int tc_blake2b_InitKey(blake2b_state *S, size_t outlen, const void *key, size_t keylen); +int tc_blake2b_InitPersonal(blake2b_state *S, size_t outlen, const void *personal, size_t personal_len); +int tc_blake2b_Update(blake2b_state *S, const void *pin, size_t inlen); +int tc_blake2b_Final(blake2b_state *S, void *out, size_t outlen); -int blake2b(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen); +int tc_blake2b(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen); // [wallet-core] -int blake2b_Personal(const uint8_t *msg, uint32_t msg_len, const void *personal, size_t personal_len, void *out, size_t outlen); -int blake2b_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen); +int tc_blake2b_Personal(const uint8_t *msg, uint32_t msg_len, const void *personal, size_t personal_len, void *out, size_t outlen); +int tc_blake2b_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen); #ifdef __cplusplus } /* extern "C" */ 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 b62f351d3af..d8d423563c6 100644 --- a/trezor-crypto/include/TrezorCrypto/curves.h +++ b/trezor-crypto/include/TrezorCrypto/curves.h @@ -35,17 +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_HD_NAME[]; +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-hash-custom-blake2b.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom-blake2b.h index 9c46630a9bd..68706f37795 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom-blake2b.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom-blake2b.h @@ -15,9 +15,9 @@ #include #define ed25519_hash_context BLAKE2B_CTX -#define ed25519_hash_init(ctx) blake2b_Init(ctx, 64) -#define ed25519_hash_update(ctx, in, inlen) blake2b_Update((ctx), (in), (inlen)) -#define ed25519_hash_final(ctx, hash) blake2b_Final((ctx), (hash), 64) -#define ed25519_hash(hash, in, inlen) blake2b((in), (inlen), (hash), 64) +#define ed25519_hash_init(ctx) tc_blake2b_Init(ctx, 64) +#define ed25519_hash_update(ctx, in, inlen) tc_blake2b_Update((ctx), (in), (inlen)) +#define ed25519_hash_final(ctx, hash) tc_blake2b_Final((ctx), (hash), 64) +#define ed25519_hash(hash, in, inlen) tc_blake2b((in), (inlen), (hash), 64) #endif // ED25519_HASH_CUSTOM 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/groestl_internal.h b/trezor-crypto/include/TrezorCrypto/groestl_internal.h index 3c675d8eac9..73d0156c379 100644 --- a/trezor-crypto/include/TrezorCrypto/groestl_internal.h +++ b/trezor-crypto/include/TrezorCrypto/groestl_internal.h @@ -116,6 +116,7 @@ typedef int64_t sph_s64; #endif +#define SPH_LITTLE_ENDIAN 1 // [wallet-core] #if defined SPH_DETECT_LITTLE_ENDIAN && !defined SPH_LITTLE_ENDIAN #define SPH_LITTLE_ENDIAN SPH_DETECT_LITTLE_ENDIAN 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/typescript/.gitignore b/typescript/.gitignore deleted file mode 100644 index 1ea6743a064..00000000000 --- a/typescript/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -src/generated/ diff --git a/typescript/codegen/bin/codegen b/typescript/codegen/bin/codegen deleted file mode 100755 index bddbe68a570..00000000000 --- a/typescript/codegen/bin/codegen +++ /dev/null @@ -1,57 +0,0 @@ -#! /usr/bin/env node -const fs = require('fs').promises; -const path = require('path'); -const ejs = require('ejs'); -const prettier = require("prettier"); - -const main = async () => { - const coins = require('../../../coins.json') - coins.forEach((coin) => { - if (!coin['slip44']) { - coin.slip44 = Number(coin['derivationPath'].split('/')[2].replace('\'', '')); - } - }) - await generateCoinType(coins); -}; - -const generateCoinType = async (coins) => { - const methods = [ - { - name: 'id', - returnType: 'string', - body: (coin) => `return '${coin.id}'` - }, - { - name: 'decimals', - returnType: 'number', - body: (coin) => `return ${coin.decimals}` - }, - { - name: 'name', - returnType: 'string', - body: (coin) => `return '${coin.name}'` - }, - { - name: 'derivationPath', - returnType: 'string', - body: (coin) => `return "${coin.derivationPath}"` - }, - { - name: 'symbol', - returnType: 'string', - body: (coin) => `return '${coin.symbol}'` - }, - { - name: 'slip44', - returnType: 'number', - body: (coin) => `return ${coin.slip44}` - } - ]; - - const template = await fs.readFile(path.resolve(__dirname, '../templates/core_types.ejs'), 'utf8'); - let data = await ejs.render(template, { coins, methods }); - data = await prettier.format(data, { parser: 'typescript', singleQuote: true, trailingComma: 'es5' }); - await fs.writeFile(path.resolve(__dirname, '../../src/generated/core_types.ts'), data); -}; - -main(); diff --git a/typescript/codegen/templates/core_types.ejs b/typescript/codegen/templates/core_types.ejs deleted file mode 100644 index 96eb09c76ae..00000000000 --- a/typescript/codegen/templates/core_types.ejs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -export enum CoinType { - <% coins.forEach((coin) => { -%> - <%-coin.id%> = <%-coin.coinId%>, - <% }) %> -} - -export namespace CoinType { - <% methods.forEach((method) => { -%> - export function <%-method.name%>(coin: CoinType): <%-method.returnType%> { - switch (coin) { - <% coins.forEach((coin) => { -%> - case CoinType.<%-coin.id%>: <%-method.body(coin)%>; - <% }) %> - } - } - <% }) %> -} diff --git a/typescript/package.json b/typescript/package.json deleted file mode 100644 index a04d833bfeb..00000000000 --- a/typescript/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@trustwallet/wallet-core", - "version": "2.2.9", - "description": "wallet core types and protobuf messages", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "test": "mocha -r ts-node/register tests/**/*.test.ts", - "generate": "yarn codegen:coin && yarn codegen:js && yarn codegen:ts", - "codegen:coin": "codegen/bin/codegen", - "codegen:js": "pbjs -t static-module '../src/proto/*.proto' --no-delimited --force-long -o src/generated/core_proto.js", - "codegen:ts": "pbts -o src/generated/core_proto.d.ts src/generated/core_proto.js", - "clean": "rm -rf dist src/generated && mkdir -p dist/generated src/generated", - "build": "yarn clean && yarn generate && cp src/generated/core_proto.d.ts src/generated/core_proto.js dist/generated && tsc --skipLibCheck" - }, - "repository": { - "type": "git", - "url": "git://github.com/trustwallet/wallet-core.git" - }, - "author": "", - "license": "MIT", - "bugs": { - "url": "https://github.com/trustwallet/wallet-core/issues" - }, - "homepage": "https://github.com/trustwallet/wallet-core#readme", - "files": [ - "dist" - ], - "dependencies": { - "protobufjs": "^6.9.0" - }, - "devDependencies": { - "@types/chai": "^4.2.11", - "@types/mocha": "^7.0.2", - "buffer": "^5.6.0", - "chai": "^4.2.0", - "ejs": "^3.1.3", - "escodegen": "^1.14.3", - "jsdoc": "^3.6.4", - "mocha": "^8.0.1", - "prettier": "^2.0.5", - "ts-node": "^8.10.2", - "typescript": "^3.9.5" - } -} diff --git a/typescript/src/index.ts b/typescript/src/index.ts deleted file mode 100644 index 7fc83ba9b2e..00000000000 --- a/typescript/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 { CoinType } from './generated/core_types' -import { TW } from './generated/core_proto' - -export { TW, CoinType } diff --git a/typescript/tests/index.test.ts b/typescript/tests/index.test.ts deleted file mode 100644 index d81b957aa58..00000000000 --- a/typescript/tests/index.test.ts +++ /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. - -import 'mocha' -import { expect } from 'chai' -import { Buffer } from 'buffer' -import { TW, CoinType } from '../dist' - -describe('Wallet Core types tests', () => { - - it('test CoinType.ethereum', () => { - const coin = CoinType.ethereum; - expect(coin).to.equal(60) - expect(CoinType.id(coin)).to.equal('ethereum') - expect(CoinType.name(coin)).to.equal('Ethereum') - expect(CoinType.slip44(coin)).to.equal(60) - expect(CoinType.symbol(coin)).to.equal('ETH') - expect(CoinType.decimals(coin)).to.equal(18) - expect(CoinType.derivationPath(coin)).to.equal(`m/44'/60'/0'/0/0`) - }) - - it('test CoinType.bsc', () => { - const coin = CoinType.bsc; - expect(coin).to.equal(10000714) - expect(CoinType.id(coin)).to.equal('bsc') - expect(CoinType.name(coin)).to.equal('Smart Chain Legacy') - expect(CoinType.slip44(coin)).to.equal(714) - expect(CoinType.symbol(coin)).to.equal('BNB') - expect(CoinType.decimals(coin)).to.equal(18) - expect(CoinType.derivationPath(coin)).to.equal(`m/44'/714'/0'/0/0`) - }) - - it('test CoinType.smartchain', () => { - const coin = CoinType.smartchain; - expect(coin).to.equal(20000714) - expect(CoinType.id(coin)).to.equal('smartchain') - expect(CoinType.name(coin)).to.equal('Smart Chain') - expect(CoinType.slip44(coin)).to.equal(714) - expect(CoinType.symbol(coin)).to.equal('BNB') - expect(CoinType.decimals(coin)).to.equal(18) - expect(CoinType.derivationPath(coin)).to.equal(`m/44'/60'/0'/0/0`) - }) - - it('test Ethereum encoding SigningInput', () => { - const input = TW.Ethereum.Proto.SigningInput.create({ - toAddress: '0x3535353535353535353535353535353535353535', - chainId: Buffer.from('01', 'hex'), - nonce: Buffer.from('09', 'hex'), - gasPrice: Buffer.from('04a817c800', 'hex'), - gasLimit: Buffer.from('5208', 'hex'), - transaction: TW.Ethereum.Proto.Transaction.create({ - transfer: TW.Ethereum.Proto.Transaction.Transfer.create({ - amount: Buffer.from('0de0b6b3a7640000', 'hex') - }) - }), - privateKey: Buffer.from('4646464646464646464646464646464646464646464646464646464646464646', 'hex') - }); - - const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish() - expect(Buffer.from(encoded).toString('hex')).to.equal("0a01011201091a0504a817c800220252082a2a307833353335333533353335333533353335333533353335333533353335333533353335333533353335322046464646464646464646464646464646464646464646464646464646464646463a0c0a0a0a080de0b6b3a7640000") - }) - - it('test Bitcoin / Bitcoin SigningInput', () => { - expect(TW.Bitcoin.Proto.SigningInput).not.null; - expect(TW.Binance.Proto.SigningInput).not.null; - }) -}) diff --git a/typescript/tools/check-gpr-version b/typescript/tools/check-gpr-version deleted file mode 100755 index 80f83b2e888..00000000000 --- a/typescript/tools/check-gpr-version +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -VERSION=$(cat "$DIR/../package.json" | jq '.version') -GHR_KEY_PATH='.data.repository.packages.edges[0].node.latestVersion.version' -GHR_VERSION=$(curl -X "POST" "https://api.github.com/graphql" \ - -H "Authorization: Bearer $TOKEN" \ - -H 'Content-Type: application/json; charset=utf-8' \ - -d $'{"query": "query { repository(owner: \\"trustwallet\\", name:\\"wallet-core\\") { name packages(names: \\"wallet-core\\", first: 1) { edges { node { name latestVersion { version summary } } } } } } "}' \ - | jq $GHR_KEY_PATH) - -if [[ $VERSION != $GHR_VERSION ]]; then - echo true -else - echo false -fi diff --git a/typescript/tools/check-npm-version b/typescript/tools/check-npm-version deleted file mode 100755 index bea97300bf1..00000000000 --- a/typescript/tools/check-npm-version +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -VERSION=$(cat "$DIR/../package.json" | jq '.version') -NPM_VERSION=\"$(npm view @trustwallet/wallet-core version)\" - -if [[ $VERSION != $NPM_VERSION ]]; then - echo true -else - echo false -fi diff --git a/typescript/tools/set-tag-version b/typescript/tools/set-tag-version deleted file mode 100755 index 336c57f830e..00000000000 --- a/typescript/tools/set-tag-version +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PACKAGE_JSON=$DIR/../package.json -LATEST_TAG=`git describe --long --tags | cut -f 1 -d "-"` -if [[ -n $LATEST_TAG ]]; then - NEW_PACKAGE=$(jq --arg tag "$LATEST_TAG" '.version = $tag' $PACKAGE_JSON) - echo $NEW_PACKAGE | jq . > $PACKAGE_JSON -fi diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json deleted file mode 100644 index 808eaf5fc74..00000000000 --- a/typescript/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "declaration": true, - "outDir": "./dist", - "strict": true, - "typeRoots": [ - "./node_modules/@types" - ], - "types": [ - "node" - ], - "noImplicitAny": false - }, - "exclude": [ - "node_modules", - "./tests/**/*.ts" - ], -} diff --git a/typescript/yarn.lock b/typescript/yarn.lock deleted file mode 100644 index cec78d32aec..00000000000 --- a/typescript/yarn.lock +++ /dev/null @@ -1,1227 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/parser@^7.9.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64" - integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"@types/chai@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" - integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== - -"@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/mocha@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" - integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== - -"@types/node@^13.7.0": - version "13.13.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.12.tgz#9c72e865380a7dc99999ea0ef20fc9635b503d20" - integrity sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw== - -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@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-styles@^3.2.0, 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" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array.prototype.map@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.2.tgz#9a4159f416458a23e9483078de1106b2ef68f8ec" - integrity sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.4" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bluebird@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -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" - -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== - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -catharsis@^0.8.11: - version "0.8.11" - resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.11.tgz#d0eb3d2b82b7da7a3ce2efb1a7b00becc6643468" - integrity sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g== - dependencies: - lodash "^4.17.14" - -chai@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - pathval "^1.1.0" - type-detect "^4.0.5" - -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" - -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= - -chokidar@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.3.0" - optionalDependencies: - fsevents "~2.1.2" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.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-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -debug@3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== - dependencies: - type-detect "^4.0.0" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -diff@4.0.2, diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -ejs@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.3.tgz#514d967a8894084d18d3d47bd169a1c0560f093d" - integrity sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg== - dependencies: - jake "^10.6.1" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -entities@~2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-get-iterator@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escape-string-regexp@1.0.5, 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 sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escodegen@^1.14.3: - version "1.14.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -estraverse@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -filelist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb" - integrity sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ== - dependencies: - minimatch "^3.0.4" - -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" - -find-up@4.1.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" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -flat@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== - dependencies: - is-buffer "~2.0.3" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^2.0.1: - 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-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= - -glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob@7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - 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" - -graceful-fs@^4.1.9: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -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-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -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== - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - -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-buffer@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" - integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== - -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - -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-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== - dependencies: - has-symbols "^1.0.1" - -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -iterate-iterator@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" - integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw== - -iterate-value@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" - integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== - dependencies: - es-get-iterator "^1.0.2" - iterate-iterator "^1.0.1" - -jake@^10.6.1: - version "10.8.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" - integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== - dependencies: - async "0.9.x" - chalk "^2.4.2" - filelist "^1.0.1" - minimatch "^3.0.4" - -js-yaml@3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js2xmlparser@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.1.tgz#670ef71bc5661f089cc90481b99a05a1227ae3bd" - integrity sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw== - dependencies: - xmlcreate "^2.0.3" - -jsdoc@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.4.tgz#246b2832a0ea8b37a441b61745509cfe29e174b6" - integrity sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA== - dependencies: - "@babel/parser" "^7.9.4" - bluebird "^3.7.2" - catharsis "^0.8.11" - escape-string-regexp "^2.0.0" - js2xmlparser "^4.0.1" - klaw "^3.0.0" - markdown-it "^10.0.0" - markdown-it-anchor "^5.2.7" - marked "^0.8.2" - mkdirp "^1.0.4" - requizzle "^0.2.3" - strip-json-comments "^3.1.0" - taffydb "2.6.2" - underscore "~1.10.2" - -klaw@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" - integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== - dependencies: - graceful-fs "^4.1.9" - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -linkify-it@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" - integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== - dependencies: - uc.micro "^1.0.1" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -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" - -lodash@^4.17.14, lodash@^4.17.15: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== - -log-symbols@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -markdown-it-anchor@^5.2.7: - version "5.3.0" - resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz#d549acd64856a8ecd1bea58365ef385effbac744" - integrity sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA== - -markdown-it@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" - integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== - dependencies: - argparse "^1.0.7" - entities "~2.0.0" - linkify-it "^2.0.0" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -marked@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355" - integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= - -minimatch@3.0.4, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -mocha@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.0.1.tgz#fe01f0530362df271aa8f99510447bc38b88d8ed" - integrity sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.3.1" - debug "3.2.6" - diff "4.0.2" - escape-string-regexp "1.0.5" - find-up "4.1.0" - glob "7.1.6" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" - minimatch "3.0.4" - ms "2.1.2" - object.assign "4.1.0" - promise.allsettled "1.0.2" - serialize-javascript "3.0.0" - strip-json-comments "3.0.1" - supports-color "7.1.0" - which "2.0.2" - wide-align "1.1.3" - workerpool "6.0.0" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - -ms@2.1.2, ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -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-inspect@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@4.1.0, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -p-limit@^2.0.0, 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-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.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-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== - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -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 sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= - -picomatch@^2.0.4, picomatch@^2.0.7: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prettier@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== - -promise.allsettled@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.2.tgz#d66f78fbb600e83e863d893e98b3d4376a9c47c9" - integrity sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg== - dependencies: - array.prototype.map "^1.0.1" - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - iterate-value "^1.0.0" - -protobufjs@^6.9.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.9.0.tgz#c08b2bf636682598e6fabbf0edb0b1256ff090bd" - integrity sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg== - 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" - -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== - dependencies: - picomatch "^2.0.7" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -requizzle@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded" - integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ== - dependencies: - lodash "^4.17.14" - -serialize-javascript@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.0.0.tgz#492e489a2d77b7b804ad391a5f5d97870952548e" - integrity sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -source-map-support@^0.5.17: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - 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== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-json-comments@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== - -strip-json-comments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" - integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== - -supports-color@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - 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" - -taffydb@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" - integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg= - -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" - -ts-node@^8.10.2: - version "8.10.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" - integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== - dependencies: - arg "^4.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.17" - yn "3.1.1" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -typescript@^3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== - -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -underscore@~1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" - integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@2.0.2: - 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" - -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -workerpool@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.0.tgz#85aad67fa1a2c8ef9386a1b43539900f61d03d58" - integrity sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA== - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xmlcreate@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.3.tgz#df9ecd518fd3890ab3548e1b811d040614993497" - integrity sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ== - -y18n@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" - integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== - -yargs-parser@13.1.2, yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== - dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" - -yargs@13.3.2, yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/walletconsole/CMakeLists.txt b/walletconsole/CMakeLists.txt index c68f04fbfe4..f5c8c08ff31 100644 --- a/walletconsole/CMakeLists.txt +++ b/walletconsole/CMakeLists.txt @@ -1,15 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + # walletconsole executable file(GLOB walletconsole_sources *.cpp) add_executable(walletconsole ${walletconsole_sources}) -#target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore protobuf Boost::boost) 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}) \ No newline at end of file +INSTALL(TARGETS walletconsole DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/walletconsole/lib/Address.cpp b/walletconsole/lib/Address.cpp index 0f4b5685a1a..804b575309e 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,9 +27,9 @@ 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; } - TWCoinType ctype = (TWCoinType)coin.c; + auto ctype = (TWCoinType)coin.c; PublicKey pubKey = PublicKey(pubDat, (TWPublicKeyType)coin.pubKeyType); res = TW::deriveAddress(ctype, pubKey); return true; @@ -45,18 +43,18 @@ 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; } - TWCoinType ctype = (TWCoinType)coin.c; - PrivateKey priKey = PrivateKey(priDat); + auto ctype = (TWCoinType)coin.c; + PrivateKey priKey = PrivateKey(priDat, TWCoinTypeCurve(ctype)); res = TW::deriveAddress(ctype, priKey); 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; } - TWCoinType ctype = (TWCoinType)coin.c; + auto ctype = (TWCoinType)coin.c; if (!TW::validateAddress(ctype, addrStr)) { _out << "Address is not a valid " << coin.name << " address! " << addrStr << endl; return false; @@ -68,7 +66,7 @@ bool Address::addr(const string& coinid, const string& addrStr, string& res) { bool Address::addrDefault(const string& coinid, string& res) { Coin coin; if (!_coins.findCoin(coinid, coin)) { return false; } - TWCoinType ctype = (TWCoinType)coin.c; + auto ctype = (TWCoinType)coin.c; string mnemo = _keys.getMnemo(); assert(mnemo.length() > 0); // a mnemonic is always set HDWallet wallet(mnemo, ""); @@ -82,7 +80,7 @@ bool Address::addrDefault(const string& coinid, string& res) { bool Address::deriveFromPath(const string& coinid, const string& derivPath, string& res) { Coin coin; if (!_coins.findCoin(coinid, coin)) { return false; } - TWCoinType ctype = (TWCoinType)coin.c; + auto ctype = (TWCoinType)coin.c; DerivationPath dp(derivPath); // get the private key @@ -99,7 +97,7 @@ bool Address::deriveFromPath(const string& coinid, const string& derivPath, stri bool Address::deriveFromXpubIndex(const string& coinid, const string& xpub, const string& accountIndex, string& res) { Coin coin; if (!_coins.findCoin(coinid, coin)) { return false; } - TWCoinType ctype = (TWCoinType)coin.c; + auto ctype = (TWCoinType)coin.c; int index = std::stoi(accountIndex); @@ -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 90b2a3749ea..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" @@ -21,7 +19,7 @@ using namespace std; void Buffer::addResult(const string& val) { if (val.length() == 0) { return; } _last = SavedValue(val); - _prev.push_back(SavedValue(val)); + _prev.emplace_back(SavedValue(val)); } bool Buffer::prepareInput(const string& in, string& in_out) { @@ -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 b1ac8a1afac..dc3453b0112 100644 --- a/walletconsole/lib/CMakeLists.txt +++ b/walletconsole/lib/CMakeLists.txt @@ -1,13 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + # walletconsolelib library file(GLOB_RECURSE walletconsolelib_sources *.cpp) 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 2f6075a7cd7..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" @@ -10,7 +8,6 @@ #include "Coins.h" #include "Util.h" #include "Address.h" -#include "TonCoin.h" #include "Base64.h" #include "HexCoding.h" @@ -74,7 +71,6 @@ void CommandExecutor::help() const { _out << " addrDP Derive a new address with the given derivation path (using current coin and mnemonic)" << endl; _out << " addrXpub Derive a new address from the given XPUB and address index (using current coin)" << endl; _out << "Coin-specific methods:" << endl; - _out << " tonInitMsg Build TON account initialization message." << endl; _out << "Transformations:" << endl; _out << " hex Encode given string to hex" << endl; _out << " base64Encode Encode given hex data to Base64" << endl; @@ -137,8 +133,6 @@ bool CommandExecutor::executeOne(const string& cmd, const vector& params if (cmd == "addrdp") { if (!checkMinParams(params, 1)) { return false; } return _address.deriveFromPath(_activeCoin, params[1], res); } if (cmd == "addrxpub") { if (!checkMinParams(params, 2)) { return false; } return _address.deriveFromXpubIndex(_activeCoin, params[1], params[2], res); } - if (cmd == "toninitmsg") { if (!checkMinParams(params, 1)) { return false; } setCoin("ton", false); return TonCoin::tonInitMsg(params[1], res); } - if (cmd == "hex") { if (!checkMinParams(params, 1)) { return false; } return Util::hex(params[1], res); } if (cmd == "base64encode") { if (!checkMinParams(params, 1)) { return false; } return _util.base64Encode(params[1], res); } if (cmd == "base64decode") { if (!checkMinParams(params, 1)) { return false; } return _util.base64Decode(params[1], res); } @@ -193,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 ccbbcaf498f..7545b1e4f0c 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" @@ -25,16 +23,12 @@ using namespace std; Keys::Keys(ostream& out, const Coins& coins) : _out(out), _coins(coins) { // init a random mnemonic HDWallet newwall(128, ""); - _currentMnemonic = newwall.mnemonic; + _currentMnemonic = newwall.getMnemonic(); } void privateKeyToResult(const PrivateKey& priKey, string& res_out) { // take the key, but may need to take extension as well res_out = hex(priKey.bytes); - if (priKey.extensionBytes.size() > 0) { - res_out += hex(priKey.extensionBytes); - res_out += hex(priKey.chainCodeBytes); - } } bool Keys::newKey(const string& coinid, string& res) { @@ -58,7 +52,7 @@ bool Keys::pubPri(const string& coinid, const string& p, string& res) { Data privDat; try { privDat = parse_hex(p); - auto priv = PrivateKey(privDat); + auto priv = PrivateKey(privDat, TWCoinTypeCurve(TWCoinType(coin.c))); auto pub = priv.getPublicKey((TWPublicKeyType)coin.pubKeyType); res = hex(pub.bytes); _out << "Public key created, type " << (int)coin.pubKeyType << ", length " << pub.bytes.size() << endl; @@ -69,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; } @@ -81,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]; } @@ -104,12 +98,12 @@ bool Keys::newMnemonic(const string& param1, string& res) { return false; } HDWallet newwall(strength, ""); - if (newwall.mnemonic.length() == 0) { + if (newwall.getMnemonic().length() == 0) { _out << "Error: no mnemonic generated." << endl; return false; } // store - _currentMnemonic = newwall.mnemonic; + _currentMnemonic = newwall.getMnemonic(); res = _currentMnemonic; _out << "New mnemonic set." << endl; return false; @@ -118,7 +112,7 @@ bool Keys::newMnemonic(const string& param1, string& res) { bool Keys::dumpSeed(string& res) { assert(_currentMnemonic.length() > 0); // a mnemonic is always set HDWallet wallet(_currentMnemonic, ""); - string seedHex = hex(wallet.seed); + string seedHex = hex(wallet.getSeed()); res = seedHex; return true; } 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/TonCoin.h b/walletconsole/lib/TonCoin.h deleted file mode 100644 index 3b51476d5c5..00000000000 --- a/walletconsole/lib/TonCoin.h +++ /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. - -#pragma once - -#include "TON/Address.h" -#include "TON/Cell.h" -#include "TON/Signer.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include "HexCoding.h" - -#include -#include -#include -#include - -namespace TW::WalletConsole { - -using namespace std; -using namespace TW; -using namespace TW::TON; - -class TonCoin { -public: - static bool tonInitMsg(const string& privkey, string& res) { - PrivateKey privv = PrivateKey(parse_hex(privkey)); - Data extMsg = Signer::buildInitMessage(privv); - res = hex(extMsg); - return true; - } -}; - -} // namespace TW::WalletConsole 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 f86ed09e34e..d025feb064e 100644 --- a/walletconsole/lib/Util.h +++ b/walletconsole/lib/Util.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 namespace TW::WalletConsole { diff --git a/walletconsole/lib/WalletConsole.cpp b/walletconsole/lib/WalletConsole.cpp index b7c1f3656f2..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" @@ -10,7 +8,6 @@ #include "Coins.h" #include "Util.h" #include "Address.h" -#include "TonCoin.h" #include "Base64.h" #include "HexCoding.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 new file mode 100644 index 00000000000..13d2d8098a4 --- /dev/null +++ b/wasm/.gitignore @@ -0,0 +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 new file mode 100644 index 00000000000..1b4017a7ea4 --- /dev/null +++ b/wasm/CMakeLists.txt @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017 Trust Wallet. + +file(GLOB wasm_sources src/*.cpp src/generated/*.cpp) +file(GLOB wasm_headers src/*.h src/generated/*.h) +set(TARGET_NAME wallet-core) +set(CMAKE_EXECUTABLE_SUFFIX ".js") +add_executable(${TARGET_NAME} ${wasm_sources} ${wasm_headers}) + +target_link_libraries(${TARGET_NAME} TrustWalletCore) +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/trezor-crypto/include) +target_compile_options(${TARGET_NAME} PRIVATE "-Wall") + +set_target_properties(${TARGET_NAME} + PROPERTIES + 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 "-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 new file mode 100644 index 00000000000..a7f54ee4b95 --- /dev/null +++ b/wasm/index.ts @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import * as Loader from "./lib/wallet-core"; +import { TW } from "./generated/core_proto"; +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 new file mode 100644 index 00000000000..94e227042c2 --- /dev/null +++ b/wasm/package-lock.json @@ -0,0 +1,2210 @@ +{ + "name": "@trustwallet/wallet-core", + "version": "1.0.0", + "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.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT" + }, + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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/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.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": 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": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "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/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": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "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, + "license": "ISC" + }, + "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.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "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/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": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "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/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, + "license": "MIT", + "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" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "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.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "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": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "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.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "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", + "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 + }, + "@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, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@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==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@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 + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@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 + }, + "@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "@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 + }, + "@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + }, + "@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": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "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 + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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" + } + }, + "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, + "requires": { + "fill-range": "^7.1.1" + } + }, + "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 + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "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" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "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, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "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 + }, + "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, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "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 + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "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 + }, + "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 + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "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 + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "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 + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "dependencies": { + "diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true + } + } + }, + "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 + }, + "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 + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "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, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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 + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "protobufjs": { + "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", + "@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" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "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 + }, + "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 + }, + "serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "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 + }, + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true + }, + "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 + }, + "workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "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" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "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, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "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 + } + } +} diff --git a/wasm/package.json b/wasm/package.json new file mode 100644 index 00000000000..996f10ac35e --- /dev/null +++ b/wasm/package.json @@ -0,0 +1,46 @@ +{ + "name": "@trustwallet/wallet-core", + "version": "1.0.0", + "description": "wallet core wasm and protobuf models", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "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 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", + "url": "git://github.com/trustwallet/wallet-core.git" + }, + "author": "hewigovens", + "license": "MIT", + "bugs": { + "url": "https://github.com/trustwallet/wallet-core/issues" + }, + "homepage": "https://github.com/trustwallet/wallet-core#readme", + "files": [ + "dist" + ], + "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" + } +} diff --git a/wasm/src/AnySigner.cpp b/wasm/src/AnySigner.cpp new file mode 100644 index 00000000000..9ebd732149c --- /dev/null +++ b/wasm/src/AnySigner.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include +#include + +#include "WasmData.h" +#include "Coin.h" + +using namespace emscripten; + +namespace TW::Wasm { + +class AnySigner { + public: + static auto sign(const std::string& string, TWCoinType coin) { + Data out; + TW::anyCoinSign(coin, TW::data(string), out); + return DataToVal(out); + } + + static auto supportsJSON(TWCoinType coin) { + return TW::supportsJSONSigning(coin); + } + + static auto plan(const std::string& string, TWCoinType coin) { + Data out; + TW::anyCoinPlan(coin, TW::data(string), out); + return DataToVal(out); + } +}; + +EMSCRIPTEN_BINDINGS(Wasm_TWAnySigner) { + class_("AnySigner") + .class_function("sign", &AnySigner::sign) + .class_function("plan", &AnySigner::plan) + .class_function("supportsJSON", &AnySigner::supportsJSON); +} + +} // namespace TW::Wasm diff --git a/wasm/src/AnySigner.d.ts b/wasm/src/AnySigner.d.ts new file mode 100644 index 00000000000..b188bc2f479 --- /dev/null +++ b/wasm/src/AnySigner.d.ts @@ -0,0 +1,5 @@ +export class AnySigner { + static sign(data: Uint8Array | Buffer, coin: CoinType): Uint8Array; + static plan(data: Uint8Array | Buffer, coin: CoinType): Uint8Array; + static supportsJSON(coin: CoinType): boolean; +} 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 new file mode 100644 index 00000000000..b91dd05fc7c --- /dev/null +++ b/wasm/src/HexCoding.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include + +#include "WasmData.h" +#include "HexCoding.h" + +using namespace emscripten; + +namespace TW::Wasm { + +class HexCoding { + public: + static auto parseHex(const std::string& string) { + return DataToVal(TW::parse_hex(string, true)); + } + + static auto hexEncoded(const std::string& string) { + auto data = TW::data(string); + return TW::hexEncoded(data); + } +}; + +EMSCRIPTEN_BINDINGS(Wasm_HexCoding) { + class_("HexCoding") + .class_function("decode", &HexCoding::parseHex) + .class_function("encode", &HexCoding::hexEncoded); +} + +} // namespace TW::Wasm diff --git a/wasm/src/HexCoding.d.ts b/wasm/src/HexCoding.d.ts new file mode 100644 index 00000000000..f15943f9d56 --- /dev/null +++ b/wasm/src/HexCoding.d.ts @@ -0,0 +1,4 @@ +export namespace HexCoding { + export function decode(hex: string): Uint8Array; + export function encode(buffer: Uint8Array | Buffer): string; +} diff --git a/wasm/src/Random.cpp b/wasm/src/Random.cpp new file mode 100644 index 00000000000..84e49a515f4 --- /dev/null +++ b/wasm/src/Random.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#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) { + randombytes_init_if_needed(); + return javascript_random(); +} + +void random_buffer(uint8_t* buf, size_t len) { + randombytes_init_if_needed(); + javascript_buf(buf, len); + return; +} + +} // extern "C" diff --git a/wasm/src/WasmData.cpp b/wasm/src/WasmData.cpp new file mode 100644 index 00000000000..7916c9e39a0 --- /dev/null +++ b/wasm/src/WasmData.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include "WasmData.h" +#include "Defer.h" + +using namespace emscripten; + +namespace TW::Wasm { + +auto DataToVal(Data data) -> val { + auto view = val(typed_memory_view(data.size(), data.data())); + auto jsArray = val::global("Uint8Array").new_(data.size()); + jsArray.call("set", view); + return jsArray; +} + +auto TWDataToVal(TWData* _Nonnull data) -> val { + defer { + TWDataDelete(data); + }; + auto* v = reinterpret_cast(data); + return DataToVal(*v); +} + +} // namespace TW::Wasm diff --git a/wasm/src/WasmData.h b/wasm/src/WasmData.h new file mode 100644 index 00000000000..8cd2a4853ad --- /dev/null +++ b/wasm/src/WasmData.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#pragma once + +#include +#include + +#include "Data.h" + +namespace TW::Wasm { + +auto DataToVal(Data data) -> emscripten::val; + +/// Converts a TWData * to Uint8Array, deleting the TWData * when done. +auto TWDataToVal(TWData *_Nonnull data) -> emscripten::val; + +} // namespace TW::Wasm diff --git a/wasm/src/WasmString.cpp b/wasm/src/WasmString.cpp new file mode 100644 index 00000000000..f03f58d7b6e --- /dev/null +++ b/wasm/src/WasmString.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include "WasmString.h" +#include "Defer.h" + +namespace TW::Wasm { + +auto TWStringToStd(TWString *_Nonnull string) -> std::string { + defer { + TWStringDelete(string); + }; + auto* s = reinterpret_cast(string); + auto result = *s; + return result; +} + +} // namespace TW::Wasm diff --git a/wasm/src/WasmString.h b/wasm/src/WasmString.h new file mode 100644 index 00000000000..9d7c8999e18 --- /dev/null +++ b/wasm/src/WasmString.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#pragma once + +#include +#include + +namespace TW::Wasm { + +/// Converts a TWString * to std::string, deleting the TWString * when done. +auto TWStringToStd(TWString *_Nonnull string) -> std::string; + +} // namespace TW::Wasm 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..c6368bc978d --- /dev/null +++ b/wasm/src/keystore/default-impl.ts @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import {WalletCore, CoinType, Derivation, 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, + derivation: Derivation + ): 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.importPrivateKeyWithEncryptionAndDerivation(key, name, pass, coin, encryption, derivation); + let wallet = this.mapWallet(storedKey); + storedKey.delete(); + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + + importKeyEncoded( + key: string, + name: string, + password: string, + coin: CoinType, + encryption: StoredKeyEncryption, + derivation: Derivation + ): Promise { + return new Promise((resolve, reject) => { + const { StoredKey, PrivateKey, Curve, StoredKeyEncryption } = this.core; + + let pass = Buffer.from(password); + let storedKey = StoredKey.importPrivateKeyEncodedWithEncryptionAndDerivation(key, name, pass, coin, encryption, derivation); + 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 { + const { Derivation } = this.core; + + let coins_with_derivations = coins.map(coin => ({ + coin: coin, + derivation: Derivation.default, + })); + return this.addAccountsWithDerivations(id, password, coins_with_derivations); + } + + addAccountsWithDerivations(id: string, password: string, coins: Types.CoinWithDerivation[]): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let hdWallet = storedKey.wallet(Buffer.from(password)); + coins.forEach((item) => { + storedKey.accountForCoinDerivation(item.coin, item.derivation, 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 coin = (this.core.CoinType as any).values["" + account.coin]; + let privateKey = storedKey.privateKey(coin, Buffer.from(password)); + storedKey.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; + }); + } + + getWalletType(id: string): Promise { + return this.load(id).then((wallet) => wallet.type); + } + + exportMnemonic(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + if (wallet.type !== Types.WalletType.Mnemonic) { + throw Types.Error.UnsupportedWalletType; + } + let storedKey = this.mapStoredKey(wallet); + let value = storedKey.decryptMnemonic(Buffer.from(password)); + storedKey.delete(); + return value; + }); + } + + exportPrivateKey(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + if (wallet.type !== Types.WalletType.PrivateKey) { + throw Types.Error.UnsupportedWalletType; + } + let storedKey = this.mapStoredKey(wallet); + let value = storedKey.decryptPrivateKey(Buffer.from(password)); + storedKey.delete(); + return value; + }); + } + + exportPrivateKeyEncoded(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + if (wallet.type !== Types.WalletType.PrivateKey) { + throw Types.Error.UnsupportedWalletType; + } + let storedKey = this.mapStoredKey(wallet); + let value = storedKey.decryptPrivateKeyEncoded(Buffer.from(password)); + 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..c5e94d2f8fd --- /dev/null +++ b/wasm/src/keystore/types.ts @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { CoinType, Derivation, PrivateKey, StoredKeyEncryption } from "../wallet-core"; + +export enum WalletType { + Mnemonic = "mnemonic", + PrivateKey = "private-key", + 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", + UnsupportedWalletType = "unsupported wallet type", +} + +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 CoinWithDerivation { + coin: CoinType, + derivation: Derivation, +} + +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, + derivation: Derivation + ): 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; + + // Add active accounts paired with corresponding derivations to a wallet by wallet id, password, coin. + addAccountsWithDerivations(id: string, password: string, coins: CoinWithDerivation[]): 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 new file mode 100644 index 00000000000..a6a6b65a56a --- /dev/null +++ b/wasm/tests/AES.test.ts @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("AES", () => { + + it("test decrypting", () => { + const { AES, HexCoding, AESPaddingMode } = globalThis.core; + + const key = HexCoding.decode( + "5caa3a74154cee16bd1b570a1330be46e086474ac2f4720530662ef1a469662c" + ); + const iv = HexCoding.decode("89ef1d6728bac2f1dcde2ef9330d2bb8"); + const cipher = HexCoding.decode( + "1b3db3674de082d65455eba0ae61cfe7e681c8ef1132e60c8dbd8e52daf18f4fea42cc76366c83351dab6dca52682ff81f828753f89a21e1cc46587ca51ccd353914ffdd3b0394acfee392be6c22b3db9237d3f717a3777e3577dd70408c089a4c9c85130a68c43b0a8aadb00f1b8a8558798104e67aa4ff027b35d4b989e7fd3988d5dcdd563105767670be735b21c4" + ); + + const decrypted = AES.decryptCBC(key, cipher, iv, AESPaddingMode.pkcs7); + + assert.equal(Buffer.from(decrypted).toString(), + '{"id":1554098597199736,"jsonrpc":"2.0","method":"wc_sessionUpdate","params":[{"approved":false,"chainId":null,"accounts":null}]}' + ); + }); + + it("test encrypting", () => { + const { AES, HexCoding, AESPaddingMode } = globalThis.core; + + const key = HexCoding.decode( + "bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96" + ); + const iv = HexCoding.decode("5b3a1a561e395d7ad7fe9c92abdacd17"); + const plain = Buffer.from('{"jsonrpc":"2.0","id":1554343834752446,"error":{"code":-32000,"message":"Session Rejected"}}'); + + const encrypted = AES.encryptCBC(key, plain, iv, AESPaddingMode.pkcs7); + + assert.equal(HexCoding.encode(encrypted), + '0x6a93788fcd6d266d06ccff35d1ed328d634605a7f2734f1519256b9950d86d6ca34fe4a13ff0ed513025b49427e6fe15268c84d6dfad6c0c8a21abc9306a5308f545b08d946a2a24b7cd18526bcefd6d9740db9b8e21f4511df148d9b9b03ad9' + ); + }); +}); diff --git a/wasm/tests/AnyAddress.test.ts b/wasm/tests/AnyAddress.test.ts new file mode 100644 index 00000000000..5701ea89c98 --- /dev/null +++ b/wasm/tests/AnyAddress.test.ts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("AnyAddress", () => { + it("test validating Solana address", () => { + const { AnyAddress, HexCoding, CoinType } = globalThis.core; + + var address = AnyAddress.createWithString( + "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q", + CoinType.solana + ); + + assert.equal(address.coin().value, 501); + assert.equal(address.description(), "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q"); + + const data = address.data(); + + assert.equal(data.length, 32); + assert.typeOf(data, "Uint8Array"); + 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/Bech32.test.ts b/wasm/tests/Bech32.test.ts new file mode 100644 index 00000000000..f9ac76a0c9f --- /dev/null +++ b/wasm/tests/Bech32.test.ts @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Bech32", () => { + + it("test encode", () => { + const { Bech32 } = globalThis.core; + + const encoded = Bech32.encode("abcdef", Buffer.from("00443214c74254b635cf84653a56d7c675be77df", "hex")); + + assert.equal(encoded, "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"); + }); + + it("test decode", () => { + const { Bech32, HexCoding } = globalThis.core; + + const decoded = Bech32.decode("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"); + + assert.equal( + HexCoding.encode(decoded), + "0x00443214c74254b635cf84653a56d7c675be77df" + ); + }); + + it("test encodeM", () => { + const { Bech32 } = globalThis.core; + + const encoded = Bech32.encodeM("abcdef", Buffer.from("ffbbcdeb38bdab49ca307b9ac5a928398a418820", "hex")); + + assert.equal(encoded, "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"); + }); + + it("test decodeM", () => { + const { Bech32, HexCoding } = globalThis.core; + + const decoded = Bech32.decodeM("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"); + + assert.equal( + HexCoding.encode(decoded), + "0xffbbcdeb38bdab49ca307b9ac5a928398a418820" + ); + }); +}); 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 new file mode 100644 index 00000000000..f839f31b8f3 --- /dev/null +++ b/wasm/tests/Blockchain/Bitcoin.test.ts @@ -0,0 +1,345 @@ +// 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("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({ + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.SelectDescending, + feePerVb: new Long(8), + changeOutput: changeOut, + fixedDustThreshold: dustAmount, + }, + privateKeys: [privateKeyData], + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + // WARNING Do not use in production! + dangerousUseFixedSchnorrRng: true, + }); + + 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({ + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0, utxo1], + outputs: [out0, changeOut], + inputSelector: Proto.InputSelector.UseAll, + fixedDustThreshold: dustSatoshis, + }, + privateKeys: [privateKeyData], + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + }); + + 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({ + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0], + outputs: [out0, changeOut], + inputSelector: Proto.InputSelector.UseAll, + fixedDustThreshold: dustAmount, + }, + privateKeys: [privateKeyData], + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + }); + + 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({ + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.UseAll, + fixedDustThreshold: dustAmount, + }, + privateKeys: [privateKeyData], + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + // WARNING Do not use in production! + dangerousUseFixedSchnorrRng: true, + }); + + 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 new file mode 100644 index 00000000000..357e8d306f3 --- /dev/null +++ b/wasm/tests/Blockchain/Ethereum.test.ts @@ -0,0 +1,166 @@ +// 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("Ethereum", () => { + + it("test address", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + + const data = HexCoding.decode("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06"); + + assert.isTrue(PrivateKey.isValid(data, Curve.secp256k1)); + + const key = PrivateKey.createWithData(data); + const pubKey = key.getPublicKeySecp256k1(false); + + assert.equal( + HexCoding.encode(pubKey.data()), + "0x043182a24fdefe5711d735a434e983bf32a63fd99d214d63936b312643c325c6e33545c4aaff6b923544044d363d73668ec8724b7e62b54d17d49879405cf20648" + ); + + const address = AnyAddress.createWithPublicKey(pubKey, CoinType.smartChain); + + assert.equal(address.description(), "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"); + + key.delete(); + pubKey.delete(); + address.delete(); + }); + + it("test signing transfer tx", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core;; + const input = TW.Ethereum.Proto.SigningInput.create({ + toAddress: "0x3535353535353535353535353535353535353535", + chainId: Buffer.from("01", "hex"), + nonce: Buffer.from("09", "hex"), + gasPrice: Buffer.from("04a817c800", "hex"), + gasLimit: Buffer.from("5208", "hex"), + transaction: TW.Ethereum.Proto.Transaction.create({ + transfer: TW.Ethereum.Proto.Transaction.Transfer.create({ + amount: Buffer.from("0de0b6b3a7640000", "hex"), + }), + }), + privateKey: HexCoding.decode( + "4646464646464646464646464646464646464646464646464646464646464646" + ), + }); + + const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish(); + assert.equal( + HexCoding.encode(encoded), + "0x0a0101120109220504a817c8002a025208422a3078333533353335333533353335333533353335333533353335333533353335333533353335333533354a204646464646464646464646464646464646464646464646464646464646464646520c0a0a0a080de0b6b3a7640000" + ); + + const outputData = AnySigner.sign(encoded, CoinType.ethereum); + const output = TW.Ethereum.Proto.SigningOutput.decode(outputData); + assert.equal( + HexCoding.encode(output.encoded), + "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83" + ); + }); + + it("test signing eip1559 erc20 transfer tx", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core;; + + const input = TW.Ethereum.Proto.SigningInput.create({ + toAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", + chainId: Buffer.from("01", "hex"), + nonce: Buffer.from("00", "hex"), + txMode: TW.Ethereum.Proto.TransactionMode.Enveloped, + maxInclusionFeePerGas: Buffer.from("0077359400", "hex"), + maxFeePerGas: Buffer.from("00b2d05e00", "hex"), + gasLimit: Buffer.from("0130B9", "hex"), + transaction: TW.Ethereum.Proto.Transaction.create({ + erc20Transfer: TW.Ethereum.Proto.Transaction.ERC20Transfer.create({ + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84", + amount: Buffer.from("1bc16d674ec80000", "hex"), + }), + }), + privateKey: HexCoding.decode( + "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151" + ), + }); + + const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish(); + const outputData = AnySigner.sign(encoded, CoinType.ethereum); + const output = TW.Ethereum.Proto.SigningOutput.decode(outputData); + assert.equal( + HexCoding.encode(output.encoded), + "0x02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf" + ); + }); + + it("test signing personal message", () => { + 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])); + + assert.equal(HexCoding.encode(hash), "0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655"); + + var key = PrivateKey.createWithData(HexCoding.decode("1fcb84974220eb76e619d7208e1446ae9c0f755e97fb220a8f61c7dc03a0dfce")); + + const signature = key.sign(hash, Curve.secp256k1); + + assert.equal(HexCoding.encode(signature), "0x58156c371347613642e94b66abc4ced8e36011fb3233f5372371aa5ad321671b1a10c0b88f47ce543fd4c455761f5fbf8f61d050f57dcba986640011da794a9000"); + + key.delete(); + }); + + it("test signing EIP712 message", () => { + const { EthereumAbi, HexCoding, Hash, PrivateKey, Curve } = globalThis.core;; + + const key = PrivateKey.createWithData(Hash.keccak256(Buffer.from("cow"))); + const message = { + 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!", + }, + }; + + const hash = EthereumAbi.encodeTyped(JSON.stringify(message)); + const signature = key.sign(hash, Curve.secp256k1); + + assert.equal(HexCoding.encode(hash), "0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); + assert.equal(HexCoding.encode(signature), "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b9156201"); + + key.delete(); + }); +}); 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/Ripple.test.ts b/wasm/tests/Blockchain/Ripple.test.ts new file mode 100644 index 00000000000..323ae28dd1e --- /dev/null +++ b/wasm/tests/Blockchain/Ripple.test.ts @@ -0,0 +1,39 @@ +// 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("Ripple", () => { + it("test sign XRP payment", () => { + const {PrivateKey, HexCoding, AnySigner, CoinType} = globalThis.core; + + const privateKey = PrivateKey.createWithData( + HexCoding.decode("0xa5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77") + ); + const txDataInput = TW.Ripple.Proto.SigningInput.create({ + fee: new Long(10), + sequence: 32_268_248, + lastLedgerSequence: 32_268_269, + account: "rfxdLwsZnoespnTDDb1Xhvbc8EFNdztaoq", + privateKey: privateKey.data(), + opPayment: { + amount: new Long(10), + destination: "rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF", + }, + }); + const input = TW.Ripple.Proto.SigningInput.encode(txDataInput).finish(); + const outputData = AnySigner.sign(input, CoinType.xrp); + const output = TW.Ripple.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, TW.Common.Proto.SigningError.OK); + // https://testnet.xrpl.org/transactions/A202034796F37F38D1D20F2025DECECB1623FC801F041FC694199C0D0E49A739 + assert.equal( + HexCoding.encode(output.encoded), + "0x12000022000000002401ec5fd8201b01ec5fed61400000000000000a68400000000000000a732103d13e1152965a51a4a9fd9a8b4ea3dd82a4eba6b25fcad5f460a2342bb650333f74463044022037d32835c9394f39b2cfd4eaf5b0a80e0db397ace06630fa2b099ff73e425dbc02205288f780330b7a88a1980fa83c647b5908502ad7de9a44500c08f0750b0d9e8481144c55f5a78067206507580be7bb2686c8460adff983148132e4e20aecf29090ac428a9c43f230a829220d" + ); + }); +}); 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..da55da7cfca --- /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: Buffer.from("0A", "hex"), // 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: Buffer.from("1DCD6500", "hex"), // 500 * 1000 * 1000, + toOwner: "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8", + responseAddress: "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk", + forwardAmount: Buffer.from("01", "hex"), // 1 + }); + + let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ + dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja", + amount: Buffer.from("05F5E100", "hex"), // 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/Blockchain/Zcash.test.ts b/wasm/tests/Blockchain/Zcash.test.ts new file mode 100644 index 00000000000..2b564885aca --- /dev/null +++ b/wasm/tests/Blockchain/Zcash.test.ts @@ -0,0 +1,75 @@ +// 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("Zcash", () => { + // Successfully broadcasted: https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 + it("test Zcash sign Sapling", () => { + const { AnySigner, BitcoinSigHashType, HexCoding, CoinType } = globalThis.core; + const Proto = TW.BitcoinV2.Proto; + + const privateKeyData = HexCoding.decode("a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559"); + const dustAmount = new Long(546); + const utxoTxId = HexCoding.decode("3a19dd44032dfed61bfca5ba5751aab8a107b30609cbd5d70dc5ef09885b6853").reverse(); + const sapplingBranchId = HexCoding.decode("bb09b876"); + + const utxo0 = Proto.Input.create({ + outPoint: { + hash: utxoTxId, + vout: 0, + }, + value: new Long(494_000), + sighashType: BitcoinSigHashType.all.value, + receiverAddress: "t1gWVE2uyrET2CxSmCaBiKzmWxQdHhnvMSz", + }); + + const out0 = Proto.Output.create({ + value: new Long(488_000), + toAddress: "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS", + }); + + const signingInput = Proto.SigningInput.create({ + builder: { + version: Proto.TransactionVersion.UseDefault, + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.SelectDescending, + fixedDustThreshold: dustAmount, + // Set ZCash specific extra parameters. + zcashExtraData: { + branchId: sapplingBranchId, + }, + }, + privateKeys: [privateKeyData], + chainInfo: { + p2pkhPrefix: 184, + p2shPrefix: 189, + }, + }); + + const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ + signingV2: signingInput, + coinType: CoinType.zcash.value, + }); + + const encoded = TW.Bitcoin.Proto.SigningInput.encode(legacySigningInput).finish(); + const outputData = AnySigner.sign(encoded, CoinType.zcash); + 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), + "0x0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000" + ); + assert.equal( + HexCoding.encode(output.signingResultV2!.txid), + "0xec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256" + ); + }); +}); diff --git a/wasm/tests/CoinType.test.ts b/wasm/tests/CoinType.test.ts new file mode 100644 index 00000000000..84dc7da419d --- /dev/null +++ b/wasm/tests/CoinType.test.ts @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("CoinType", () => { + it("test raw value", () => { + const { CoinType } = globalThis.core; + + assert.equal(CoinType.bitcoin.value, 0); + assert.equal(CoinType.litecoin.value, 2); + assert.equal(CoinType.dogecoin.value, 3); + assert.equal(CoinType.ethereum.value, 60); + assert.equal(CoinType.binance.value, 714); + assert.equal(CoinType.cosmos.value, 118); + assert.equal(CoinType.solana.value, 501); + assert.equal(CoinType.pactus.value, 21888); + }); + + 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"); + }); + + it("test CoinTypeExt methods for Pactus", () => { + const { CoinType, CoinTypeExt, Blockchain, Purpose, Curve, Derivation } = globalThis.core; + + assert.equal(CoinTypeExt.blockchain(CoinType.pactus), Blockchain.pactus); + assert.equal(CoinTypeExt.purpose(CoinType.pactus), Purpose.bip44); + assert.equal(CoinTypeExt.curve(CoinType.pactus), Curve.ed25519); + assert.isTrue(CoinTypeExt.validate(CoinType.pactus, "pc1rnvlc4wa73lc0rydmgfswz4j5wad4un376vv2d7")) + assert.equal(CoinTypeExt.derivationPath(CoinType.pactus), "m/44'/21888'/3'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.pactus, Derivation.pactusMainnet), "m/44'/21888'/3'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.pactus, Derivation.pactusTestnet), "m/44'/21777'/3'/0'"); + }); + + it("test deriveAddress for Pactus", () => { + const { CoinType, CoinTypeExt, PrivateKey, HexCoding } = globalThis.core; + + const data = HexCoding.decode("8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"); + const key = PrivateKey.createWithData(data); + const addr = CoinTypeExt.deriveAddress(CoinType.pactus, key); + assert.equal(addr, "pc1rnvlc4wa73lc0rydmgfswz4j5wad4un376vv2d7") + }); +}); 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 new file mode 100644 index 00000000000..a45fc92c3dd --- /dev/null +++ b/wasm/tests/HDWallet.test.ts @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("HDWallet", () => { + + it("test creating 24 words", () => { + const { HDWallet, Mnemonic } = globalThis.core; + + var wallet = HDWallet.create(256, "password"); + const mnemonic = wallet.mnemonic(); + + assert.equal(mnemonic.split(" ").length, 24); + assert.isTrue(Mnemonic.isValid(mnemonic)); + + wallet.delete(); + }); + + it("test deriving Ethereum address", () => { + 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); + + assert.equal(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); + + wallet.delete(); + }); +}); diff --git a/wasm/tests/HRP.test.ts b/wasm/tests/HRP.test.ts new file mode 100644 index 00000000000..e6e81505003 --- /dev/null +++ b/wasm/tests/HRP.test.ts @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("HRP", () => { + it("test string value", () => { + 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 new file mode 100644 index 00000000000..1747995eb48 --- /dev/null +++ b/wasm/tests/Hash.test.ts @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Hash", () => { + it("test keccak256", () => { + const { Hash, HexCoding } = globalThis.core; + + const sha3Hash = Hash.keccak256(Buffer.from("Test keccak-256")); + + assert.equal( + HexCoding.encode(sha3Hash), + "0x9aeb50f48121c80b2ff73ad48b5f197d940f748d936d35c992367370c1abfb18" + ); + }); + + it("test sha256", () => { + const { Hash, HexCoding } = globalThis.core; + + const sha256Hash = Hash.sha256(Buffer.from("Test hash")); + assert.equal( + HexCoding.encode(sha256Hash), + "0xf250fc8f40aeea3297c0158ec1bfa07b503805f2a822530bd63c50625bb9376b" + ); + }); + + it("test sha512_256", () => { + const { Hash, HexCoding } = globalThis.core; + + const hash = Hash.sha512_256(Buffer.from("hello")); + assert.equal( + HexCoding.encode(hash), + "0xe30d87cfa2a75db545eac4d61baf970366a8357c7f72fa95b52d0accb698f13a" + ); + }); +}); diff --git a/wasm/tests/HexCoding.test.ts b/wasm/tests/HexCoding.test.ts new file mode 100644 index 00000000000..9879072a047 --- /dev/null +++ b/wasm/tests/HexCoding.test.ts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("HexCoding", () => { + it("test encoding / decoding hex string", () => { + const { HexCoding } = globalThis.core; + + const expected = new Uint8Array([0x52, 0x8]); + const decoded = HexCoding.decode("0x5208"); + + assert.deepEqual(decoded, expected); + + const encoded = HexCoding.encode(expected); + + assert.equal(encoded, "0x5208"); + }); +}); diff --git a/wasm/tests/KeyStore+extension.test.ts b/wasm/tests/KeyStore+extension.test.ts new file mode 100644 index 00000000000..69895fccf88 --- /dev/null +++ b/wasm/tests/KeyStore+extension.test.ts @@ -0,0 +1,130 @@ +// 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, Derivation, 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); + + var 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")); + + wallet = await keystore.addAccountsWithDerivations(wallet.id, password, [{ + coin: CoinType.solana, + derivation: Derivation.solanaSolana, + }]); + assert.equal(wallet.activeAccounts.length, 4); + account = wallet.activeAccounts[3]; + assert.equal(account.address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C"); + + 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/KeyStore.test.ts b/wasm/tests/KeyStore.test.ts new file mode 100644 index 00000000000..ffe9017dfe6 --- /dev/null +++ b/wasm/tests/KeyStore.test.ts @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { KeyStore } from "../dist"; + +describe("KeyStore", () => { + it("test get key default", async () => { + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr, Derivation.default); + + const account2 = wallet.activeAccounts[0]; + assert.equal(account2.derivationPath, "m/84'/0'/0'/0/0"); + assert.equal(account2.address, "bc1qygled22ne2atsrdhhg92xaqflk67tpc4q69kjd"); + const key2 = await keystore.getKey(wallet.id, password, account2); + + assert.equal( + HexCoding.encode(key2.data()), + "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + ); + }).timeout(10000); + + it("test get key legacy", async () => { + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr, Derivation.bitcoinLegacy); + + const account2 = wallet.activeAccounts[0]; + assert.equal(account2.derivationPath, "m/44'/0'/0'/0/0"); + assert.equal(account2.address, "14869jMDBsEra11WyervLDxCQD4GMBfjD7"); + const key2 = await keystore.getKey(wallet.id, password, account2); + + assert.equal( + HexCoding.encode(key2.data()), + "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + ); + }).timeout(10000); + + it("test export", async () => { + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr, Derivation.default); + + const exported2 = await keystore.export(wallet.id, password); + assert.equal(HexCoding.encode(exported2), "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"); + }).timeout(10000); + + it("test export mnemonic", async () => { + const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + assert.equal(await keystore.getWalletType(wallet.id), "mnemonic"); + + const exported = await keystore.exportMnemonic(wallet.id, password); + assert.equal(exported, mnemonic); + }).timeout(10000); + + it("test export key", async () => { + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + const wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr, Derivation.default); + assert.equal(await keystore.getWalletType(wallet.id), "private-key"); + + const exported = await keystore.exportPrivateKey(wallet.id, password); + assert.equal(HexCoding.encode(exported), "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"); + }).timeout(10000); + + it("test export key encoded", async () => { + const { CoinType, StoredKeyEncryption, Derivation } = globalThis.core; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + const inputPrivateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"; + const wallet = await keystore.importKeyEncoded(inputPrivateKeyBase58, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr, Derivation.default); + assert.equal(await keystore.getWalletType(wallet.id), "private-key"); + + const exported = await keystore.exportPrivateKeyEncoded(wallet.id, password); + assert.equal(exported, inputPrivateKeyBase58); + }).timeout(10000); +}); diff --git a/wasm/tests/Mnemonic.test.ts b/wasm/tests/Mnemonic.test.ts new file mode 100644 index 00000000000..f392a659e27 --- /dev/null +++ b/wasm/tests/Mnemonic.test.ts @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("Mnemonic", () => { + + it("test isValid", () => { + const { Mnemonic } = globalThis.core; + + assert.isTrue( + Mnemonic.isValid( + "credit expect life fade cover suit response wash pear what skull force" + ) + ); + assert.isFalse( + Mnemonic.isValid( + "ripple scissors hisc mammal hire column oak again sun offer wealth tomorrow" + ) + ); + }); + + it("test isValidWord", () => { + const { Mnemonic } = globalThis.core; + + assert.isTrue(Mnemonic.isValidWord("credit")); + + assert.isFalse(Mnemonic.isValidWord("di")); + assert.isFalse(Mnemonic.isValidWord("cr")); + assert.isFalse(Mnemonic.isValidWord("hybridous")); + }); + + it("test suggest", () => { + 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 new file mode 100644 index 00000000000..8658a79d69e --- /dev/null +++ b/wasm/tests/PBKDF2.test.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("PBKDF2", () => { + it("test sha256 hmac", () => { + const { PBKDF2, HexCoding } = globalThis.core; + + const password = Buffer.from("password"); + const salt = Buffer.from("salt"); + + const key = PBKDF2.hmacSha256(password, salt, 1, 20); + const key2 = PBKDF2.hmacSha256(password, salt, 4096, 20); + + assert.equal(HexCoding.encode(key), "0x120fb6cffcf8b32c43e7225256c4f837a86548c9"); + assert.equal(HexCoding.encode(key2), "0xc5e478d59288c841aa530db6845c4c8d962893a0"); + }); +}); diff --git a/wasm/tests/StoredKey.test.ts b/wasm/tests/StoredKey.test.ts new file mode 100644 index 00000000000..7f3b04638c2 --- /dev/null +++ b/wasm/tests/StoredKey.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"; + +describe("StoredKey", () => { + it("test importing mnemonic", () => { + const { StoredKey, CoinType } = globalThis.core; + + const mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + const password = Buffer.from("password"); + const storedKey = StoredKey.importHDWallet( + mnemonic, + "test wallet", + password, + CoinType.bitcoin + ); + + assert.isTrue(storedKey.isMnemonic()); + + const jsonData = storedKey.exportJSON(); + const json = JSON.parse(Buffer.from(jsonData).toString()); + + assert.equal(json.type, "mnemonic"); + assert.equal(json.name, "test wallet"); + + const account = json.activeAccounts[0]; + + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal(account.extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + assert.equal(account.publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + + storedKey.delete(); + }).timeout(5000); +}); 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 new file mode 100644 index 00000000000..bcca79e9e1b --- /dev/null +++ b/wasm/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "outDir": "./dist", + "strict": true, + "skipLibCheck": true, + "typeRoots": [ + "./node_modules/@types" + ], + "types": [ + "node", + "mocha" + ], + "noImplicitAny": false + }, + "exclude": [ + "node_modules", + "./tests/**/*.ts" + ], +}