diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..33536958aad --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. + +* @hewigovens @catenocrypt diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a0460d715e..7f3a3e9b492 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,4 +23,5 @@ - [ ] Prefix PR title with `[WIP]` if necessary. - [ ] Add tests to cover changes as needed. - [ ] Update documentation as needed. -- [ ] If there is a related Issue, mention it in the description (e.g. 'Fixes #444 ). +- [ ] 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 # ). diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml new file mode 100644 index 00000000000..4724d5602a3 --- /dev/null +++ b/.github/workflows/android-ci.yml @@ -0,0 +1,40 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: macos-10.15 + + 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 + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Run test + run: | + tools/generate-files + tools/android-test + - name: Build sample app + run: | + tools/samples-build android diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml new file mode 100644 index 00000000000..474c194ccd5 --- /dev/null +++ b/.github/workflows/android-release.yml @@ -0,0 +1,41 @@ +name: Android Release + +on: + push: + tags: + - '*' + pull_request: + tags: + - '*' + +jobs: + release: + runs-on: macos-10.15 + 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 + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Generate files + run: tools/generate-files + - name: Upload to maven + run: | + tools/maven-android-upload + env: + BINTRAY_KEY: ${{ secrets.BINTRAY_API_KEY }} + BINTRAY_USER: ${{ secrets.BINTRAY_USER }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000000..369efae9cbd --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,30 @@ +name: Docker CI + +on: + push: + branches: [ master ] + paths: + - Dockerfile + - tools/install-dependencies + pull_request: + branches: [ master ] + paths: + - Dockerfile + - tools/install-dependencies + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Lint Dockerfile + run: | + curl -L https://github.com/hadolint/hadolint/releases/download/v1.17.6/hadolint-Linux-x86_64 -o hadolint && chmod +x hadolint + ./hadolint Dockerfile + + - name: Build Dockerfile + uses: docker/build-push-action@v1.1.0 + with: + repository: trustwallet/wallet-core + tags: latest + push: false diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml new file mode 100644 index 00000000000..4d922a6db57 --- /dev/null +++ b/.github/workflows/ios-ci.yml @@ -0,0 +1,35 @@ +name: iOS CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: macos-10.15 + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: | + brew install boost ninja xcodegen + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Run codegen tests + run: tools/codegen-test + - name: Run iOS tests + run: | + tools/generate-files + tools/ios-test + - name: Build sample app + run: | + tools/samples-build ios diff --git a/.github/workflows/ios-release.yml b/.github/workflows/ios-release.yml new file mode 100644 index 00000000000..5f8dc4e6003 --- /dev/null +++ b/.github/workflows/ios-release.yml @@ -0,0 +1,37 @@ +name: iOS Release + +on: + push: + tags: + - '*' + pull_request: + tags: + - '*' + +jobs: + release: + runs-on: macos-10.15 + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: | + brew install boost ninja xcodegen + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Generate files + run: tools/generate-files + - name: Build and release + run: | + tools/ios-release + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + GITHUB_TOKEN: ${{ secrets.WC_GITHUB_TOKEN }} + GITHUB_USER: ${{ secrets.WC_GITHUB_USER }} diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml new file mode 100644 index 00000000000..a1556dffeab --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,46 @@ +name: Linux CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - 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 + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Build and test + run: | + tools/generate-files + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON -DBOOST_ROOT=${BOOST_ROOT_1_72_0} + make -Cbuild -j12 tests TrezorCryptoTests + + build/trezor-crypto/tests/TrezorCryptoTests + build/tests/tests tests --gtest_output=xml + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + CK_TIMEOUT_MULTIPLIER: 4 + - name: Gather code coverage + run: | + sudo rm -rf coverage.info + tools/coverage diff --git a/.github/workflows/ts-ci.yml b/.github/workflows/ts-ci.yml new file mode 100644 index 00000000000..acc17b1602e --- /dev/null +++ b/.github/workflows/ts-ci.yml @@ -0,0 +1,25 @@ +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/ts-release.yml b/.github/workflows/ts-release.yml new file mode 100644 index 00000000000..7b5af2706c6 --- /dev/null +++ b/.github/workflows/ts-release.yml @@ -0,0 +1,73 @@ +name: Typescript Release + +on: + push: + tags: + - '*' + pull_request: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + outputs: + publish_npm: ${{ steps.version.outputs.publish_npm }} + publish_gpr: ${{ steps.version.outputs.publish_gpr }} + 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 + - name: Check if needed to publish + id: version + run: | + tools/set-tag-version + echo "::set-output name=publish_npm::$(tools/check-npm-version)" + echo "::set-output name=publish_gpr::$(tools/check-gpr-version)" + working-directory: typescript + env: + TOKEN: ${{secrets.GITHUB_TOKEN}} + publish-npm: + needs: build + if: needs.build.outputs.publish_npm == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://registry.npmjs.org/ + - name: Publish + run: | + tools/set-tag-version + yarn install && yarn build && npm publish --access public + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-gpr: + needs: build + if: needs.build.outputs.publish_gpr == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://npm.pkg.github.com/ + - name: Publish + run: | + tools/set-tag-version + yarn install && yarn build && npm publish --access public + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore index 7a5561d7def..a1d015d1a45 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ lib/protobuf .idea/ .vscode/ .project +.history/ # Generated files jni/cpp/generated @@ -33,6 +34,7 @@ include/TrustWalletCore/TW*Proto.h # Code coverage files coverage.info coverage/ +swift/test_output/ # Sourcetrail *.srctrldb diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 00000000000..438a9ddfd1b --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,3 @@ +ignored: + - DL3008 + - DL3015 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1877f80c137..a571eb1f40c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,11 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) project(TrustWalletCore) +# 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++") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ ${TW_CXX_WARNINGS}") set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -18,6 +20,13 @@ else() set(PREFIX "$ENV{PREFIX}") endif() +# Configure CCache if available +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif(CCACHE_FOUND) + include_directories(${PREFIX}/include) link_directories(${PREFIX}/lib) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..a188031d29e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +FROM ubuntu:18.04 + +# 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/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 \ + && apt-get install -y \ + build-essential \ + libtool autoconf pkg-config \ + ninja-build \ + ruby-full \ + clang-9 \ + llvm-9 \ + libc++-dev libc++abi-dev \ + cmake \ + libboost1.70-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +ENV CC=/usr/bin/clang-9 +ENV CXX=/usr/bin/clang++-9 + +# ↑ Setup build environment (could be a base image) +# ↓ Build and compile wallet core + +RUN git clone https://github.com/trustwallet/wallet-core.git +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 + +CMD ["/bin/bash"] diff --git a/README.md b/README.md index a42187be546..bdc9d0bf701 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -Trust Wallet Core is a cross-platform library that implements low-level cryptographic wallet functionality for all supported blockchains. Most of the code is C++ with a set of strict exported C interfaces. The library provides idiomatic interfaces for all supported languages (currently Swift for iOS and Java for Android). - -[![iOS status](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_apis/build/status/Wallet%20Core%20iOS)](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_build/latest?definitionId=13) -[![Android status](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_apis/build/status/Wallet%20Core%20Android)](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_build/latest?definitionId=11) -[![Linux status](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_apis/build/status/Wallet%20Core%20Linux)](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_build/latest?definitionId=24) +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. + +![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) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/82e76f6ea4ba4f0d9029e8846c04c093)](https://www.codacy.com/app/hewigovens/wallet-core?utm_source=github.com&utm_medium=referral&utm_content=TrustWallet/wallet-core&utm_campaign=Badge_Grade) ![Codecov](https://codecov.io/gh/TrustWallet/wallet-core/branch/master/graph/badge.svg) @@ -13,29 +19,19 @@ Trust Wallet Core is a cross-platform library that implements low-level cryptogr ![Cocoapods](https://img.shields.io/cocoapods/v/TrustWalletCore.svg) ![Cocoapods platforms](https://img.shields.io/cocoapods/p/TrustWalletCore.svg) -## Documentation +# Documentation -For more complete documentation, see [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core). +For comprehensive documentation, see [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core). -## Supported Blockchains +# Supported Blockchains -We support Bitcoin, Ethereum, Binance Chain and 50+ blockchains, you can see the full list [here](docs/coins.md). +Wallet Core supports more than **50** blockchains: Bitcoin, Ethereum, Binance Chain, and most major blockchain platforms. +The full list is [here](docs/coins.md). -## Building +# Building For build instructions, see [developer.trustwallet.com/wallet-core/building](https://developer.trustwallet.com/wallet-core/building). -## WalletConsole Utility - -Our project comes with an interactive command-line utility called _WalletConsole_, for accessing key and address management functionality of the library. It can be started using: - -``` -$ ./build/walletconsole/walletconsole -Type 'help' for list of commands. -> help -``` - -Further details: [developer.trustwallet.com/wallet-core/walletconsole](https://developer.trustwallet.com/wallet-core/walletconsole). # Using from your project @@ -47,10 +43,10 @@ Add this dependency to build.gradle: ```groovy dependencies { - implementation 'com.trustwallet:wallet-core:x.x.x' + implementation 'com.trustwallet:wallet-core:x.y.z' } ``` -[Replace with version](https://github.com/trustwallet/wallet-core/releases) +Replace x.y.z with a [fresh version](https://github.com/trustwallet/wallet-core/releases) ## iOS @@ -60,12 +56,18 @@ We currently support only CocoaPods. Add this line to your Podfile and run `pod pod 'TrustWalletCore' ``` -## Add your project below +# Projects + +Projects using Trust Wallet Core. Add yours too! + +[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/) -- [Trust Wallet](https://trustwallet.com) -- [coinpaprika](https://coinpaprika.com/) -- [IFWallet](https://www.ifwallet.com/) -- [Alice](https://www.alicedapp.com/) # Contributing diff --git a/TrustWalletCore.podspec b/TrustWalletCore.podspec index 028cb115716..3182e93d445 100644 --- a/TrustWalletCore.podspec +++ b/TrustWalletCore.podspec @@ -28,7 +28,7 @@ Pod::Spec.new do |s| end s.subspec 'Core' do |ss| - protobuf_source_dir = 'build/protobuf/staging/protobuf-3.9.0' + protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.9.0' include_dir = 'build/local/include' ss.source_files = 'src/**/*.{c,cc,cpp,h}', diff --git a/android/app/build.gradle b/android/app/build.gradle index 609fbaf8b18..4094afec59a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 28 - buildToolsVersion '28.0.3' + ndkVersion '21.2.6472646' defaultConfig { applicationId "com.trustwallet.core.app" minSdkVersion 23 @@ -18,11 +18,20 @@ android { release { minifyEnabled false } + debug { + minifyEnabled false + // limit platforms built for testing + ndk { + abiFilters 'x86' + } + } } } dependencies { implementation project(':trustwalletcore') + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.0.2' 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 8bfe4dd5268..64bfcf48ed9 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 @@ -5,6 +5,7 @@ import org.junit.Test import wallet.core.jni.HDWallet import wallet.core.jni.CoinType import wallet.core.jni.CoinType.* +import kotlinx.coroutines.* class CoinAddressDerivationTests { @@ -17,10 +18,12 @@ class CoinAddressDerivationTests { val wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", "") for (i in 0 .. 4) { - CoinType.values().forEach { coin -> - val privateKey = wallet.getKeyForCoin(coin) - val address = coin.deriveAddress(privateKey) - runDerivationChecks(coin, address) + GlobalScope.launch { + CoinType.values().forEach { coin -> + val privateKey = wallet.getKeyForCoin(coin) + val address = coin.deriveAddress(privateKey) + runDerivationChecks(coin, address) + } } } } @@ -29,10 +32,11 @@ class CoinAddressDerivationTests { BINANCE -> assertEquals("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", address) BITCOIN -> assertEquals("bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d", address) BITCOINCASH -> assertEquals("bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70", address) + BITCOINGOLD -> assertEquals("btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg", address) CALLISTO -> assertEquals("0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04", address) DASH -> assertEquals("XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT", address) DIGIBYTE -> assertEquals("dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu", address) - ETHEREUM -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + ETHEREUM, SMARTCHAIN -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address) GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address) GROESTLCOIN -> assertEquals("grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j", address) @@ -77,11 +81,14 @@ class CoinAddressDerivationTests { SOLANA -> assertEquals("2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m", address) TON -> assertEquals("EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo", address) ALGORAND -> assertEquals("JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ", address) - KUSAMA -> assertEquals("DE2jNrgosggXWJXfYDmRgy1q8XKkbtzSxj2uWAy5fbBfZwT", address) - POLKADOT -> assertEquals("1b97X8xTpFKMDzJpxiVhdYMNvekBDSfvGFf4DutxFkUjqfR", address) + KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) + POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) CARDANO -> assertEquals("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", address) NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) + ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) + BANDCHAIN -> assertEquals("band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r", address) + SMARTCHAINLEGACY -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt index 5c6ddfa4240..77cbc83a95f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt @@ -82,8 +82,8 @@ class TestHDWallet { @Test fun testPublicKeyFromX() { val xpub = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj" - val xpubAddr2 = HDWallet.getPublicKeyFromExtended(xpub, "m/44'/145'/0'/0/2") - val xpubAddr9 = HDWallet.getPublicKeyFromExtended(xpub, "m/44'/145'/0'/0/9") + val xpubAddr2 = HDWallet.getPublicKeyFromExtended(xpub, CoinType.BITCOINCASH,"m/44'/145'/0'/0/2") + val xpubAddr9 = HDWallet.getPublicKeyFromExtended(xpub, CoinType.BITCOINCASH,"m/44'/145'/0'/0/9") assertEquals( Numeric.toHexString(xpubAddr2.data()), diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt deleted file mode 100644 index a8b6f138dc6..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.trustwallet.core.app.blockchains - -import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.Hash -import org.junit.Assert.assertEquals -import org.junit.Test - -class TestHash { - - init { - System.loadLibrary("TrustWalletCore") - } - - @Test - fun testHashKeccak256() { - val bytes = Hash.keccak256("Test keccak-256".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x9aeb50f48121c80b2ff73ad48b5f197d940f748d936d35c992367370c1abfb18") - } - - @Test - fun testSha1() { - val bytes = Hash.sha1("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x5df9954f1ca26eabf18c663cc9258f7f1fd09f9e") - } - - @Test - fun testSha256() { - val bytes = Hash.sha256("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0xf250fc8f40aeea3297c0158ec1bfa07b503805f2a822530bd63c50625bb9376b") - } - - @Test - fun testSha512() { - val bytes = Hash.sha512("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x14ee17baf1fa3b1c5b3d3f232d609bc8e8c22dc1c4c8a81ac3d51468a27cc2431a54726d511f467d3420f37d5fc3694e8001990b706c4cc9239c397b4a7522e9") - } - - @Test - fun testKeccak512() { - val bytes = Hash.keccak512("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x6caadef962497d4ee4769b854b00cc0eb922cbfc1c8d676bc193ae9fc8d09c9c044d9771dfd96dc362db0dec6dba593a870806de283d177a5d07e36a9aa52077") - } - - @Test - fun testSha3_256() { - val bytes = Hash.sha3256("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0xc0f4cbc9992e2085fbe43a73bc1e2938f54babc0ede584d47d9df4e4511c8c62") - } - - @Test - fun testSha3_512() { - val bytes = Hash.sha3512("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x4fda1bee2d0c28e5eaf7fff8cbb48eed946a6aec7090c610d71896059fc942cfef1a56b811aefe31a750cce4f27921032100a7030aa8b347b3720494a1561fb9") - } - - @Test - fun testBlake2b256() { - val bytes = Hash.blake2b("Test hash".toByteArray(), 32) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0xe45cf5595c68cb024ad6ec872ab6b7e88377015712e775f643da6af788b5347f") - } - - @Test - fun testRipemd160() { - val bytes = Hash.ripemd("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x687901a63dc3d0dc884232fbaee0badbda853cfa") - } - - @Test - fun testSha256RIPEMD() { - val bytesExpected = Hash.ripemd(Hash.sha256("Test hash".toByteArray())) - val expHex = Numeric.toHexString(bytesExpected) - val bytes = Hash.sha256RIPEMD("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, expHex) - } -} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java deleted file mode 100644 index 15228b3d554..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.trustwallet.core.app.blockchains; - -import wallet.core.jni.SegwitAddress; -import wallet.core.jni.HRP; -import wallet.core.jni.PublicKey; -import wallet.core.jni.PublicKeyType; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class TestSegwitAddress { - - static { - System.loadLibrary("TrustWalletCore"); - } - - @Test - public void testFromPublic() { - byte[] data = {0x02, - (byte) 0xf1, (byte) 0xe7, (byte) 0x33, (byte) 0xed, (byte) 0x60, (byte) 0x30, (byte) 0xcc, (byte) 0x56, - (byte) 0x9c, (byte) 0x43, (byte) 0x23, (byte) 0xa3, (byte) 0x4b, (byte) 0x17, (byte) 0xe1, (byte) 0x92, - (byte) 0xd5, (byte) 0x81, (byte) 0x07, (byte) 0xd9, (byte) 0xff, (byte) 0xbc, (byte) 0xe7, (byte) 0x1c, - (byte) 0x84, (byte) 0x20, (byte) 0xb7, (byte) 0x79, (byte) 0xf4, (byte) 0x84, (byte) 0xdb, (byte) 0xa1 - }; - PublicKey publicKey = new PublicKey(data, PublicKeyType.SECP256K1); - SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); - assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); - } - - @Test - public void testBadHrp() { - byte[] data = {0x02, - (byte) 0xf1, (byte) 0xe7, (byte) 0x33, (byte) 0xed, (byte) 0x60, (byte) 0x30, (byte) 0xcc, (byte) 0x56, - (byte) 0x9c, (byte) 0x43, (byte) 0x23, (byte) 0xa3, (byte) 0x4b, (byte) 0x17, (byte) 0xe1, (byte) 0x92, - (byte) 0xd5, (byte) 0x81, (byte) 0x07, (byte) 0xd9, (byte) 0xff, (byte) 0xbc, (byte) 0xe7, (byte) 0x1c, - (byte) 0x84, (byte) 0x20, (byte) 0xb7, (byte) 0x79, (byte) 0xf4, (byte) 0x84, (byte) 0xdb, (byte) 0xa1 - }; - PublicKey publicKey = new PublicKey(data, PublicKeyType.SECP256K1); - SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); - assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); - } -} 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 new file mode 100644 index 00000000000..d988eed5bb1 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt @@ -0,0 +1,32 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.bandchain + +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 TestBandChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(publicKey, CoinType.BANDCHAIN) + val expected = AnyAddress("band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", CoinType.BANDCHAIN) + + assertEquals(publicKey.data().toHex(), "0x035df185566521d6a7802319ee06e1a28e97b7772dfb5fdd13ca6f0575518968e4") + assertEquals(address.description(), expected.description()) + } +} 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 new file mode 100644 index 00000000000..0e25638c1de --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt @@ -0,0 +1,107 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.bandchain + +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.BANDCHAIN +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput + +class TestBandChainSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigningTransaction() { + val key = + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, BANDCHAIN).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = 1 + denom = "uband" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = 200 + denom = "uband" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + accountNumber = 1037 + chainId = "band-wenchang-testnet2" + memo = "" + sequence = 8 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, BANDCHAIN, SigningOutput.parser()) + val jsonPayload = output.json + + val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"uband"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"uband"}],"from_address":"band1hsk6jryyqjfhp5dhc55tc9jtckygx0epw4d0hz","to_address":"band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"B1ZG7pUWW2mPxE7WzBt8SafZMtEtFWgUJePJ+Dj/q7cHxj4scGmopQUG4+AZcJbRQgjrMGM11Yhm3vXYQYtSDA=="}]}}""" + assertEquals(expectedJsonPayload, jsonPayload) + + } + + @Test + fun testSigningJSON() { + val json = """ + { + "accountNumber": "8733", + "chainId": "band-wenchang-testnet2", + "fee": { + "amounts": [{ + "denom": "uband", + "amount": "5000" + }], + "gas": "200000" + }, + "memo": "Testing", + "messages": [{ + "sendCoinsMessage": { + "fromAddress": "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", + "toAddress": "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh", + "amounts": [{ + "denom": "uband", + "amount": "995000" + }] + } + }] + } + """ + val key = "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() + val result = AnySigner.signJSON(json, key, BANDCHAIN.value()) + + assertEquals(result, """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uband"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uband"}],"from_address":"band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3","to_address":"band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"sw2YPxjQ5DiKjd2o70sQb44OSzMH2Pm4V+Z8ld1uYiNbMXWQBK8SH2tcKUIU3SwYZ1qvi2DbmxqHyONksJ0Rmg=="}]}}""") + } +} 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 ec8b8b85c5b..531d8c20303 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 @@ -6,6 +6,9 @@ import com.trustwallet.core.app.utils.toHexBytes 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.proto.Bitcoin import wallet.core.jni.proto.Bitcoin.SigningOutput @@ -20,7 +23,7 @@ class TestBitcoinSigning { fun testSignP2WPKH() { val input = Bitcoin.SigningInput.newBuilder() .setAmount(335_790_000) - .setHashType(0x01) + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN)) .setToAddress("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx") .setChangeAddress("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU") .setByteFee(1) @@ -75,7 +78,7 @@ class TestBitcoinSigning { assertEquals(2, signedTransaction.outputsCount) val encoded = output.encoded - assertEquals("0x0100000001fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000049483045022100b6006eb0fe2da8cbbd204f702b1ffdb1e29c49f3de51c4983d420bf9f9125635022032a195b153ccb2c4978333b4aad72aaa7e6a0b334a14621d5d817a42489cb0d301ffffffff02b0bf0314000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088acaefd3c11000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000", + assertEquals("0x0100000001fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000049483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01ffffffff02b0bf0314000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088acd0fd3c11000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000", Numeric.toHexString(encoded.toByteArray())); } @@ -84,7 +87,7 @@ class TestBitcoinSigning { fun testSignP2PKH() { val input = Bitcoin.SigningInput.newBuilder() .setAmount(55_000) - .setHashType(0x01) + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN)) .setToAddress("1GDCMHsTLBkawQXP8dqcZtr8zGgb4XpCug") .setChangeAddress("1CSR6tXqngr1CfwVF23V4bQotttJmzXqpf") .setByteFee(10) @@ -133,8 +136,8 @@ class TestBitcoinSigning { val plan = AnySigner.plan(input.build(), BITCOIN, Bitcoin.TransactionPlan.parser()) assertEquals(55_000, plan.amount) assertEquals(75_000, plan.availableAmount) - assertEquals(3740, plan.fee) - assertEquals(16260, plan.change) + assertEquals(2610, plan.fee) + assertEquals(17390, plan.change) // Set the precomputed plan input.plan = plan @@ -148,7 +151,7 @@ class TestBitcoinSigning { assertEquals(2, signedTransaction.outputsCount) val encoded = output.encoded - assertEquals("0x01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000049483045022100991ea84c8f22cbcbdee114a687b31bc80fca181161adc354e37b16b0f4664a6f022016e34b232524a1296a636026f8bb1f5f3635d88bf936532aae70a499c52f77d201ffffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02d8d60000000000001976a914a6d85a488bb777a540f24bf777d30d1486036f6188ac843f0000000000001976a9147d77e6cfb05a9cfc123824279f6caf8b66ac267688ac000247304402200ebd8fe637d7344984dd173d4a3089c4fc03a51117ee0363d04c714f033b33cf02204e2831939fff068068cc08fe35d84950f244fd2fe39795d839bfb8795484cc230121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635700000000", + assertEquals("0x01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008d49c4d7cc5ab93c01a67ce3f4ed2c45c59d4da6c76c891a9b56e67eda2e8cb4022078849134c697b1c70c1a19b900d94d8cab00ad7bcc8afe7ad1f6b184c13effa601ffffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02d8d60000000000001976a914a6d85a488bb777a540f24bf777d30d1486036f6188acee430000000000001976a9147d77e6cfb05a9cfc123824279f6caf8b66ac267688ac0002473044022074573d7f7828ae193fbea6d72c0fe2df6cee5c02bf455ea3d9312e16d6a9576502203861c5a3b3a83d4fe372034073f60201a8a944fb4536be0ea7544ab177b967600121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635700000000", Numeric.toHexString(encoded.toByteArray())); } } 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 new file mode 100644 index 00000000000..05c17837ddc --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt @@ -0,0 +1,49 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 new file mode 100644 index 00000000000..9b93c7dd59f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt @@ -0,0 +1,59 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 junit.framework.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/ethereum/TestEthereumAbi.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt new file mode 100644 index 00000000000..5461b7e6ed0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt @@ -0,0 +1,42 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.EthereumAbi + +class TestEthereumAbiDecoder { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumAbiDecodeApprove() { + val call = "095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed0000000000000000000000000000000000000000000000000000000000000001".toHexByteArray() + val abi = """ + { + "095ea7b3": { + "constant": false, + "inputs": [{ + "name": "_spender", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + } + """.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"}]}""" + + assertEquals(decoded, expected) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiEncoder.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt similarity index 73% rename from android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiEncoder.kt rename to android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt index 994229a6f73..aae6fc451ef 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiEncoder.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt @@ -5,8 +5,8 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.EthereumAbiFunction import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.EthereumAbiEncoder -import wallet.core.jni.EthereumAbiValueEncoder +import wallet.core.jni.EthereumAbi +import wallet.core.jni.EthereumAbiValue class TestEthereumAbiEncoder { @@ -16,12 +16,12 @@ class TestEthereumAbiEncoder { @Test fun testEthereumAbiEncoderFuncSimple1() { - val function = EthereumAbiEncoder.buildFunction("sam") + val function = EthereumAbiFunction("sam") assertEquals(0, function.addParamBool(true, false)) assertEquals("sam(bool)", function.type) - val encoded = EthereumAbiEncoder.encode(function) + val encoded = EthereumAbi.encode(function) assertEquals("0xa35856da" + "0000000000000000000000000000000000000000000000000000000000000001", Numeric.toHexString(encoded)); @@ -29,7 +29,7 @@ class TestEthereumAbiEncoder { @Test fun testEthereumAbiEncoderEncodeFuncCase1() { - val function = EthereumAbiEncoder.buildFunction("sam") + val function = EthereumAbiFunction("sam") // add params assertEquals(0, function.addParamBytes("0x64617665".toHexByteArray(), false)) assertEquals(1, function.addParamBool(true, false)) @@ -41,7 +41,7 @@ class TestEthereumAbiEncoder { // check signature assertEquals("sam(bytes,bool,uint256[])", function.type) // encode - val encoded = EthereumAbiEncoder.encode(function) + val encoded = EthereumAbi.encode(function) assertEquals("0xa5643bf2" + "0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000001" + @@ -57,7 +57,7 @@ class TestEthereumAbiEncoder { assertEquals(0, function.getParamUInt64(0, true)) // decode output val encodedOutput = "0000000000000000000000000000000000000000000000000000000000000045".toHexByteArray() - val decodeRes = EthereumAbiEncoder.decodeOutput(function, encodedOutput) + val decodeRes = EthereumAbi.decodeOutput(function, encodedOutput) assertEquals(true, decodeRes) // new output value assertEquals(0x45, function.getParamUInt64(0, true)) @@ -65,7 +65,21 @@ class TestEthereumAbiEncoder { @Test fun testEthereumAbiValueEncodeInt32() { - val data = EthereumAbiValueEncoder.encodeInt32(69) + val data = EthereumAbiValue.encodeInt32(69) assertEquals(Numeric.toHexString(data), "0x0000000000000000000000000000000000000000000000000000000000000045") } + + @Test + fun testEthereumAbiValueDecodeUInt256() { + val expected = "1234567890987654321" + val inputs = listOf( + "112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb10000000000000000000000000000000000000000000000000000000000000000" + ) + for (input in inputs) { + val data = Numeric.hexStringToByteArray(input) + assertEquals(expected, EthereumAbiValue.decodeUInt256(data)) + } + } } 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 new file mode 100644 index 00000000000..f4b29c5db5d --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt @@ -0,0 +1,23 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import org.junit.Assert.assertFalse + +class TestEthereumAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumAddresses() { + val any = AnyAddress("0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1", CoinType.ETHEREUM) + assertEquals(any.coin(), CoinType.ETHEREUM) + assertEquals(any.description(), "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1") + + assertFalse(AnyAddress.isValid("0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4", CoinType.ETHEREUM)) + } +} 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 4a8963e1eca..500b9c9e1cb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt @@ -9,6 +9,7 @@ import wallet.core.java.AnySigner import wallet.core.jni.proto.Ethereum import wallet.core.jni.proto.Ethereum.SigningOutput import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertArrayEquals import wallet.core.jni.CoinType.ETHEREUM class TestEthereumTransactionSigner { @@ -30,10 +31,21 @@ class TestEthereumTransactionSigner { amount = ByteString.copyFrom("0x0de0b6b3a7640000".toHexByteArray()) } - val sign = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + val encoded = AnySigner.encode(signingInput.build(), ETHEREUM) - assertEquals(Numeric.toHexString(sign.v.toByteArray()), "0x25") - assertEquals(Numeric.toHexString(sign.r.toByteArray()), "0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276") - assertEquals(Numeric.toHexString(sign.s.toByteArray()), "0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + assertArrayEquals(output.encoded.toByteArray(), encoded) + assertEquals(Numeric.toHexString(output.v.toByteArray()), "0x25") + assertEquals(Numeric.toHexString(output.r.toByteArray()), "0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276") + assertEquals(Numeric.toHexString(output.s.toByteArray()), "0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + assertEquals(Numeric.toHexString(encoded), "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + } + + @Test + fun testEthereumTransactionDecoding() { + val rawTx = "0xf8a86484b2d05e008277fb9400000000000c2e074ec69a0dfb2997ba6c7d2e1e80b8441896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba4125a0b55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4dfa077b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d".toHexByteArray() + val decoded = AnySigner.decode(rawTx, ETHEREUM) + + assertEquals(String(decoded), """{"gas":"0x77fb","gasPrice":"0xb2d05e00","input":"0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41","nonce":"0x64","r":"0xb55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4df","s":"0x77b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d","to":"0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e","v":"0x25","value":"0x"}""") } } 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 cce536ff660..6b6461e78d1 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 @@ -9,6 +9,7 @@ package com.trustwallet.core.app.blockchains.fio import com.trustwallet.core.app.utils.toHex import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test import wallet.core.jni.* @@ -28,4 +29,23 @@ class TestFIOAddress { val addressFromKey = AnyAddress(pubkey, CoinType.FIO) assertEquals(addressFromKey.description(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o") } + + @Test + fun testAccount() { + assertEquals(FIOAccount("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE").description(), "hhq2g4qgycfb") + assertEquals(FIOAccount("hhq2g4qgycfb").description(), "hhq2g4qgycfb") + assertEquals(FIOAccount("rewards@wallet").description(), "rewards@wallet") + + var account: FIOAccount? = null + var account2: FIOAccount? = null + try { + account = FIOAccount("asdf19s") + account2 = FIOAccount("0x320196ef1b137786be51aa391e78e0a2c756f46b") + } catch (ex: Exception) { + print(ex) + } + + assertNull(account) + assertNull(account2) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt new file mode 100644 index 00000000000..1871c6c0d76 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt @@ -0,0 +1,227 @@ +package com.trustwallet.core.app.blockchains.IoTeX + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytes +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.IOTEX +import wallet.core.jni.proto.IoTeX +import wallet.core.jni.proto.IoTeX.SigningOutput + +class TestIotexSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testIotexSigningCreate() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Create + val create = IoTeX.Staking.Create.newBuilder().apply { + candidateName = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + stakedAmount = "100" + stakedDuration = 10000 + autoStake = true + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeCreate = create + } + + val sign = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytes = sign.encoded + assertEquals(signBytes.toByteArray().toHex(), "0x0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5aed8e2e026d46e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801") + } + fun testIotexSigningAddDeposit() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign AddDeposit + val adddeposit = IoTeX.Staking.AddDeposit.newBuilder().apply { + bucketIndex = 10 + amount = "10" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeAddDeposit = adddeposit + } + + var signAddDeposit = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + var signBytesAddDeposit = signAddDeposit.encoded + assertEquals(signBytesAddDeposit.toByteArray().toHex(), "0x0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb55e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01") + } + fun testIotexSigningUnstake() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Unstake + val unstake = IoTeX.Staking.Reclaim.newBuilder().apply { + bucketIndex = 10 + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeUnstake = unstake + } + + val signUnstake = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesUnstake = signUnstake.encoded + assertEquals(signBytesUnstake.toByteArray().toHex(), "0x0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601") + } + fun testIotexSigningWithdraw() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Withdraw + val withdraw = IoTeX.Staking.Reclaim.newBuilder().apply { + bucketIndex = 10 + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeWithdraw = withdraw + } + + val signWithdraw = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesWithdraw = signWithdraw.encoded + assertEquals(signBytesWithdraw.toByteArray().toHex(), "0x0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00") + } + fun testIotexSigningRestake() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Restake + val restake = IoTeX.Staking.Restake.newBuilder().apply { + bucketIndex = 10 + stakedDuration = 1000 + autoStake = true + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeRestake = restake + } + + val signRestake = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesRestake = signRestake.encoded + assertEquals(signBytesRestake.toByteArray().toHex(), "0x0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418eedbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101") + } + fun testIotexSigningChangeCandidate() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign ChangeCandidate + val changecandidate = IoTeX.Staking.ChangeCandidate.newBuilder().apply { + bucketIndex = 10 + candidateName = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeChangeCandidate = changecandidate + } + + val signChangeCandidate = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesChangeCandidate = signChangeCandidate.encoded + assertEquals(signBytesChangeCandidate.toByteArray().toHex(), "0x0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f81895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801") + } + fun testIotexSigningTransferOwnership() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign TransferOwnership + val transfer = IoTeX.Staking.TransferOwnership.newBuilder().apply { + bucketIndex = 10 + voterAddress = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeTransferOwnership = transfer + } + + val signTransferOwnership = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesTransferOwnership = signTransferOwnership.encoded + assertEquals(signBytesTransferOwnership.toByteArray().toHex(), "0x0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01") + } + fun testIotexSigningCandidateBasicInfo() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign CandidateBasicInfo + val cbi = IoTeX.Staking.CandidateBasicInfo.newBuilder().apply { + name = "test" + operatorAddress = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng" + rewardAddress = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd" + }.build() + + input.apply { + candidateUpdate = cbi + } + + val signCandidateBasicInfo = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesCandidateBasicInfo = signCandidateBasicInfo.encoded + assertEquals(signBytesCandidateBasicInfo.toByteArray().toHex(), "0x0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c32657635646661393838716d677a673278346866617a6d7039766e326736366e671a29696f316a757678356730363365753474733833326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701") + } + fun testIotexSigningCandidateRegister() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("1000") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign CandidateBasicInfo + val cbi = IoTeX.Staking.CandidateBasicInfo.newBuilder().apply { + name = "test" + operatorAddress = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he" + rewardAddress = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv" + }.build() + val cr = IoTeX.Staking.CandidateRegister.newBuilder().apply { + candidate = cbi + stakedAmount = "100" + stakedDuration = 10000 + autoStake = false + ownerAddress ="io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + input.apply { + candidateRegister = cr + } + + val signCandidateRegister = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesCandidateRegister = signCandidateRegister.encoded + assertEquals(signBytesCandidateRegister.toByteArray().toHex(), "0x0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a32077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a9179b141ac68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400") + } +} 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 552d7e30173..d7e1a571fb8 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 @@ -11,43 +11,43 @@ import com.trustwallet.core.app.utils.toHexBytesInByteString import junit.framework.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner -import wallet.core.jni.CoinType.POLKADOT +import wallet.core.jni.CoinType.KUSAMA import wallet.core.jni.proto.Polkadot import wallet.core.jni.proto.Polkadot.SigningOutput -class TestPolkadotSigner { +class TestKusamaSigner { init { System.loadLibrary("TrustWalletCore") } @Test - fun PolkadotTransactionSigning() { - val key = "0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115".toHexBytesInByteString() + fun KusamaTransactionSigning() { + val key = "0x8cdc538e96f460da9d639afc5c226f477ce98684d77fb31e88db74c1f1dd86b2".toHexBytesInByteString() val hash = "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe".toHexBytesInByteString() val call = Polkadot.Balance.Transfer.newBuilder().apply { - toAddress = "FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP" - value = "3039".toHexBytesInByteString() - }.build() + toAddress = "CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY" + value = "0x02540be400".toHexBytesInByteString() + } - val signingInput = Polkadot.SigningInput.newBuilder().apply { + val input = Polkadot.SigningInput.newBuilder().apply { genesisHash = hash blockHash = hash - nonce = 0 - specVersion = 1031 + nonce = 1 + specVersion = 2019 network = Polkadot.Network.KUSAMA - extrinsicVersion = 4 + transactionVersion = 2 privateKey = key balanceCall = Polkadot.Balance.newBuilder().apply { - transfer = call + transfer = call.build() }.build() - }.build() + } - val output = AnySigner.sign(signingInput, POLKADOT, SigningOutput.parser()) + val output = AnySigner.sign(input.build(), KUSAMA, SigningOutput.parser()) val encoded = Numeric.toHexString(output.encoded.toByteArray()) - val expected = "0x2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0" + val expected = "0x350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402" assertEquals(encoded, expected) } } 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 new file mode 100644 index 00000000000..f3b3b32edbe --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt @@ -0,0 +1,54 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.polkadot + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytesInByteString +import junit.framework.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.POLKADOT +import wallet.core.jni.proto.Polkadot +import wallet.core.jni.proto.Polkadot.SigningOutput + +class TestPolkadotSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun PolkadotTransactionSigning() { + val key = "0x37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62".toHexBytesInByteString() + val hash = "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3".toHexBytesInByteString() + + val call = Polkadot.Staking.Bond.newBuilder().apply { + controller = "14CeRumfZBNBVux9GgaiR5qw9E8RndNsiFWvhcHs76HEPjbP" + value = "0x02540be400".toHexBytesInByteString() + rewardDestination = Polkadot.RewardDestination.STAKED + } + + val input = Polkadot.SigningInput.newBuilder().apply { + genesisHash = hash + blockHash = hash + nonce = 0 + specVersion = 17 + network = Polkadot.Network.POLKADOT + transactionVersion = 3 + privateKey = key + stakingCall = Polkadot.Staking.newBuilder().apply { + bond = call.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLKADOT, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + val expected = "0x3902848d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa00a559867d1304cc95bac7cfe5d1b2fd49aed9f43c25c7d29b9b01c1238fa1f6ffef34b9650e42325de41e20fd502af7b074c67a9ec858bd9a1ba6d4212e3e0d0f00000007008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0700e40b540200" + assertEquals(encoded, expected) + } +} 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 new file mode 100644 index 00000000000..35c0f9e9abd --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.binancesmartchain + +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 TestBinanceSmartChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.SMARTCHAIN) + val expected = AnyAddress("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", CoinType.SMARTCHAIN) + + assertEquals(address.description(), expected.description()) + } +} 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 new file mode 100644 index 00000000000..254f79bb860 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt @@ -0,0 +1,53 @@ +package com.trustwallet.core.app.blockchains.tron + +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.proto.Tron +import wallet.core.jni.proto.Tron.SigningOutput +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertArrayEquals +import wallet.core.jni.CoinType.TRON + +class TestTronTransactionSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignTransferTrc20Contract() { + val trc20Contract = Tron.TransferTRC20Contract.newBuilder() + .setOwnerAddress("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC") + .setContractAddress("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV") + .setToAddress("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z") + // 1000 + .setAmount(ByteString.copyFrom("0x00000000000000000000000000000000000000000000000000000000000003e8".toHexByteArray())) + + val blockHeader = Tron.BlockHeader.newBuilder() + .setTimestamp(1539295479000) + .setTxTrieRoot(ByteString.copyFrom("0x64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d".toHexByteArray())) + .setParentHash(ByteString.copyFrom("0x00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d".toHexByteArray())) + .setNumber(3111739) + .setWitnessAddress(ByteString.copyFrom("0x415863f6091b8e71766da808b1dd3159790f61de7d".toHexByteArray())) + .setVersion(3) + .build() + + val transaction = Tron.Transaction.newBuilder() + .setTransferTrc20Contract(trc20Contract) + .setTimestamp(1539295479000) + .setBlockHeader(blockHeader) + .build() + + val signingInput = Tron.SigningInput.newBuilder() + .setTransaction(transaction) + .setPrivateKey(ByteString.copyFrom("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54".toHexByteArray())) + + val output = AnySigner.sign(signingInput.build(), TRON, Tron.SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.id.toByteArray()), "0x0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058") + assertEquals(Numeric.toHexString(output.signature.toByteArray()), "0xbec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01") + } +} 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 3a19d9c2218..3832db95139 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 @@ -13,9 +13,9 @@ class TestKeyStore { @Test fun testDecryptMnemonic() { - val keyStore = StoredKey("Test Wallet", "password") - val result = keyStore.decryptMnemonic("wrong") - val result2 = keyStore.decryptMnemonic("password") + val keyStore = StoredKey("Test Wallet", "password".toByteArray()) + val result = keyStore.decryptMnemonic("wrong".toByteArray()) + val result2 = keyStore.decryptMnemonic("password".toByteArray()) assertNull(result) assertNotNull(result2) @@ -23,7 +23,7 @@ class TestKeyStore { @Test fun testRemoveCoins() { - val password = "password" + val password = "password".toByteArray() val keyStore = StoredKey("Test Wallet", password) val wallet = keyStore.wallet(password) @@ -37,15 +37,47 @@ class TestKeyStore { assertEquals(keyStore.account(0).coin(), CoinType.ETHEREUM) } + @Test + fun testLongHexPassword() { + val json = """ + { + "address": "34bae2218c254ed190c0f5b1dd4323aee8e7da09", + "id": "86066d8c-8dba-4d81-afd4-934e2a2b72a2", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "a4976ad73057007ad788d1f792db851d" + }, + "ciphertext": "5e4458d69964172c492616b751d6589b4ad7da4217dcfccecc3f4e515a934bb8", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "24c72d92bf88a4f7c7b3f5e3cb3620714d71fceabbb0bc6099f50c6d5d898e7c" + }, + "mac": "c15e3035ddcaca766dfc56648978d33e94d3c57d4a5e13fcf8b5f8dbb0902900" + } + } + """.trimIndent() + val password = "2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd" + val pass = password.toHexByteArray() + val keyStore = StoredKey.importJSON(json.toByteArray()) + val privateKey = keyStore.decryptPrivateKey(pass) + assertEquals(privateKey.toHex(), "0x043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b") + } + @Test fun testExportJSON() { - val password = "password" + val password = "password".toByteArray() val keyStore = StoredKey("Test Wallet", password) val json = keyStore.exportJSON() assertNotNull(json) val newKeyStore = StoredKey.importJSON(json) - val privateKey = newKeyStore.decryptPrivateKey("") + val privateKey = newKeyStore.decryptPrivateKey("".toByteArray()) assertNull(privateKey) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java index 3e2345565eb..1289b970d56 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java @@ -21,12 +21,4 @@ public void testFromPublic() { SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); } - - @Test - public void testBadHrp() { - byte[] data = Numeric.INSTANCE.hexStringToByteArray("0x02f1e733ed6030cc569c4323a34b17e192d58107d9ffbce71c8420b779f484dba1"); - PublicKey publicKey = new PublicKey(data, PublicKeyType.SECP256K1); - SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); - assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); - } } diff --git a/android/build.gradle b/android/build.gradle index c3b350a38ce..f27ea5cf3ad 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,11 +7,11 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:4.0.1' 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.4' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.9.10' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.15.2' } } diff --git a/android/gradle.properties b/android/gradle.properties index 01f57a6814d..53c570a2de0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -7,6 +7,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m +org.gradle.caching=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -19,17 +20,16 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -android.enableR8 = false VERSION_NAME=0.12.1 VERSION_CODE=1 GROUP=com.trustwallet.walletcore POM_DESCRIPTION=Cross-platform, cross-blockchain wallet library. -POM_URL=https://github.com/TrustWallet/wallet-core -POM_SCM_URL=https://github.com/TrustWallet/wallet-core -POM_SCM_CONNECTION=scm:git@github.com:TrustWallet/wallet-core.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:TrustWallet/wallet-core.git +POM_URL=https://github.com/trustwallet/wallet-core +POM_SCM_URL=https://github.com/trustwallet/wallet-core +POM_SCM_CONNECTION=scm:git@github.com:trustwallet/wallet-core.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:trustwallet/wallet-core.git POM_LICENCE_NAME=MIT POM_LICENCE_URL=https://opensource.org/licenses/MIT POM_LICENCE_DIST=repo diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index f6b961fd5a8..62d4c053550 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 3521434ef95..4f930cba13a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 07 08:02:53 CST 2020 +#Thu May 14 14:21:48 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip diff --git a/android/gradlew b/android/gradlew index cccdd3d517f..fbd7c515832 100755 --- a/android/gradlew +++ b/android/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ 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="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ 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 @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# 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 @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` 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" ;; + 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 @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +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" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat index f9553162f12..a9f778a7a96 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -1,84 +1,104 @@ -@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=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@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= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -: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 - -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% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="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 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +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%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +: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 + +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% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/trustwalletcore/build.gradle b/android/trustwalletcore/build.gradle index 83bc1d15148..ffd218d3aa3 100644 --- a/android/trustwalletcore/build.gradle +++ b/android/trustwalletcore/build.gradle @@ -4,7 +4,7 @@ group='com.github.trustwallet' android { compileSdkVersion 28 - + ndkVersion '21.2.6472646' defaultConfig { minSdkVersion 23 targetSdkVersion 28 @@ -21,6 +21,13 @@ android { release { minifyEnabled false } + debug { + minifyEnabled false + // limit platforms built for testing + ndk { + abiFilters 'x86' + } + } } sourceSets { diff --git a/bootstrap.sh b/bootstrap.sh index e5ddf642a52..f8898e319c4 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -11,7 +11,7 @@ tools/generate-files echo "#### Building... ####" cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -make -Cbuild tests TrezorCryptoTests +make -Cbuild -j12 tests TrezorCryptoTests if [ -x "$(command -v clang-tidy)" ]; then echo "#### Linting... ####" diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake index 72f271d70d8..026b61ac3a4 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -1,5 +1,5 @@ -set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/protobuf/staging/protobuf-3.9.0) -set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/protobuf/staging/protobuf-3.9.0) +set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.9.0) +set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.9.0) # Updated from https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotopuf.cmake @@ -156,4 +156,4 @@ set_target_properties( LINK_FLAGS -no-undefined ) -target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1) +target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32) diff --git a/codegen/bin/coins b/codegen/bin/coins index 428469a8c24..06fe37f07fb 100755 --- a/codegen/bin/coins +++ b/codegen/bin/coins @@ -8,14 +8,10 @@ require 'json' def self.format_name(n) formatted = n formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') + formatted = formatted.gsub(/\s/, '') formatted end -def self.coin_type(path) - path.split('/')[2].chomp("'") -end - def self.coin_img(coin) "" end @@ -29,8 +25,7 @@ def self.explorer_account_url(c) end json_string = File.read('coins.json') -coins = JSON.parse(json_string).sort_by { |x| x['name'] } -coins_md = coins.sort_by { |x| Integer(coin_type(x['derivationPath'])) } +coins = JSON.parse(json_string).sort_by { |x| x['coinId'] } erbs = [ {'template' => 'CoinInfoData.cpp.erb', 'folder' => 'src/Generated', 'file' => 'CoinInfoData.cpp'}, diff --git a/codegen/bin/cointests b/codegen/bin/cointests index 037285b56bf..e6e0a765ede 100755 --- a/codegen/bin/cointests +++ b/codegen/bin/cointests @@ -36,10 +36,6 @@ def self.display_name(coin) name end -def self.coin_type(path) - path.split('/')[2].chomp("'") -end - # Explorer urls def self.explorer_tx_url(c) path = c['explorer']['url'].to_s + c['explorer']['txPath'].to_s @@ -64,7 +60,6 @@ end json_string = File.read('coins.json') coins = JSON.parse(json_string).sort_by { |x| x['name'] } -coins_md = coins.sort_by { |x| Integer(coin_type(x['derivationPath'])) } erbs = [ {'template' => 'TWCoinTypeTests.cpp.erb', 'folder' => 'tests/interface', 'file' => 'TWCoinTypeTests.cpp'} diff --git a/codegen/bin/newcoin b/codegen/bin/newcoin index 8356e537275..6bbcde6ef18 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -14,15 +14,10 @@ require 'entity_decl' require 'code_generator' require 'coin_test_gen' -def self.coin_type(path) - path.split('/')[2].chomp("'") -end - # Transforms a coin name to a C++ name def self.format_name(coin) formatted = coin['name'] - #formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') + formatted = formatted.gsub(/\s/, '') formatted end @@ -49,7 +44,7 @@ end def self.insert_coin_type(coin) target_file = "include/TrustWalletCore/TWCoinType.h" - target_line = " TWCoinType#{coin['name']} = #{coin_type(coin['derivationPath'])},\n" + target_line = " TWCoinType#{format_name(coin)} = #{coin['coinId']},\n" if insert_target_line(target_file, target_line, "};\n") insert_blockchain_type(coin) end @@ -64,9 +59,9 @@ end def insert_coin_entry(coin) target_file = "src/Coin.cpp" - target_line = "#include \"#{coin['name']}/Entry.h\"\n" + 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 = " new #{coin['name']}::Entry(),\n" + target_line = " new #{format_name(coin)}::Entry(),\n" insert_target_line(target_file, target_line, " }; // end_of_coin_entries_marker_do_not_modify\n") end diff --git a/codegen/lib/coin_test_gen.rb b/codegen/lib/coin_test_gen.rb index f8bebd779c0..f03a2869d77 100755 --- a/codegen/lib/coin_test_gen.rb +++ b/codegen/lib/coin_test_gen.rb @@ -34,10 +34,6 @@ def display_name(coin) name end - def self.coin_type(path) - path.split('/')[2].chomp("'") - end - # Explorer urls def explorer_tx_url(c) path = c['explorer']['url'].to_s + c['explorer']['txPath'].to_s diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index 4cbd4561d37..e53010ed12d 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.erb @@ -11,18 +11,21 @@ #include #include +#include +#include using namespace TW; /// Map with info about known coins static std::map coins; +static std::mutex coinsMutex; static const CoinInfo defaultsForMissing = { "?", "?", TWBlockchainBitcoin, TWPurposeBIP44, - TWCurveED25519, + TWCurveNone, TWHDVersionNone, TWHDVersionNone, DerivationPath(), @@ -37,19 +40,29 @@ static const CoinInfo defaultsForMissing = { 2, "", "", + 0, }; void fillMap(); // forward /// Get coin from map, if missing returns defaults (not to have contains-check in each accessor method) const CoinInfo& getCoinInfo(TWCoinType coin) { - if (coins.size() == 0) { fillMap(); } - if (coins.find(coin) == coins.end()) { return defaultsForMissing; } + if (coins.size() == 0) { + fillMap(); + } + assert(coins.size() > 0); + if (coins.find(coin) == coins.end()) { + return defaultsForMissing; + } return coins.at(coin); } /// Fill the map. Simple static initializer did not work out due to nondeterministic static initialization order void fillMap() { + std::lock_guard guard(coinsMutex); + if (coins.size() > 0) { + return; + } coins = std::map { <% coins.each do |coin| -%> { @@ -73,6 +86,7 @@ void fillMap() { <%= coin['decimals'] %>, "<%= explorer_tx_url(coin) %>", "<%= explorer_account_url(coin) %>", + <% if coin['slip44'].nil? -%><%= coin['coinId'] %><% else -%><%= coin['slip44'] %><% end -%>, } }, <% end -%> diff --git a/codegen/lib/templates/coins.md.erb b/codegen/lib/templates/coins.md.erb index b7f5bcc8e22..cfb218f3849 100644 --- a/codegen/lib/templates/coins.md.erb +++ b/codegen/lib/templates/coins.md.erb @@ -4,6 +4,6 @@ This list is generated from [./coins.json](../coins.json) | Index | Name | Symbol | Logo | URL | | ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -<% coins_md.each do |coin| -%> -| <%= coin_type(coin['derivationPath']).ljust(7, " ") %> | <%= coin['name'].ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | +<% coins.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/hrp.h.erb b/codegen/lib/templates/hrp.h.erb index 30d399689bd..0974e63cd00 100644 --- a/codegen/lib/templates/hrp.h.erb +++ b/codegen/lib/templates/hrp.h.erb @@ -8,7 +8,8 @@ // #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/codegen/lib/templates/jni/method_forward.erb b/codegen/lib/templates/jni/method_forward.erb index 466ea03e488..e5ceb379935 100644 --- a/codegen/lib/templates/jni/method_forward.erb +++ b/codegen/lib/templates/jni/method_forward.erb @@ -19,7 +19,9 @@ } <% end -%> result = TWDataJByteArray(resultData, env); +<% if method.return_type.is_nullable %> cleanup: +<% end -%> <%= render('jni/parameter_release.erb', { method: method }) -%> <% if !method.static %> <%= render('jni/instance_release.erb', { entity: entity }) %> @@ -36,7 +38,9 @@ cleanup: } <% end -%> result = TWStringJString(resultString, env); +<% if method.return_type.is_nullable %> cleanup: +<% end -%> <%= render('jni/parameter_release.erb', { method: method }) -%> <% if !method.static %> <%= render('jni/instance_release.erb', { entity: entity }) %> diff --git a/coins.json b/coins.json index 5fc87eb88cb..2fddde6b0dd 100644 --- a/coins.json +++ b/coins.json @@ -1,1336 +1,1519 @@ -[{ - "id": "nebulas", - "name": "Nebulas", - "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": "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" }, - { - "id": "ethereum", - "name": "Ethereum", - "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" - } + "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/" }, - { - "id": "bitcoin", - "name": "Bitcoin", - "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" - } + "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/" }, - { - "id": "bitcoincash", - "name": "Bitcoin Cash", - "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" - } + "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/" }, - { - "id": "callisto", - "name": "Callisto", - "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" - } + "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/" }, - { - "id": "dash", - "name": "Dash", - "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" - } + "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/" }, - { - "id": "decred", - "name": "Decred", - "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" - } + "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/" }, - { - "id": "digibyte", - "name": "DigiByte", - "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" - } + "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/" }, - { - "id": "doge", - "name": "Dogecoin", - "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" - } + "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/" }, - { - "id": "classic", - "name": "Ethereum Classic", - "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" - } + "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" }, - { - "id": "gochain", - "name": "GoChain", - "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" - } + "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/" }, - { - "id": "groestlcoin", - "name": "Groestlcoin", - "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" - } + "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/" }, - { - "id": "icon", - "name": "ICON", - "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" - } + "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/" }, - { - "id": "litecoin", - "name": "Litecoin", - "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" - } + "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/" }, - { - "id": "ontology", - "name": "Ontology", - "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" - } + "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", + "coinId": 136, + "symbol": "XZC", + "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.zcoin.io", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "viacoin", - "name": "Viacoin", - "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" - } + "info": { + "url": "https://zcoin.io", + "client": "https://github.com/trezor/blockbook", + "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" }, - { - "id": "poa", - "name": "POA Network", - "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" - } + "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/" }, - { - "id": "thundertoken", - "name": "Thunder Token", - "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" - } + "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/" }, - { - "id": "tomochain", - "name": "TomoChain", - "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" - } + "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/" }, - { - "id": "tron", - "name": "Tron", - "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" - } + "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" }, - { - "id": "vechain", - "name": "VeChain", - "symbol": "VET", - "decimals": 18, - "blockchain": "Vechain", - "derivationPath": "m/44'/818'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://insight.vecha.in", - "txPath": "/#/main/txs/", - "accountPath": "/#/main/accounts/" - }, - "info": { - "url": "https://vechain.org", - "client": "https://github.com/vechain/thor", - "clientPublic": "", - "clientDocs": "https://doc.vechainworld.io/docs" - } + "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/" }, - { - "id": "wanchain", - "name": "Wanchain", - "symbol": "WAN", - "decimals": 18, - "blockchain": "Wanchain", - "derivationPath": "m/44'/5718350'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://www.wanscan.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", - "sampleAccount": "0x69b492d57BB777E97AA7044d0575228434E2e8b1" - }, - "info": { - "url": "https://wanchain.org", - "client": "https://github.com/wanchain/go-wanchain", - "clientPublic": "", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "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/" }, - { - "id": "zcoin", - "name": "Zcoin", - "symbol": "XZC", - "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.zcoin.io", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://zcoin.io", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "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/" }, - { - "id": "zcash", - "name": "Zcash", - "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://sochain.com", - "txPath": "/tx/ZEC/", - "accountPath": "/address/ZEC/" - }, - "info": { - "url": "https://z.cash", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "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/" }, - { - "id": "binance", - "name": "Binance", - "displayName": "BNB", - "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" - } + "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/" }, - { - "id": "ripple", - "name": "XRP", - "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" - } + "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": "/#" }, - { - "id": "tezos", - "name": "Tezos", - "symbol": "XTZ", - "decimals": 6, - "blockchain": "Tezos", - "derivationPath": "m/44'/1729'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://tezos.id", - "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" - } + "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" }, - { - "id": "nimiq", - "name": "Nimiq", - "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" - } + "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/" }, - { - "id": "stellar", - "name": "Stellar", - "symbol": "XLM", - "decimals": 7, - "blockchain": "Stellar", - "derivationPath": "m/44'/148'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://stellarscan.io", - "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" - } + "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/" }, - { - "id": "aion", - "name": "Aion", - "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" - } + "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/" }, - { - "id": "cosmos", - "name": "Cosmos", - "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" - } + "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/" }, - { - "id": "kin", - "name": "Kin", - "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" - } + "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=" }, - { - "id": "nuls", - "name": "NULS", - "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/" - } + "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/" }, - { - "id": "theta", - "name": "Theta", - "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" - } + "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/" }, - { - "id": "qtum", - "name": "Qtum", - "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" - } + "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" }, - { - "id": "eos", - "name": "EOS", - "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" - } + "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/" }, - { - "id": "nano", - "name": "Nano", - "symbol": "NANO", - "decimals": 30, - "blockchain": "Nano", - "derivationPath": "m/44'/165'/0'", - "curve": "ed25519Blake2bNano", - "publicKeyType": "ed25519Blake2b", - "url": "https://nano.org", - "rpcNodeInfo": "https://github.com/nanocurrency/nano-node", - "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/" - } + "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" }, - { - "id": "iotex", - "name": "IoTeX", - "symbol": "IOTX", - "decimals": 18, - "blockchain": "IoTeX", - "hrp": "io", - "derivationPath": "m/44'/304'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "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" - } + "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://filscan.io", + "txPath": "/#/message/detail?cid=", + "accountPath": "/#/address/detail?address=", + "sampleTx": "bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe", + "sampleAccount": "t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i" }, - { - "id": "zilliqa", - "name": "Zilliqa", - "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" - } + "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" }, - { - "id": "zelcash", - "name": "Zelcash", - "symbol": "ZEL", - "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.zel.cash", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://zel.cash", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "https://blockbook.zel.cash", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "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/" }, - { - "id": "ravencoin", - "name": "Ravencoin", - "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" - } + "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/" }, - { - "id": "waves", - "name": "Waves", - "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" - } + "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/" }, - { - "id": "aeternity", - "name": "Aeternity", - "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/" - } + "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" }, - { - "id": "terra", - "name": "Terra", - "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" - } + "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://insight.vecha.in", + "txPath": "/#/main/txs/", + "accountPath": "/#/main/accounts/" }, - { - "id": "monacoin", - "name": "Monacoin", - "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" - } + "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/" }, - { - "id": "fio", - "name": "FIO", - "symbol": "FIO", - "decimals": 9, - "blockchain": "FIO", - "derivationPath": "m/44'/235'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "url": "https://fio.foundation", - "rpcNodeInfo": "https://fio.foundation", - "explorer": { - "url": "https://fio.foundation", - "txPath": "/?", - "accountPath": "/?" - }, - "info": { - "url": "https://fio.foundation", - "client": "https://fio.foundation", - "clientPublic": "", - "clientDocs": "https://fio.foundation" - } + "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" }, - { - "id": "harmony", - "name": "Harmony", - "symbol": "ONE", - "decimals": 18, - "blockchain": "Harmony", - "hrp": "one", - "derivationPath": "m/44'/1023'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "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" - } + "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/" }, - { - "id": "solana", - "name": "Solana", - "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" - } + "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/" }, - { - "id": "near", - "name": "NEAR", - "symbol": "NEAR", - "decimals": 18, - "blockchain": "NEAR", - "derivationPath": "m/44'/397'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://explorer.nearprotocol.com", - "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" - } + "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/" }, - { - "id": "algorand", - "name": "Algorand", - "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" - } + "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": "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/" }, - { - "id": "ton", - "name": "TON", - "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/" - } + "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://tezos.id", + "txPath": "/", + "accountPath": "/" }, - { - "id": "kusama", - "name": "Kusama", - "symbol": "KSM", - "decimals": 12, - "blockchain": "Polkadot", - "derivationPath": "m/44'/434'/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" - } + "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" }, - { - "id": "polkadot", - "name": "Polkadot", - "symbol": "DOT", - "decimals": 15, - "blockchain": "Polkadot", - "derivationPath": "m/44'/354'/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" - } + "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" + } + }, + { + "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", + "coinId": 19167, + "symbol": "ZEL", + "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.zel.cash", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://zel.cash", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "https://blockbook.zel.cash", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "wanchain", + "name": "Wanchain", + "coinId": 5718350, + "symbol": "WAN", + "decimals": 18, + "blockchain": "Wanchain", + "derivationPath": "m/44'/5718350'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://www.wanscan.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", + "sampleAccount": "0x69b492d57BB777E97AA7044d0575228434E2e8b1" }, - { - "id": "kava", - "name": "Kava", - "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" - } + "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/" }, - { - "id": "cardano", - "name": "Cardano", - "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/" - } + "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" }, - { - "id": "neo", - "name": "NEO", - "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" - } + "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" + } + }, + { + "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" }, - { - "id": "filecoin", - "name": "Filecoin", - "symbol": "FIL", - "decimals": 18, - "blockchain": "Filecoin", - "derivationPath": "m/44'/461'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://filscan.io", - "txPath": "/#/message/detail?cid=", - "accountPath": "/#/address/detail?address=", - "sampleTx": "bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe", - "sampleAccount": "t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i" - }, - "info": { - "url": "https://filecoin.io/", - "client": "https://github.com/filecoin-project/lotus", - "clientPublic": "", - "clientDocs": "https://docs.lotu.sh" - } + "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" } + } ] diff --git a/docker/wallet-core/Dockerfile b/docker/wallet-core/Dockerfile deleted file mode 100644 index 84112998ae2..00000000000 --- a/docker/wallet-core/Dockerfile +++ /dev/null @@ -1,85 +0,0 @@ -FROM ubuntu:18.04 - -ARG CLANG_VERSION=7.0.1 -ARG CMAKE_VERSION=3.13.4 -ARG PROTOBUF_VERSION=3.9.0 - -# Install some basics -RUN apt-get update \ - && apt-get install -y \ - curl \ - git \ - nano \ - unzip \ - xz-utils \ - && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Install required packages for dev -RUN apt-get update \ - && apt-get install -y \ - autoconf \ - build-essential \ - libcurl4-openssl-dev \ - libicu-dev \ - libreadline-dev \ - libssl-dev \ - libtool \ - ninja-build \ - pkg-config \ - ruby-full \ - && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Install clang -ENV CLANG_VERSION=$CLANG_VERSION -RUN curl -fSsL http://releases.llvm.org/$CLANG_VERSION/clang+llvm-$CLANG_VERSION-x86_64-linux-gnu-ubuntu-18.04.tar.xz -o clang.tar.xz \ - && tar -xJf clang.tar.xz --directory /usr --strip-components=1 \ - && rm -rf clang.tar.xz - -# Install Boost -RUN curl -fSsL https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz -o boost.tar.gz \ - && tar xzf boost.tar.gz \ - && mv boost_1_66_0/boost /usr/include \ - && rm -rf boost* - -# Install CMake, binaries from GitHub -ENV CMAKE_VERSION=$CMAKE_VERSION -RUN cd /usr/local/src \ - && curl -fSsOL https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz \ - && ls -l cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz \ - && tar xf cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz \ - && cd cmake-$CMAKE_VERSION-Linux-x86_64 \ - && cp -r bin /usr/ \ - && cp -r share /usr/ \ - && cp -r doc /usr/share/ \ - && cp -r man /usr/share/ \ - && cd .. \ - && rm -rf cmake* -RUN cmake --version - -# Clone repo -ENV CC=/usr/bin/clang -ENV CXX=/usr/bin/clang++ -RUN git clone https://github.com/TrustWallet/wallet-core.git - -# Prepare dependencies -RUN cd /wallet-core \ - && export PREFIX=/usr/local \ - && tools/install-dependencies - -# Clean Up -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Version checks -RUN ruby --version \ - && clang --version \ - && cmake --version \ - && protoc --version - -# Build library: generate, cmake, and make -# rbenv trick is needed as Ruby is installed to non-standard location -RUN cd /wallet-core \ - && ./tools/generate-files \ - && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ - && make -Cbuild - -CMD ["/bin/bash"] diff --git a/docs/coins.md b/docs/coins.md index bd8b83ac4f5..5e3b3c4070b 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -22,12 +22,13 @@ This list is generated from [./coins.json](../coins.json) | 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 | | | +| 235 | FIO | FIO | | | | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | | 304 | IoTeX | IOTX | | | @@ -41,8 +42,10 @@ This list is generated from [./coins.json](../coins.json) | 457 | Aeternity | AE | | | | 459 | Kava | KAVA | | | | 461 | Filecoin | FIL | | | +| 494 | BandChain | BAND | | | | 500 | Theta | THETA | | | | 501 | Solana | SOL | | | +| 508 | Elrond | eGLD | | | | 714 | Binance | BNB | | | | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | @@ -61,3 +64,4 @@ This list is generated from [./coins.json](../coins.json) | 19167 | Zelcash | ZEL | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | +| 20000714 | Smart Chain | BNB | | | diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h index cb0196e8d17..bdaae85cbfb 100644 --- a/include/TrustWalletCore/TWAES.h +++ b/include/TrustWalletCore/TWAES.h @@ -6,8 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" +#include +#include +#include TW_EXTERN_C_BEGIN @@ -22,7 +23,7 @@ struct TWAES { /// \param data data to encrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCBCEncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +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. /// @@ -30,7 +31,7 @@ TWData *_Nullable TWAESCBCEncrypt(TWData *_Nonnull key, TWData *_Nonnull data, T /// \param data data to decrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCBCDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +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. /// @@ -38,7 +39,7 @@ TWData *_Nullable TWAESCBCDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, T /// \param data data to encrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); /// Decrypts a block of data using AES in Counter (CTR) mode. /// @@ -46,6 +47,6 @@ TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, T /// \param data data to decrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCTRDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWAESPaddingMode.h b/include/TrustWalletCore/TWAESPaddingMode.h new file mode 100644 index 00000000000..dc5647376ce --- /dev/null +++ b/include/TrustWalletCore/TWAESPaddingMode.h @@ -0,0 +1,19 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +TW_EXTERN_C_BEGIN + +TW_EXPORT_ENUM(uint32_t) +enum TWAESPaddingMode { + TWAESPaddingModeZero = 0, // padding value is zero + TWAESPaddingModePKCS7 = 1, // padding value is the number of padding bytes; for even size add an extra block +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWAccount.h b/include/TrustWalletCore/TWAccount.h index c48d6451ba9..f2056f2b44c 100644 --- a/include/TrustWalletCore/TWAccount.h +++ b/include/TrustWalletCore/TWAccount.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWString.h" -#include "TWCoinType.h" +#include +#include +#include TW_EXTERN_C_BEGIN @@ -17,7 +17,7 @@ TW_EXPORT_CLASS struct TWAccount; TW_EXPORT_STATIC_METHOD -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey); +struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey); TW_EXPORT_METHOD void TWAccountDelete(struct TWAccount *_Nonnull account); diff --git a/include/TrustWalletCore/TWAnyAddress.h b/include/TrustWalletCore/TWAnyAddress.h index 3771b80e762..ace62af5ded 100644 --- a/include/TrustWalletCore/TWAnyAddress.h +++ b/include/TrustWalletCore/TWAnyAddress.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWAnySigner.h b/include/TrustWalletCore/TWAnySigner.h index f4246843baf..85514447f7f 100644 --- a/include/TrustWalletCore/TWAnySigner.h +++ b/include/TrustWalletCore/TWAnySigner.h @@ -5,10 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN @@ -23,6 +23,14 @@ extern TWString *_Nonnull TWAnySignerSignJSON(TWString *_Nonnull json, TWData *_ extern bool TWAnySignerSupportsJSON(enum TWCoinType coin); +/// Encodes serialized SigningInput data to raw platform/coin specific bytes +/// Example: EthereumSigningInput will be encoded as raw RLP bytes which can be sent over JSONRPC (eth_sendRawTransaction) +extern TWData *_Nonnull TWAnySignerEncode(TWData *_Nonnull input, enum TWCoinType coin); + +/// Decodes raw platform/coin specific bytes to representable json data +/// Example: Ethereum RLP bytes will be decoded into same json returned from JSONRPC (eth_getTransactionByHash) +extern TWData *_Nonnull TWAnySignerDecode(TWData *_Nonnull input, enum TWCoinType coin); + /// Plan a transaction (for UTXO chains). extern TWData *_Nonnull TWAnySignerPlan(TWData *_Nonnull input, enum TWCoinType coin); diff --git a/include/TrustWalletCore/TWBase58.h b/include/TrustWalletCore/TWBase58.h index 5c6ca64ddac..392c73bf53b 100644 --- a/include/TrustWalletCore/TWBase58.h +++ b/include/TrustWalletCore/TWBase58.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBitcoinAddress.h b/include/TrustWalletCore/TWBitcoinAddress.h index b3af8e29880..0bca579c033 100644 --- a/include/TrustWalletCore/TWBitcoinAddress.h +++ b/include/TrustWalletCore/TWBitcoinAddress.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index a50499fa4db..3cb75a728bf 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -6,10 +6,11 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWPublicKey.h" -#include "TWCoinType.h" +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN @@ -49,6 +50,10 @@ bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript *_Nonnull scr TW_EXPORT_PROPERTY bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *_Nonnull script); +/// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. +TW_EXPORT_PROPERTY +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *_Nonnull script); + /// Determines whether this is a witness programm script. TW_EXPORT_PROPERTY bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *_Nonnull script); @@ -106,8 +111,12 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWDa TW_EXPORT_STATIC_METHOD struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *_Nonnull scriptHash); -/// Builds a pay-to-public-key-hash (P2PKH) script appropriate for the given address. +/// Builds a appropriate lock script for the given address. +TW_EXPORT_STATIC_METHOD +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin); + +// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildForAddress(TWString *_Nonnull address, enum TWCoinType coin); +uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBitcoinSigHashType.h b/include/TrustWalletCore/TWBitcoinSigHashType.h index 1ed7d7c0e4c..74fa785d5f7 100644 --- a/include/TrustWalletCore/TWBitcoinSigHashType.h +++ b/include/TrustWalletCore/TWBitcoinSigHashType.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN @@ -16,6 +16,7 @@ enum TWBitcoinSigHashType { TWBitcoinSigHashTypeNone = 0x02, TWBitcoinSigHashTypeSingle = 0x03, TWBitcoinSigHashTypeFork = 0x40, + TWBitcoinSigHashTypeForkBTG = 0x4f40, TWBitcoinSigHashTypeAnyoneCanPay = 0x80 }; diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 8b1b82d6028..1710fe5be40 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN @@ -45,6 +45,7 @@ enum TWBlockchain { TWBlockchainCardano = 30, TWBlockchainNEO = 31, TWBlockchainFilecoin = 32, + TWBlockchainElrondNetwork = 33, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 0c2a152cec0..3c2237f194f 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -6,14 +6,14 @@ #pragma once -#include "TWBase.h" -#include "TWBlockchain.h" -#include "TWCurve.h" -#include "TWHDVersion.h" -#include "TWHRP.h" -#include "TWPrivateKey.h" -#include "TWPurpose.h" -#include "TWString.h" +#include +#include +#include +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN @@ -27,6 +27,7 @@ enum TWCoinType { TWCoinTypeBinance = 714, TWCoinTypeBitcoin = 0, TWCoinTypeBitcoinCash = 145, + TWCoinTypeBitcoinGold = 156, TWCoinTypeCallisto = 820, TWCoinTypeCardano = 1815, // Note: Cardano Shelley testnet uses purpose 1852 (not 44) 1852/1815 TWCoinTypeCosmos = 118, @@ -79,6 +80,10 @@ enum TWCoinType { TWCoinTypeKusama = 434, TWCoinTypePolkadot = 354, TWCoinTypeFilecoin = 461, + TWCoinTypeElrond = 508, + TWCoinTypeBandChain = 494, + TWCoinTypeSmartChainLegacy = 10000714, + TWCoinTypeSmartChain = 20000714, }; /// Returns the blockchain for a coin type. @@ -135,4 +140,8 @@ uint8_t TWCoinTypeP2shPrefix(enum TWCoinType coin); TW_EXPORT_PROPERTY uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin); +/// Static prefix for this coin type +TW_EXPORT_PROPERTY +uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin); + TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinTypeConfiguration.h b/include/TrustWalletCore/TWCoinTypeConfiguration.h index 34d93a68835..884e7b59fd4 100644 --- a/include/TrustWalletCore/TWCoinTypeConfiguration.h +++ b/include/TrustWalletCore/TWCoinTypeConfiguration.h @@ -5,9 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWString.h" + +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWCurve.h b/include/TrustWalletCore/TWCurve.h index 89590c62694..39ce70ea43b 100644 --- a/include/TrustWalletCore/TWCurve.h +++ b/include/TrustWalletCore/TWCurve.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN @@ -18,6 +19,7 @@ enum TWCurve { TWCurveCurve25519 /* "curve25519" */, TWCurveNIST256p1 /* "nist256p1" */, TWCurveED25519Extended /* "ed25519-cardano-seed" */, + TWCurveNone }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWData.h b/include/TrustWalletCore/TWData.h index 25ff75a14fa..16d638bd0d4 100644 --- a/include/TrustWalletCore/TWData.h +++ b/include/TrustWalletCore/TWData.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN @@ -27,7 +27,7 @@ TWData *_Nonnull TWDataCreateWithSize(size_t size); /// Creates a block of data by copying another block of data. TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data); -/// Creates a block of data from a hexadecimal string. +/// 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); /// Returns the size in bytes. diff --git a/include/TrustWalletCore/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h new file mode 100644 index 00000000000..47cd9d5370d --- /dev/null +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +// Wrapper class for Ethereum ABI encoding & decoding. + +TW_EXTERN_C_BEGIN + +struct TWEthereumAbiFunction; + +TW_EXPORT_CLASS +struct TWEthereumAbi; + +/// Encode function to Eth ABI binary +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull fn); + +/// Decode function output from Eth ABI binary, fill output parameters +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 +TW_EXPORT_STATIC_METHOD +TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _Nonnull abi); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiEncoder.h b/include/TrustWalletCore/TWEthereumAbiEncoder.h deleted file mode 100644 index c59ee109813..00000000000 --- a/include/TrustWalletCore/TWEthereumAbiEncoder.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 "TWBase.h" -#include "TWString.h" -#include "TWData.h" - -// Wrapper class for Ethereum ABI encoding & decoding. Also builder for Function objects. -// See also TWEthereumAbiFunction. - -TW_EXTERN_C_BEGIN - -struct TWEthereumAbiFunction; - -TW_EXPORT_CLASS -struct TWEthereumAbiEncoder; - -/// Creates a function object, with the given name and empty parameter list. It must be deleted at the end. -/// Note: Create name is reserved for own-class creation in the codegen toolchain -TW_EXPORT_STATIC_METHOD -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiEncoderBuildFunction(TWString *_Nonnull name); - -/// Deletes a function object created with a 'TWEthereumAbiEncoderBuildFunction' method. -/// Note: func is a reserved keyword in codegenerated swift. -TW_EXPORT_STATIC_METHOD -void TWEthereumAbiEncoderDeleteFunction(struct TWEthereumAbiFunction *_Nonnull func_in); - -/// Encode function to Eth ABI binary -TW_EXPORT_STATIC_METHOD -TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnull func_in); - -/// Decode function output from Eth ABI binary, fill output parameters -TW_EXPORT_STATIC_METHOD -bool TWEthereumAbiEncoderDecodeOutput(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull encoded); - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiFunction.h b/include/TrustWalletCore/TWEthereumAbiFunction.h index 2d8b43f1f4d..0f9466dbcb6 100644 --- a/include/TrustWalletCore/TWEthereumAbiFunction.h +++ b/include/TrustWalletCore/TWEthereumAbiFunction.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWString.h" -#include "TWData.h" +#include +#include +#include TW_EXTERN_C_BEGIN @@ -17,172 +17,172 @@ struct TWEthereumAbiFunction; /// Creates a function object, with the given name and empty parameter list. It must be deleted at the end. TW_EXPORT_STATIC_METHOD -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name); +struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name); /// Deletes a function object created with a 'TWEthereumAbiFunctionCreateWithString' method. TW_EXPORT_METHOD -void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull func_in); +void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull fn); /// Return the function type signature, of the form "baz(int32,uint256)" TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull func_in); +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 needd. /// Returns the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, uint8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, uint8_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, uint16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, uint16_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, uint32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, uint32_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, uint64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, uint64_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int8_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int16_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int32_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int64_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, bool val, bool isOutput); +int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull fn, bool val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull func_in, TWString *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull fn, TWString *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, size_t count_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, size_t size, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull func_in, bool isOutput); +int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull fn, bool isOutput); /// Methods for accessing the value of an output or input parameter, of different types. TW_EXPORT_METHOD -uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, 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" TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint8_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint8_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint16_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint16_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint32_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint32_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint64_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint64_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int8_t val); +int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int8_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int16_t val); +int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int16_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int32_t val); +int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int32_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int64_t val); +int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int64_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, bool val); +int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, bool val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWString *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWString *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, size_t count_in, 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 new file mode 100644 index 00000000000..17cab074c09 --- /dev/null +++ b/include/TrustWalletCore/TWEthereumAbiValue.h @@ -0,0 +1,59 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +TW_EXTERN_C_BEGIN + +TW_EXPORT_CLASS +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. + +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value); + +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value); + +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value); + +/// Encode an int256. Input value is represented as a 32-byte value +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value); + +/// Encode an uint256. Input value is represented as a 32-byte binary value +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value); + +/// Encode the 20 bytes of an address +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value); + +/// Encode a string by encoding its hash +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. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value); + +/// Encode a dynamic number of bytes by encoding its hash +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value); + + +/// Decodes input data (bytes longer than 32 will be truncated) as uint256 +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumAbiValueDecodeUInt256(TWData* _Nonnull input); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiValueEncoder.h b/include/TrustWalletCore/TWEthereumAbiValueEncoder.h deleted file mode 100644 index 0fcc93e91e8..00000000000 --- a/include/TrustWalletCore/TWEthereumAbiValueEncoder.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" - -TW_EXTERN_C_BEGIN - -TW_EXPORT_CLASS -struct TWEthereumAbiValueEncoder; - -/// Returned data must be deleted (hint: use WRAPD() macro). -/// Encode a type according to EIP712, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise. -/// See: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md - -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBool(bool value); - -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt32(int32_t value); - -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt32(uint32_t value); - -/// Encode an int256. Input value is represented as a 32-byte value -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt256(TWData* _Nonnull value); - -/// Encode an uint256. Input value is represented as a 32-byte binary value -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt256(TWData* _Nonnull value); - -/// Encode the 20 bytes of an address -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeAddress(TWData* _Nonnull value); - -/// Encode a string by encoding its hash -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeString(TWString* _Nonnull value); - -/// Encode a number of bytes, up to 32 bytes, padded on the right. Longer arrays are truncated. -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytes(TWData* _Nonnull value); - -/// Encode a dynamic number of bytes by encoding its hash -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytesDyn(TWData* _Nonnull value); - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumChainID.h b/include/TrustWalletCore/TWEthereumChainID.h index 67bebf95499..cc6a8dad2d1 100644 --- a/include/TrustWalletCore/TWEthereumChainID.h +++ b/include/TrustWalletCore/TWEthereumChainID.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN @@ -20,6 +21,7 @@ enum TWEthereumChainID { TWEthereumChainIDVeChain = 74, TWEthereumChainIDThunderToken = 108, TWEthereumChainIDTomoChain = 88, + TWEthereumChainIDBinanceSmartChain = 56, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWFIOAccount.h b/include/TrustWalletCore/TWFIOAccount.h new file mode 100644 index 00000000000..a3fb9257503 --- /dev/null +++ b/include/TrustWalletCore/TWFIOAccount.h @@ -0,0 +1,28 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +TW_EXTERN_C_BEGIN + +/// Represents a FIO Account name +TW_EXPORT_CLASS +struct TWFIOAccount; + +TW_EXPORT_STATIC_METHOD +struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string); + +TW_EXPORT_METHOD +void TWFIOAccountDelete(struct TWFIOAccount *_Nonnull account); + +/// Returns the short account string representation. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWFIOAccountDescription(struct TWFIOAccount *_Nonnull account); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWGroestlcoinAddress.h b/include/TrustWalletCore/TWGroestlcoinAddress.h index a32b05dc102..b116f157318 100644 --- a/include/TrustWalletCore/TWGroestlcoinAddress.h +++ b/include/TrustWalletCore/TWGroestlcoinAddress.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWHDVersion.h b/include/TrustWalletCore/TWHDVersion.h index 1b47fe14449..383d0636471 100644 --- a/include/TrustWalletCore/TWHDVersion.h +++ b/include/TrustWalletCore/TWHDVersion.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index bdf27f3fba7..cd2c291a6df 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -6,15 +6,15 @@ #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWCurve.h" -#include "TWData.h" -#include "TWHDVersion.h" -#include "TWPrivateKey.h" -#include "TWPublicKey.h" -#include "TWPurpose.h" -#include "TWString.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN @@ -63,7 +63,7 @@ TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *_Nonnull walle /// Generates the private key for the specified derivation path. TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, 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. /// @@ -81,6 +81,6 @@ TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *_Nonnull wa /// Computes the public key from an exteded public key representation. TW_EXPORT_STATIC_METHOD -struct TWPublicKey *_Nullable TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, 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..9f7e3fd1fe9 100644 --- a/include/TrustWalletCore/TWHash.h +++ b/include/TrustWalletCore/TWHash.h @@ -6,8 +6,8 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index 38d41b5ae04..f89844fb034 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWCurve.h" -#include "TWData.h" -#include "TWPublicKey.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPublicKey.h b/include/TrustWalletCore/TWPublicKey.h index 220672d2a1e..29b64ffa206 100644 --- a/include/TrustWalletCore/TWPublicKey.h +++ b/include/TrustWalletCore/TWPublicKey.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWPublicKeyType.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPublicKeyType.h b/include/TrustWalletCore/TWPublicKeyType.h index 0a3bc5c38ce..174a6c3c8a3 100644 --- a/include/TrustWalletCore/TWPublicKeyType.h +++ b/include/TrustWalletCore/TWPublicKeyType.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPurpose.h b/include/TrustWalletCore/TWPurpose.h index 3227efd89e3..c7dc5ccdb83 100644 --- a/include/TrustWalletCore/TWPurpose.h +++ b/include/TrustWalletCore/TWPurpose.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWRippleXAddress.h b/include/TrustWalletCore/TWRippleXAddress.h index 975d7476250..d475c4b024b 100644 --- a/include/TrustWalletCore/TWRippleXAddress.h +++ b/include/TrustWalletCore/TWRippleXAddress.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWHRP.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWSS58AddressType.h b/include/TrustWalletCore/TWSS58AddressType.h index b76f3c212fe..2ad30d717c1 100644 --- a/include/TrustWalletCore/TWSS58AddressType.h +++ b/include/TrustWalletCore/TWSS58AddressType.h @@ -6,7 +6,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWSegwitAddress.h b/include/TrustWalletCore/TWSegwitAddress.h index e77d2f20996..4133c0a08b0 100644 --- a/include/TrustWalletCore/TWSegwitAddress.h +++ b/include/TrustWalletCore/TWSegwitAddress.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWHRP.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStellarMemoType.h b/include/TrustWalletCore/TWStellarMemoType.h index 45f9c629206..f5abda479ca 100644 --- a/include/TrustWalletCore/TWStellarMemoType.h +++ b/include/TrustWalletCore/TWStellarMemoType.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStellarPassphrase.h b/include/TrustWalletCore/TWStellarPassphrase.h index aca15cdeff3..f6a8ea33214 100644 --- a/include/TrustWalletCore/TWStellarPassphrase.h +++ b/include/TrustWalletCore/TWStellarPassphrase.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStellarVersionByte.h b/include/TrustWalletCore/TWStellarVersionByte.h index b694c80b6bb..defd6e105b5 100644 --- a/include/TrustWalletCore/TWStellarVersionByte.h +++ b/include/TrustWalletCore/TWStellarVersionByte.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 784339fd6e0..ec72fef83d9 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -6,12 +6,12 @@ #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWHDWallet.h" -#include "TWPrivateKey.h" -#include "TWString.h" +#include +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN @@ -21,88 +21,88 @@ struct TWStoredKey; /// Loads a key from a file. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyLoad(TWString *_Nonnull path); +struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path); /// Imports a private key. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyImportPrivateKey(TWData *_Nonnull privateKey, TWString *_Nonnull name, TWString *_Nonnull password, enum TWCoinType coin); +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); /// Imports an HD wallet. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyImportHDWallet(TWString *_Nonnull mnemonic, TWString *_Nonnull name, TWString *_Nonnull password, enum TWCoinType coin); +struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); /// Imports a key from JSON. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyImportJSON(TWData *_Nonnull json); +struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json); /// Creates a new key. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nonnull TWStoredKeyCreate(TWString *_Nonnull name, TWString *_Nonnull password); +struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); TW_EXPORT_METHOD -void TWStoredKeyDelete(struct TWStoredKey *_Nonnull key); +void TWStoredKeyDelete(struct TWStoredKey* _Nonnull key); /// Stored key uniqie identifier. TW_EXPORT_PROPERTY -TWString *_Nullable TWStoredKeyIdentifier(struct TWStoredKey *_Nonnull key); +TWString* _Nullable TWStoredKeyIdentifier(struct TWStoredKey* _Nonnull key); /// Stored key namer. TW_EXPORT_PROPERTY -TWString *_Nonnull TWStoredKeyName(struct TWStoredKey *_Nonnull key); +TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key); /// Whether this key is a mnemonic phrase for a HD wallet. TW_EXPORT_PROPERTY -bool TWStoredKeyIsMnemonic(struct TWStoredKey *_Nonnull key); +bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key); /// The number of accounts. TW_EXPORT_PROPERTY -size_t TWStoredKeyAccountCount(struct TWStoredKey *_Nonnull key); +size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key); /// Returns the account at a given index. TW_EXPORT_METHOD -struct TWAccount *_Nullable TWStoredKeyAccount(struct TWStoredKey *_Nonnull key, size_t index); +struct TWAccount* _Nullable TWStoredKeyAccount(struct TWStoredKey* _Nonnull key, size_t index); /// Returns the account for a specific coin, creating it if necessary. TW_EXPORT_METHOD -struct TWAccount *_Nullable TWStoredKeyAccountForCoin(struct TWStoredKey *_Nonnull key, enum TWCoinType coin, struct TWHDWallet *_Nullable wallet); +struct TWAccount* _Nullable TWStoredKeyAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, struct TWHDWallet* _Nullable wallet); /// Remove the account for a specific coin TW_EXPORT_METHOD -void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey *_Nonnull key, enum TWCoinType coin); +void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin); /// Adds a new account. TW_EXPORT_METHOD -void TWStoredKeyAddAccount(struct TWStoredKey *_Nonnull key, TWString *_Nonnull address, TWString *_Nonnull derivationPath, TWString *_Nonnull extetndedPublicKey); +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey); /// Saves the key to a file. TW_EXPORT_METHOD -bool TWStoredKeyStore(struct TWStoredKey *_Nonnull key, TWString *_Nonnull path); +bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path); /// Decrypts the private key. TW_EXPORT_METHOD -TWData *_Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Decrypts the mnemonic phrase. TW_EXPORT_METHOD -TWString *_Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Returns the private key for a specific coin. TW_EXPORT_METHOD -struct TWPrivateKey *_Nullable TWStoredKeyPrivateKey(struct TWStoredKey *_Nonnull key, enum TWCoinType coin, TWString *_Nonnull password); +struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password); /// Dercrypts and returns the HD Wallet for mnemonic phrase keys. TW_EXPORT_METHOD -struct TWHDWallet *_Nullable TWStoredKeyWallet(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Exports the key as JSON TW_EXPORT_METHOD -TWData *_Nullable TWStoredKeyExportJSON(struct TWStoredKey *_Nonnull key); +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. TW_EXPORT_METHOD -bool TWStoredKeyFixAddresses(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWString.h b/include/TrustWalletCore/TWString.h index 2495be45941..dea9441d488 100644 --- a/include/TrustWalletCore/TWString.h +++ b/include/TrustWalletCore/TWString.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/jni/cpp/AnySigner.c b/jni/cpp/AnySigner.c index d9ac054adc4..6a454d2c921 100644 --- a/jni/cpp/AnySigner.c +++ b/jni/cpp/AnySigner.c @@ -19,6 +19,22 @@ jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclas return resultData; } +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeEncode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerEncode(inputData, coin); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} + +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeDecode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerDecode(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); } diff --git a/jni/cpp/AnySigner.h b/jni/cpp/AnySigner.h index b51323a6554..afddb70872c 100644 --- a/jni/cpp/AnySigner.h +++ b/jni/cpp/AnySigner.h @@ -15,6 +15,12 @@ TW_EXTERN_C_BEGIN JNIEXPORT jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeEncode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); + +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeDecode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); + JNIEXPORT jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin); diff --git a/jni/cpp/Random.cpp b/jni/cpp/Random.cpp index d0d9616e164..de97baff225 100644 --- a/jni/cpp/Random.cpp +++ b/jni/cpp/Random.cpp @@ -35,7 +35,7 @@ void random_buffer(uint8_t *buf, size_t len) { jobject random = env->NewObject(secureRandomClass, constructor); //byte array[] = new byte[len]; - jbyteArray array = env->NewByteArray(len); + jbyteArray array = env->NewByteArray(static_cast(len)); //random.nextBytes(bytes); jmethodID nextBytes = env->GetMethodID(secureRandomClass, "nextBytes", "([B)V"); diff --git a/jni/cpp/TWJNIData.cpp b/jni/cpp/TWJNIData.cpp index 9d468ec4231..c0577f31442 100644 --- a/jni/cpp/TWJNIData.cpp +++ b/jni/cpp/TWJNIData.cpp @@ -10,8 +10,9 @@ #include "TWJNIData.h" jbyteArray TWDataJByteArray(TWData *_Nonnull data, JNIEnv *env) { - jbyteArray array = env->NewByteArray(TWDataSize(data)); - env->SetByteArrayRegion(array, 0, TWDataSize(data), (jbyte *) TWDataBytes(data)); + jsize dataSize = static_cast(TWDataSize(data)); + jbyteArray array = env->NewByteArray(dataSize); + env->SetByteArrayRegion(array, 0, dataSize, (jbyte *) TWDataBytes(data)); TWDataDelete(data); return array; } diff --git a/jni/java/wallet/core/java/AnySigner.java b/jni/java/wallet/core/java/AnySigner.java index 47f9b4fce9a..c3d53105103 100644 --- a/jni/java/wallet/core/java/AnySigner.java +++ b/jni/java/wallet/core/java/AnySigner.java @@ -21,6 +21,19 @@ public static T sign(Message input, CoinType coin, Parser } public static native byte[] nativeSign(byte[] data, int coin); + public static byte[] encode(Message input, CoinType coin) throws Exception { + byte[] data = input.toByteArray(); + return nativeEncode(data, coin.value()); + } + + public static native byte[] nativeEncode(byte[] data, int coin); + + public static byte[] decode(byte[] data, CoinType coin) throws Exception { + return nativeDecode(data, coin.value()); + } + + public static native byte[] nativeDecode(byte[] data, int coin); + public static native String signJSON(String json, byte[] key, int coin); public static native boolean supportsJSON(int coin); diff --git a/protobuf-plugin/CMakeLists.txt b/protobuf-plugin/CMakeLists.txt index bc4bcb291f1..7375fc0efda 100644 --- a/protobuf-plugin/CMakeLists.txt +++ b/protobuf-plugin/CMakeLists.txt @@ -13,7 +13,7 @@ endif() include_directories(${PREFIX}/include) link_directories(${PREFIX}/lib) -find_library(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) +find_package(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/samples/android/.gitignore b/samples/android/.gitignore index 2f56990dfa4..88ba88714fc 100644 --- a/samples/android/.gitignore +++ b/samples/android/.gitignore @@ -52,5 +52,5 @@ gen-external-apklibs tn/trustnative.aar .idea/misc.xml -gradle.properties +#gradle.properties /app/src/main/play/listings/* \ No newline at end of file diff --git a/samples/android/app/build.gradle b/samples/android/app/build.gradle index 53d7c729e47..6003ffe28e5 100644 --- a/samples/android/app/build.gradle +++ b/samples/android/app/build.gradle @@ -24,7 +24,7 @@ android { } project.ext { - walletcore_version = "2.0.3" + walletcore_version = "2.1.0" } dependencies { diff --git a/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt b/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt index fcfd305e051..eb2b4dc0c9b 100644 --- a/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt +++ b/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt @@ -64,7 +64,7 @@ class MainActivity : AppCompatActivity() { val secretPrivateKeyBtc = wallet.getKeyForCoin(coinBtc) val toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" val changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" - val script = BitcoinScript.buildForAddress(addressBtc, coinBtc).data() + val script = BitcoinScript.lockScriptForAddress(addressBtc, coinBtc).data() val outPoint = Bitcoin.OutPoint.newBuilder().apply { this.hash = ByteString.copyFrom(utxoTxId) @@ -77,21 +77,24 @@ class MainActivity : AppCompatActivity() { }.build() val input = Bitcoin.SigningInput.newBuilder().apply { this.amount = 600 - this.hashType = BitcoinSigHashType.ALL.value().or(BitcoinSigHashType.FORK.value()) + this.hashType = BitcoinSigHashType.ALL.value() this.toAddress = toAddress this.changeAddress = changeAddress - this.byteFee = 1 + this.byteFee = 2 this.coinType = coinBtc.value() this.addUtxo(utxo) this.addPrivateKey(ByteString.copyFrom(secretPrivateKeyBtc.data())) } // Calculate fee (plan a tranaction) - val plan = AnySigner.plan(input.build(), CoinType.BITCOIN, Bitcoin.TransactionPlan.parser()) + val plan = AnySigner.plan(input.build(), coinBtc, Bitcoin.TransactionPlan.parser()) + showLog("Planned fee: ${plan.fee} amount: ${plan.amount} avail_amount: ${plan.availableAmount} change: ${plan.change}") // Set the precomputed plan input.plan = plan - val output = AnySigner.sign(input.build(), CoinType.BITCOIN, Bitcoin.SigningOutput.parser()) + input.amount = plan.amount + + val output = AnySigner.sign(input.build(), coinBtc, Bitcoin.SigningOutput.parser()) assert(output.error.isEmpty()) val signedTransaction = output.encoded?.toByteArray() diff --git a/samples/android/build.gradle b/samples/android/build.gradle index a706027ba80..b908ed10fd6 100644 --- a/samples/android/build.gradle +++ b/samples/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:3.6.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/samples/android/gradle.properties b/samples/android/gradle.properties new file mode 100644 index 00000000000..5bac8ac5046 --- /dev/null +++ b/samples/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/samples/go/README.md b/samples/go/README.md index 43b27458a81..d28a5dd0ad6 100644 --- a/samples/go/README.md +++ b/samples/go/README.md @@ -30,7 +30,7 @@ and [Build Instructions](https://developer.trustwallet.com/wallet-core/building) The librabry is already built in this image (Build instructions [here](building.md)) Note: may not be the most recent version. 2. Install go: `apt-get update && apt-get install golang` -(or download from here [go1.13.3](https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). +(or download from here [go1.14.2](https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). 3. Go to the **samples/go** folder within wallet core repo: ```shell @@ -44,6 +44,7 @@ cd wallet-core/samples/go ```shell ==> calling wallet core from go -<== mnemonic is valid: true +==> mnemonic is valid: true +==> bitcoin... ``` 6. You might want to copy and run `main` outside of the docker container, make sure you have `libc++1` and `libc++abi1` installed in your host Ubuntu. diff --git a/samples/go/go.mod b/samples/go/go.mod new file mode 100644 index 00000000000..9a564387542 --- /dev/null +++ b/samples/go/go.mod @@ -0,0 +1,5 @@ +module tw + +go 1.14 + +require github.com/golang/protobuf v1.4.2 diff --git a/samples/go/go.sum b/samples/go/go.sum new file mode 100644 index 00000000000..0eeb4918550 --- /dev/null +++ b/samples/go/go.sum @@ -0,0 +1,20 @@ +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= diff --git a/samples/go/main.go b/samples/go/main.go index 3f56b5241a9..a74b4c0ae17 100644 --- a/samples/go/main.go +++ b/samples/go/main.go @@ -1,69 +1,87 @@ package main -// #cgo CFLAGS: -I/wallet-core/include -// #cgo LDFLAGS: -L/wallet-core/build -L/wallet-core/build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #cgo CFLAGS: -I../../include +// #cgo LDFLAGS: -L../../build -L../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm // #include -// #include -// #include // #include // #include +// #include +// #include import "C" -import "fmt" -import "unsafe" -import "encoding/hex" +import ( + "encoding/hex" + "fmt" + "tw/protos/bitcoin" + "tw/types" -// C.TWString -> Go string -func TWStringGoString(s unsafe.Pointer) string { - return C.GoString(C.TWStringUTF8Bytes(s)) -} + "github.com/golang/protobuf/proto" +) -// Go string -> C.TWString -func TWStringCreateWithGoString(s string) unsafe.Pointer { - cStr := C.CString(s) - defer C.free(unsafe.Pointer(cStr)) - str := C.TWStringCreateWithUTF8Bytes(cStr) - return str -} +func main() { + fmt.Println("==> calling wallet core from go") + str := types.TWStringCreateWithGoString("confirm bleak useless tail chalk destroy horn step bulb genuine attract split") + emtpy := types.TWStringCreateWithGoString("") + defer C.TWStringDelete(str) + defer C.TWStringDelete(emtpy) -// C.TWData -> Go byte[] -func TWDataGoBytes(d unsafe.Pointer) []byte { - cBytes := C.TWDataBytes(d) - cSize := C.TWDataSize(d) - return C.GoBytes(unsafe.Pointer(cBytes), C.int(cSize)) -} + fmt.Println("==> mnemonic is valid: ", C.TWHDWalletIsValid(str)) -// Go byte[] -> C.TWData -func TWDataCreateWithGoBytes(d []byte) unsafe.Pointer { - cBytes := C.CBytes(d) - defer C.free(unsafe.Pointer(cBytes)) - data := C.TWDataCreateWithBytes((*C.uchar)(cBytes), C.ulong(len(d))) - return data -} + wallet := C.TWHDWalletCreateWithMnemonic(str, emtpy) + defer C.TWHDWalletDelete(wallet) -func main() { - fmt.Println("==> calling wallet core from go") - str := TWStringCreateWithGoString("confirm bleak useless tail chalk destroy horn step bulb genuine attract split") - emtpy := TWStringCreateWithGoString("") - defer C.TWStringDelete(str) - defer C.TWStringDelete(emtpy) + key := C.TWHDWalletGetKeyForCoin(wallet, C.TWCoinTypeBitcoin) + keyData := C.TWPrivateKeyData(key) + defer C.TWDataDelete(keyData) + + fmt.Println("<== bitcoin private key: ", types.TWDataHexString(keyData)) + + pubKey, _ := hex.DecodeString("0288be7586c41a0498c1f931a0aaf08c15811ee2651a5fe0fa213167dcaba59ae8") + pubKeyData := types.TWDataCreateWithGoBytes(pubKey) + defer C.TWDataDelete(pubKeyData) + fmt.Println("==> bitcoin public key is valid: ", C.TWPublicKeyIsValid(pubKeyData, C.TWPublicKeyTypeSECP256k1)) + + address := C.TWHDWalletGetAddressForCoin(wallet, C.TWCoinTypeBitcoin) + defer C.TWStringDelete(address) + fmt.Println("<== bitcoin address: ", types.TWStringGoString(address)) + + script := C.TWBitcoinScriptLockScriptForAddress(address, C.TWCoinTypeBitcoin) + scriptData := C.TWBitcoinScriptData(script) + defer C.TWBitcoinScriptDelete(script) + defer C.TWDataDelete(scriptData) + fmt.Println("<== bitcoin address lock script: ", types.TWDataHexString(scriptData)) - fmt.Println("<== mnemonic is valid: ", C.TWHDWalletIsValid(str)) + utxoHash, _ := hex.DecodeString("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f") - wallet := C.TWHDWalletCreateWithMnemonic(str, emtpy) - defer C.TWHDWalletDelete(wallet) + utxo := bitcoin.UnspentTransaction{ + OutPoint: &bitcoin.OutPoint{ + Hash: utxoHash, + Index: 0, + Sequence: 4294967295, + }, + Amount: 625000000, + Script: types.TWDataGoBytes(scriptData), + } - key := C.TWHDWalletGetKeyForCoin(wallet, C.TWCoinTypeBitcoin) - keyData := C.TWPrivateKeyData(key) - keyHex := hex.EncodeToString(TWDataGoBytes(keyData)) - fmt.Println("<== bitcoin private key: ", keyHex) + input := bitcoin.SigningInput{ + HashType: 1, // TWBitcoinSigHashTypeAll + Amount: 1000000, + ByteFee: 1, + ToAddress: "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx", + ChangeAddress: "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU", + PrivateKey: [][]byte{types.TWDataGoBytes(keyData)}, + Utxo: []*bitcoin.UnspentTransaction{&utxo}, + CoinType: 0, // TWCoinTypeBitcoin + } - pubKey, _ := hex.DecodeString("0288be7586c41a0498c1f931a0aaf08c15811ee2651a5fe0fa213167dcaba59ae8") - pubKeyData := TWDataCreateWithGoBytes(pubKey) - defer C.TWDataDelete(pubKeyData) + inputBytes, _ := proto.Marshal(&input) + inputData := types.TWDataCreateWithGoBytes(inputBytes) + defer C.TWDataDelete(inputData) - fmt.Println("<== bitcoin public key is valid: ", C.TWPublicKeyIsValid(pubKeyData, C.TWPublicKeyTypeSECP256k1)) + outputData := C.TWAnySignerSign(inputData, C.TWCoinTypeBitcoin) + defer C.TWDataDelete(outputData) - address := C.TWHDWalletGetAddressForCoin(wallet, C.TWCoinTypeBitcoin) - fmt.Println("<== bitcoin address: ", TWStringGoString(address)) + var output bitcoin.SigningOutput + _ = proto.Unmarshal(types.TWDataGoBytes(outputData), &output) + fmt.Println("<== bitcoin signed tx: ", hex.EncodeToString(output.Encoded)) } diff --git a/samples/go/protos/bitcoin/Bitcoin.pb.go b/samples/go/protos/bitcoin/Bitcoin.pb.go new file mode 100644 index 00000000000..ed93c62556a --- /dev/null +++ b/samples/go/protos/bitcoin/Bitcoin.pb.go @@ -0,0 +1,660 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: Bitcoin.proto + +package bitcoin + +import ( + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Transaction struct { + // Transaction data format version. + Version int32 `protobuf:"zigzag32,1,opt,name=version,proto3" json:"version,omitempty"` + // The block number or timestamp at which this transaction is unlocked. + LockTime uint32 `protobuf:"varint,2,opt,name=lockTime,proto3" json:"lockTime,omitempty"` + // A list of 1 or more transaction inputs or sources for coins. + Inputs []*TransactionInput `protobuf:"bytes,3,rep,name=inputs,proto3" json:"inputs,omitempty"` + // A list of 1 or more transaction outputs or destinations for coins + Outputs []*TransactionOutput `protobuf:"bytes,4,rep,name=outputs,proto3" json:"outputs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Transaction) Reset() { *m = Transaction{} } +func (m *Transaction) String() string { return proto.CompactTextString(m) } +func (*Transaction) ProtoMessage() {} +func (*Transaction) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{0} +} + +func (m *Transaction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Transaction.Unmarshal(m, b) +} +func (m *Transaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Transaction.Marshal(b, m, deterministic) +} +func (m *Transaction) XXX_Merge(src proto.Message) { + xxx_messageInfo_Transaction.Merge(m, src) +} +func (m *Transaction) XXX_Size() int { + return xxx_messageInfo_Transaction.Size(m) +} +func (m *Transaction) XXX_DiscardUnknown() { + xxx_messageInfo_Transaction.DiscardUnknown(m) +} + +var xxx_messageInfo_Transaction proto.InternalMessageInfo + +func (m *Transaction) GetVersion() int32 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *Transaction) GetLockTime() uint32 { + if m != nil { + return m.LockTime + } + return 0 +} + +func (m *Transaction) GetInputs() []*TransactionInput { + if m != nil { + return m.Inputs + } + return nil +} + +func (m *Transaction) GetOutputs() []*TransactionOutput { + if m != nil { + return m.Outputs + } + return nil +} + +// Bitcoin transaction input. +type TransactionInput struct { + // Reference to the previous transaction's output. + PreviousOutput *OutPoint `protobuf:"bytes,1,opt,name=previousOutput,proto3" json:"previousOutput,omitempty"` + // Transaction version as defined by the sender. + Sequence uint32 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` + // Computational script for confirming transaction authorization. + Script []byte `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionInput) Reset() { *m = TransactionInput{} } +func (m *TransactionInput) String() string { return proto.CompactTextString(m) } +func (*TransactionInput) ProtoMessage() {} +func (*TransactionInput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{1} +} + +func (m *TransactionInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionInput.Unmarshal(m, b) +} +func (m *TransactionInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionInput.Marshal(b, m, deterministic) +} +func (m *TransactionInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionInput.Merge(m, src) +} +func (m *TransactionInput) XXX_Size() int { + return xxx_messageInfo_TransactionInput.Size(m) +} +func (m *TransactionInput) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionInput.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionInput proto.InternalMessageInfo + +func (m *TransactionInput) GetPreviousOutput() *OutPoint { + if m != nil { + return m.PreviousOutput + } + return nil +} + +func (m *TransactionInput) GetSequence() uint32 { + if m != nil { + return m.Sequence + } + return 0 +} + +func (m *TransactionInput) GetScript() []byte { + if m != nil { + return m.Script + } + return nil +} + +// Bitcoin transaction out-point reference. +type OutPoint struct { + // The hash of the referenced transaction. + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + // The index of the specific output in the transaction. + Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` + // Transaction version as defined by the sender. + Sequence uint32 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OutPoint) Reset() { *m = OutPoint{} } +func (m *OutPoint) String() string { return proto.CompactTextString(m) } +func (*OutPoint) ProtoMessage() {} +func (*OutPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{2} +} + +func (m *OutPoint) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OutPoint.Unmarshal(m, b) +} +func (m *OutPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OutPoint.Marshal(b, m, deterministic) +} +func (m *OutPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_OutPoint.Merge(m, src) +} +func (m *OutPoint) XXX_Size() int { + return xxx_messageInfo_OutPoint.Size(m) +} +func (m *OutPoint) XXX_DiscardUnknown() { + xxx_messageInfo_OutPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_OutPoint proto.InternalMessageInfo + +func (m *OutPoint) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *OutPoint) GetIndex() uint32 { + if m != nil { + return m.Index + } + return 0 +} + +func (m *OutPoint) GetSequence() uint32 { + if m != nil { + return m.Sequence + } + return 0 +} + +// Bitcoin transaction output. +type TransactionOutput struct { + // Transaction amount. + Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` + // Usually contains the public key as a Bitcoin script setting up conditions to claim this output. + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionOutput) Reset() { *m = TransactionOutput{} } +func (m *TransactionOutput) String() string { return proto.CompactTextString(m) } +func (*TransactionOutput) ProtoMessage() {} +func (*TransactionOutput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{3} +} + +func (m *TransactionOutput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionOutput.Unmarshal(m, b) +} +func (m *TransactionOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionOutput.Marshal(b, m, deterministic) +} +func (m *TransactionOutput) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionOutput.Merge(m, src) +} +func (m *TransactionOutput) XXX_Size() int { + return xxx_messageInfo_TransactionOutput.Size(m) +} +func (m *TransactionOutput) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionOutput.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionOutput proto.InternalMessageInfo + +func (m *TransactionOutput) GetValue() int64 { + if m != nil { + return m.Value + } + return 0 +} + +func (m *TransactionOutput) GetScript() []byte { + if m != nil { + return m.Script + } + return nil +} + +type UnspentTransaction struct { + OutPoint *OutPoint `protobuf:"bytes,1,opt,name=out_point,json=outPoint,proto3" json:"out_point,omitempty"` + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UnspentTransaction) Reset() { *m = UnspentTransaction{} } +func (m *UnspentTransaction) String() string { return proto.CompactTextString(m) } +func (*UnspentTransaction) ProtoMessage() {} +func (*UnspentTransaction) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{4} +} + +func (m *UnspentTransaction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UnspentTransaction.Unmarshal(m, b) +} +func (m *UnspentTransaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UnspentTransaction.Marshal(b, m, deterministic) +} +func (m *UnspentTransaction) XXX_Merge(src proto.Message) { + xxx_messageInfo_UnspentTransaction.Merge(m, src) +} +func (m *UnspentTransaction) XXX_Size() int { + return xxx_messageInfo_UnspentTransaction.Size(m) +} +func (m *UnspentTransaction) XXX_DiscardUnknown() { + xxx_messageInfo_UnspentTransaction.DiscardUnknown(m) +} + +var xxx_messageInfo_UnspentTransaction proto.InternalMessageInfo + +func (m *UnspentTransaction) GetOutPoint() *OutPoint { + if m != nil { + return m.OutPoint + } + return nil +} + +func (m *UnspentTransaction) GetScript() []byte { + if m != nil { + return m.Script + } + return nil +} + +func (m *UnspentTransaction) GetAmount() int64 { + if m != nil { + return m.Amount + } + return 0 +} + +// Input data necessary to create a signed transaction. +type SigningInput struct { + // Hash type to use when signing. + HashType uint32 `protobuf:"varint,1,opt,name=hash_type,json=hashType,proto3" json:"hash_type,omitempty"` + // Amount to send. + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + // Transaction fee per byte. + ByteFee int64 `protobuf:"varint,3,opt,name=byte_fee,json=byteFee,proto3" json:"byte_fee,omitempty"` + // Recipient's address. + ToAddress string `protobuf:"bytes,4,opt,name=to_address,json=toAddress,proto3" json:"to_address,omitempty"` + // Change address. + ChangeAddress string `protobuf:"bytes,5,opt,name=change_address,json=changeAddress,proto3" json:"change_address,omitempty"` + // Available private keys. + PrivateKey [][]byte `protobuf:"bytes,10,rep,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + // Available redeem scripts indexed by script hash. + Scripts map[string][]byte `protobuf:"bytes,11,rep,name=scripts,proto3" json:"scripts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Available unspent transaction outputs. + Utxo []*UnspentTransaction `protobuf:"bytes,12,rep,name=utxo,proto3" json:"utxo,omitempty"` + // If sending max amount. + UseMaxAmount bool `protobuf:"varint,13,opt,name=use_max_amount,json=useMaxAmount,proto3" json:"use_max_amount,omitempty"` + // Coin type (forks). + CoinType uint32 `protobuf:"varint,14,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + // Optional transaction plan + Plan *TransactionPlan `protobuf:"bytes,15,opt,name=plan,proto3" json:"plan,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SigningInput) Reset() { *m = SigningInput{} } +func (m *SigningInput) String() string { return proto.CompactTextString(m) } +func (*SigningInput) ProtoMessage() {} +func (*SigningInput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{5} +} + +func (m *SigningInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SigningInput.Unmarshal(m, b) +} +func (m *SigningInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SigningInput.Marshal(b, m, deterministic) +} +func (m *SigningInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_SigningInput.Merge(m, src) +} +func (m *SigningInput) XXX_Size() int { + return xxx_messageInfo_SigningInput.Size(m) +} +func (m *SigningInput) XXX_DiscardUnknown() { + xxx_messageInfo_SigningInput.DiscardUnknown(m) +} + +var xxx_messageInfo_SigningInput proto.InternalMessageInfo + +func (m *SigningInput) GetHashType() uint32 { + if m != nil { + return m.HashType + } + return 0 +} + +func (m *SigningInput) GetAmount() int64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *SigningInput) GetByteFee() int64 { + if m != nil { + return m.ByteFee + } + return 0 +} + +func (m *SigningInput) GetToAddress() string { + if m != nil { + return m.ToAddress + } + return "" +} + +func (m *SigningInput) GetChangeAddress() string { + if m != nil { + return m.ChangeAddress + } + return "" +} + +func (m *SigningInput) GetPrivateKey() [][]byte { + if m != nil { + return m.PrivateKey + } + return nil +} + +func (m *SigningInput) GetScripts() map[string][]byte { + if m != nil { + return m.Scripts + } + return nil +} + +func (m *SigningInput) GetUtxo() []*UnspentTransaction { + if m != nil { + return m.Utxo + } + return nil +} + +func (m *SigningInput) GetUseMaxAmount() bool { + if m != nil { + return m.UseMaxAmount + } + return false +} + +func (m *SigningInput) GetCoinType() uint32 { + if m != nil { + return m.CoinType + } + return 0 +} + +func (m *SigningInput) GetPlan() *TransactionPlan { + if m != nil { + return m.Plan + } + return nil +} + +// Describes a preliminary transaction plan. +type TransactionPlan struct { + // Amount to be received at the other end. + Amount int64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + // Maximum available amount. + AvailableAmount int64 `protobuf:"varint,2,opt,name=available_amount,json=availableAmount,proto3" json:"available_amount,omitempty"` + // Estimated transaction fee. + Fee int64 `protobuf:"varint,3,opt,name=fee,proto3" json:"fee,omitempty"` + // Change. + Change int64 `protobuf:"varint,4,opt,name=change,proto3" json:"change,omitempty"` + // Selected unspent transaction outputs. + Utxos []*UnspentTransaction `protobuf:"bytes,5,rep,name=utxos,proto3" json:"utxos,omitempty"` + // Zcash branch id + BranchId []byte `protobuf:"bytes,6,opt,name=branch_id,json=branchId,proto3" json:"branch_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionPlan) Reset() { *m = TransactionPlan{} } +func (m *TransactionPlan) String() string { return proto.CompactTextString(m) } +func (*TransactionPlan) ProtoMessage() {} +func (*TransactionPlan) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{6} +} + +func (m *TransactionPlan) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionPlan.Unmarshal(m, b) +} +func (m *TransactionPlan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionPlan.Marshal(b, m, deterministic) +} +func (m *TransactionPlan) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionPlan.Merge(m, src) +} +func (m *TransactionPlan) XXX_Size() int { + return xxx_messageInfo_TransactionPlan.Size(m) +} +func (m *TransactionPlan) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionPlan.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionPlan proto.InternalMessageInfo + +func (m *TransactionPlan) GetAmount() int64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *TransactionPlan) GetAvailableAmount() int64 { + if m != nil { + return m.AvailableAmount + } + return 0 +} + +func (m *TransactionPlan) GetFee() int64 { + if m != nil { + return m.Fee + } + return 0 +} + +func (m *TransactionPlan) GetChange() int64 { + if m != nil { + return m.Change + } + return 0 +} + +func (m *TransactionPlan) GetUtxos() []*UnspentTransaction { + if m != nil { + return m.Utxos + } + return nil +} + +func (m *TransactionPlan) GetBranchId() []byte { + if m != nil { + return m.BranchId + } + return nil +} + +// Transaction signing output. +type SigningOutput struct { + // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. + Transaction *Transaction `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"` + // Signed and encoded transaction bytes. + Encoded []byte `protobuf:"bytes,2,opt,name=encoded,proto3" json:"encoded,omitempty"` + // Transaction id + TransactionId string `protobuf:"bytes,3,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + // Optional error message + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SigningOutput) Reset() { *m = SigningOutput{} } +func (m *SigningOutput) String() string { return proto.CompactTextString(m) } +func (*SigningOutput) ProtoMessage() {} +func (*SigningOutput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{7} +} + +func (m *SigningOutput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SigningOutput.Unmarshal(m, b) +} +func (m *SigningOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SigningOutput.Marshal(b, m, deterministic) +} +func (m *SigningOutput) XXX_Merge(src proto.Message) { + xxx_messageInfo_SigningOutput.Merge(m, src) +} +func (m *SigningOutput) XXX_Size() int { + return xxx_messageInfo_SigningOutput.Size(m) +} +func (m *SigningOutput) XXX_DiscardUnknown() { + xxx_messageInfo_SigningOutput.DiscardUnknown(m) +} + +var xxx_messageInfo_SigningOutput proto.InternalMessageInfo + +func (m *SigningOutput) GetTransaction() *Transaction { + if m != nil { + return m.Transaction + } + return nil +} + +func (m *SigningOutput) GetEncoded() []byte { + if m != nil { + return m.Encoded + } + return nil +} + +func (m *SigningOutput) GetTransactionId() string { + if m != nil { + return m.TransactionId + } + return "" +} + +func (m *SigningOutput) GetError() string { + if m != nil { + return m.Error + } + return "" +} + +func init() { + proto.RegisterType((*Transaction)(nil), "TW.Bitcoin.Proto.Transaction") + proto.RegisterType((*TransactionInput)(nil), "TW.Bitcoin.Proto.TransactionInput") + proto.RegisterType((*OutPoint)(nil), "TW.Bitcoin.Proto.OutPoint") + proto.RegisterType((*TransactionOutput)(nil), "TW.Bitcoin.Proto.TransactionOutput") + proto.RegisterType((*UnspentTransaction)(nil), "TW.Bitcoin.Proto.UnspentTransaction") + proto.RegisterType((*SigningInput)(nil), "TW.Bitcoin.Proto.SigningInput") + proto.RegisterMapType((map[string][]byte)(nil), "TW.Bitcoin.Proto.SigningInput.ScriptsEntry") + proto.RegisterType((*TransactionPlan)(nil), "TW.Bitcoin.Proto.TransactionPlan") + proto.RegisterType((*SigningOutput)(nil), "TW.Bitcoin.Proto.SigningOutput") +} + +func init() { proto.RegisterFile("Bitcoin.proto", fileDescriptor_9fbb050c4cb9ab40) } + +var fileDescriptor_9fbb050c4cb9ab40 = []byte{ + // 715 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xdb, 0x6a, 0xdb, 0x4c, + 0x10, 0x46, 0x96, 0xe3, 0xc3, 0xf8, 0x10, 0x67, 0xf9, 0x0f, 0xfa, 0x13, 0xc2, 0xef, 0xaa, 0x29, + 0xb8, 0x14, 0x7c, 0x91, 0x52, 0x1a, 0x0c, 0xa5, 0x24, 0x90, 0x42, 0x28, 0x25, 0x66, 0xe3, 0xd2, + 0x4b, 0xb1, 0x96, 0xb6, 0xf1, 0x36, 0xca, 0xae, 0x2a, 0xad, 0x5c, 0xfb, 0xa2, 0x2f, 0xd0, 0x17, + 0xe9, 0x5b, 0xf4, 0x35, 0xfa, 0x32, 0xbd, 0x28, 0x7b, 0x90, 0x23, 0xdb, 0x34, 0xe4, 0xca, 0xfb, + 0x0d, 0x33, 0xdf, 0xec, 0xf7, 0xcd, 0xac, 0x05, 0x9d, 0x33, 0x26, 0x43, 0xc1, 0xf8, 0x30, 0x49, + 0x85, 0x14, 0xa8, 0x37, 0xf9, 0x30, 0x2c, 0x22, 0x63, 0x15, 0xf1, 0x7f, 0x38, 0xd0, 0x9a, 0xa4, + 0x84, 0x67, 0x24, 0x94, 0x4c, 0x70, 0xe4, 0x41, 0x7d, 0x4e, 0xd3, 0x8c, 0x09, 0xee, 0x39, 0x7d, + 0x67, 0xb0, 0x87, 0x0b, 0x88, 0xf6, 0xa1, 0x11, 0x8b, 0xf0, 0x66, 0xc2, 0x6e, 0xa9, 0x57, 0xe9, + 0x3b, 0x83, 0x0e, 0x5e, 0x61, 0x34, 0x82, 0x1a, 0xe3, 0x49, 0x2e, 0x33, 0xcf, 0xed, 0xbb, 0x83, + 0xd6, 0xb1, 0x3f, 0xdc, 0x6c, 0x34, 0x2c, 0x35, 0xb9, 0x50, 0xa9, 0xd8, 0x56, 0xa0, 0x57, 0x50, + 0x17, 0xb9, 0xd4, 0xc5, 0x55, 0x5d, 0xfc, 0xf8, 0xde, 0xe2, 0x4b, 0x9d, 0x8b, 0x8b, 0x1a, 0xff, + 0x9b, 0x03, 0xbd, 0x4d, 0x6e, 0x74, 0x06, 0xdd, 0x24, 0xa5, 0x73, 0x26, 0xf2, 0xcc, 0xe4, 0x6b, + 0x31, 0xad, 0xe3, 0xfd, 0x6d, 0xea, 0xcb, 0x5c, 0x8e, 0x05, 0xe3, 0x12, 0x6f, 0x54, 0x28, 0xbd, + 0x19, 0xfd, 0x9c, 0x53, 0x1e, 0xae, 0xf4, 0x16, 0x18, 0xfd, 0x03, 0xb5, 0x2c, 0x4c, 0x59, 0x22, + 0x3d, 0xb7, 0xef, 0x0c, 0xda, 0xd8, 0x22, 0x7f, 0x0c, 0x8d, 0x82, 0x0f, 0x21, 0xa8, 0xce, 0x48, + 0x36, 0xd3, 0x9d, 0xdb, 0x58, 0x9f, 0xd1, 0x5f, 0xb0, 0xc3, 0x78, 0x44, 0x17, 0x96, 0xd0, 0x80, + 0xb5, 0x4e, 0xee, 0x7a, 0x27, 0xff, 0x14, 0xf6, 0xb6, 0xc4, 0x2b, 0x9a, 0x39, 0x89, 0x73, 0xaa, + 0xb9, 0x5d, 0x6c, 0x40, 0xe9, 0x52, 0x95, 0xb5, 0x4b, 0x7d, 0x05, 0xf4, 0x9e, 0x67, 0x09, 0xe5, + 0xb2, 0x3c, 0xe8, 0x97, 0xd0, 0x14, 0xb9, 0x0c, 0x12, 0x75, 0xd7, 0x07, 0xb8, 0xd3, 0x10, 0x85, + 0xae, 0x3f, 0xb4, 0x51, 0x71, 0x72, 0x2b, 0x72, 0x6e, 0x3c, 0x71, 0xb1, 0x45, 0xfe, 0x2f, 0x17, + 0xda, 0x57, 0xec, 0x9a, 0x33, 0x7e, 0x6d, 0x86, 0x73, 0x00, 0x4d, 0x65, 0x46, 0x20, 0x97, 0x89, + 0x51, 0xd0, 0xc1, 0x0d, 0x15, 0x98, 0x2c, 0x13, 0x5a, 0x62, 0xa9, 0x94, 0x59, 0xd0, 0x7f, 0xd0, + 0x98, 0x2e, 0x25, 0x0d, 0x3e, 0x52, 0x6a, 0xf9, 0xeb, 0x0a, 0xbf, 0xa1, 0x14, 0x1d, 0x02, 0x48, + 0x11, 0x90, 0x28, 0x4a, 0x69, 0xa6, 0x76, 0xc8, 0x19, 0x34, 0x71, 0x53, 0x8a, 0x53, 0x13, 0x40, + 0x4f, 0xa0, 0x1b, 0xce, 0x08, 0xbf, 0xa6, 0xab, 0x94, 0x1d, 0x9d, 0xd2, 0x31, 0xd1, 0x22, 0xed, + 0x7f, 0x68, 0x25, 0x29, 0x9b, 0x13, 0x49, 0x83, 0x1b, 0xba, 0xf4, 0xa0, 0xef, 0x0e, 0xda, 0x18, + 0x6c, 0xe8, 0x2d, 0x5d, 0xa2, 0x73, 0xa8, 0x1b, 0xa5, 0x99, 0xd7, 0xd2, 0x7b, 0xfa, 0x6c, 0xdb, + 0xae, 0xb2, 0xce, 0xe1, 0x95, 0xc9, 0x3e, 0xe7, 0x32, 0x5d, 0xe2, 0xa2, 0x16, 0x9d, 0x40, 0x35, + 0x97, 0x0b, 0xe1, 0xb5, 0x35, 0xc7, 0xd1, 0x36, 0xc7, 0xf6, 0xac, 0xb0, 0xae, 0x40, 0x47, 0xd0, + 0xcd, 0x33, 0x1a, 0xdc, 0x92, 0x45, 0x60, 0x2d, 0xea, 0xf4, 0x9d, 0x41, 0x03, 0xb7, 0xf3, 0x8c, + 0xbe, 0x23, 0x8b, 0x53, 0x63, 0xd4, 0x01, 0x34, 0x15, 0x99, 0x71, 0xb7, 0x6b, 0xdc, 0x55, 0x01, + 0xed, 0xee, 0x0b, 0xa8, 0x26, 0x31, 0xe1, 0xde, 0xae, 0x9e, 0xf7, 0xa3, 0x7b, 0x1f, 0xda, 0x38, + 0x26, 0x1c, 0xeb, 0xf4, 0xfd, 0x11, 0xb4, 0xcb, 0x62, 0x50, 0x0f, 0x5c, 0xe5, 0x91, 0xa3, 0x7d, + 0x54, 0xc7, 0xbb, 0x8d, 0x34, 0x3b, 0x61, 0xc0, 0xa8, 0x72, 0xe2, 0xf8, 0x3f, 0x1d, 0xd8, 0xdd, + 0x60, 0x2d, 0x0d, 0xd9, 0x59, 0x1b, 0xf2, 0x53, 0xe8, 0x91, 0x39, 0x61, 0x31, 0x99, 0xc6, 0x34, + 0x58, 0x5b, 0x83, 0xdd, 0x55, 0xdc, 0xca, 0xec, 0x81, 0x7b, 0xb7, 0x0a, 0xea, 0xa8, 0x48, 0xcd, + 0x44, 0xf5, 0x0a, 0xb8, 0xd8, 0x22, 0x34, 0x82, 0x1d, 0x65, 0x9f, 0x1a, 0xfb, 0xc3, 0x1d, 0x37, + 0x25, 0xca, 0xcc, 0x69, 0x4a, 0x78, 0x38, 0x0b, 0x58, 0xe4, 0xd5, 0xb4, 0xb4, 0x86, 0x09, 0x5c, + 0x44, 0xfe, 0x77, 0x07, 0x3a, 0x76, 0xe0, 0xf6, 0x5d, 0xbe, 0x86, 0x96, 0xbc, 0x23, 0xb1, 0xaf, + 0xea, 0xf0, 0x5e, 0x97, 0x71, 0xb9, 0x42, 0xfd, 0xfb, 0x52, 0x1e, 0x8a, 0x88, 0x46, 0xd6, 0xc8, + 0x02, 0xaa, 0x2d, 0x2e, 0x25, 0xaa, 0xeb, 0xb8, 0x66, 0x8b, 0x4b, 0xd1, 0x8b, 0x48, 0xcd, 0x81, + 0xa6, 0xa9, 0x48, 0xed, 0x33, 0x30, 0xe0, 0xec, 0x5f, 0xf8, 0xfb, 0x0b, 0x89, 0x63, 0x2a, 0x87, + 0xa1, 0x48, 0xe9, 0xf0, 0x13, 0x67, 0xe6, 0x7b, 0x30, 0xad, 0xe9, 0x9f, 0xe7, 0xbf, 0x03, 0x00, + 0x00, 0xff, 0xff, 0x53, 0xd6, 0xbb, 0x06, 0x27, 0x06, 0x00, 0x00, +} diff --git a/samples/go/types/twdata.go b/samples/go/types/twdata.go new file mode 100644 index 00000000000..f1195643570 --- /dev/null +++ b/samples/go/types/twdata.go @@ -0,0 +1,31 @@ +package types + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #include +import "C" + +import ( + "unsafe" + "encoding/hex" +) + +// C.TWData -> Go byte[] +func TWDataGoBytes(d unsafe.Pointer) []byte { + cBytes := C.TWDataBytes(d) + cSize := C.TWDataSize(d) + return C.GoBytes(unsafe.Pointer(cBytes), C.int(cSize)) +} + +// Go byte[] -> C.TWData +func TWDataCreateWithGoBytes(d []byte) unsafe.Pointer { + cBytes := C.CBytes(d) + defer C.free(unsafe.Pointer(cBytes)) + data := C.TWDataCreateWithBytes((*C.uchar)(cBytes), C.ulong(len(d))) + return data +} + +// C.TWData -> Go hex string +func TWDataHexString(d unsafe.Pointer) string { + return hex.EncodeToString(TWDataGoBytes(d)) +} diff --git a/samples/go/types/twstring.go b/samples/go/types/twstring.go new file mode 100644 index 00000000000..de9fbb5eb2e --- /dev/null +++ b/samples/go/types/twstring.go @@ -0,0 +1,23 @@ +package types + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #include +import "C" + +import ( + "unsafe" +) + +// C.TWString -> Go string +func TWStringGoString(s unsafe.Pointer) string { + return C.GoString(C.TWStringUTF8Bytes(s)) +} + +// Go string -> C.TWString +func TWStringCreateWithGoString(s string) unsafe.Pointer { + cStr := C.CString(s) + defer C.free(unsafe.Pointer(cStr)) + str := C.TWStringCreateWithUTF8Bytes(cStr) + return str +} diff --git a/samples/osx/cocoapods/Podfile b/samples/osx/cocoapods/Podfile index 320e1550d7c..922910ccdc8 100644 --- a/samples/osx/cocoapods/Podfile +++ b/samples/osx/cocoapods/Podfile @@ -2,7 +2,6 @@ platform :osx, '10.12' target 'WalletCoreExample' do use_frameworks! - pod 'TrustWalletCore', '~> 2.0.0' - + pod 'TrustWalletCore' end diff --git a/samples/osx/cocoapods/Podfile.lock b/samples/osx/cocoapods/Podfile.lock index 06dec5ec6ee..4d9eb6faebb 100644 --- a/samples/osx/cocoapods/Podfile.lock +++ b/samples/osx/cocoapods/Podfile.lock @@ -1,14 +1,14 @@ PODS: - - SwiftProtobuf (1.8.0) - - TrustWalletCore (2.0.3): - - TrustWalletCore/Core (= 2.0.3) - - TrustWalletCore/Core (2.0.3): + - SwiftProtobuf (1.9.0) + - TrustWalletCore (2.1.1): + - TrustWalletCore/Core (= 2.1.1) + - TrustWalletCore/Core (2.1.1): - TrustWalletCore/Types - - TrustWalletCore/Types (2.0.3): + - TrustWalletCore/Types (2.1.1): - SwiftProtobuf DEPENDENCIES: - - TrustWalletCore (~> 2.0.0) + - TrustWalletCore (~> 2.1.0) SPEC REPOS: trunk: @@ -16,9 +16,9 @@ SPEC REPOS: - TrustWalletCore SPEC CHECKSUMS: - SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70 - TrustWalletCore: 43c900920c15fd1ccb8774a051b1055a660f2e91 + SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932 + TrustWalletCore: cd0373c69fd9bf92700783287544ce6a9aab041b -PODFILE CHECKSUM: 9f88ed3f7ceadcf38049ea37584a81f309d04e83 +PODFILE CHECKSUM: a34fe0b289ed6fb4db3ae7964f62fa6b21ff0c0d -COCOAPODS: 1.9.0 +COCOAPODS: 1.9.3 diff --git a/samples/osx/cocoapods/WalletCoreExample/ViewController.swift b/samples/osx/cocoapods/WalletCoreExample/ViewController.swift index bbcd4e29a0d..bd0f1a911ca 100644 --- a/samples/osx/cocoapods/WalletCoreExample/ViewController.swift +++ b/samples/osx/cocoapods/WalletCoreExample/ViewController.swift @@ -16,13 +16,14 @@ class ViewController: NSViewController { let wallet = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "") print("Mnemonic: ", wallet.mnemonic) - let coin: CoinType = .ethereum + // Ethereum example + var coin: CoinType = .ethereum // Get the default address - let address = wallet.getAddressForCoin(coin: coin) - print("Default address: ", address) + let addressEth = wallet.getAddressForCoin(coin: coin) + print("Default ETH address: ", addressEth) // Signing a transaction (using AnySigner) - let secretPrivateKey = wallet.getKeyForCoin(coin: coin) + let secretPrivateKeyEth = wallet.getKeyForCoin(coin: coin) let dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986" let signerInput = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! @@ -30,10 +31,53 @@ class ViewController: NSViewController { $0.gasLimit = Data(hexString: "5208")! // decimal 21000 $0.toAddress = dummyReceiverAddress $0.amount = Data(hexString: "0348bca5a16000")! - $0.privateKey = secretPrivateKey.data + $0.privateKey = secretPrivateKeyEth.data } - let output: EthereumSigningOutput = AnySigner.sign(input: signerInput, coin: .ethereum) + let outputEth: EthereumSigningOutput = AnySigner.sign(input: signerInput, coin: coin) print("Signed transaction:") - print(" data: ", output.encoded.hexString) + print(" data: ", outputEth.encoded.hexString) + + // Bitcoin example + coin = .bitcoin + // Get the default address + let addressBtc = wallet.getAddressForCoin(coin: coin) + print("Default BTC address: ", addressBtc) + + // Build a transaction + let toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + let changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" + let secretPrivateKeyBtc = wallet.getKeyForCoin(coin: coin) + let outPoint = BitcoinOutPoint.with { + $0.hash = Data(hexString: "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2")! + $0.index = 2 + } + let utxo = BitcoinUnspentTransaction.with { + $0.amount = 5151 + $0.outPoint = outPoint + $0.script = BitcoinScript.lockScriptForAddress(address: addressBtc, coin: coin).data + } + var input = BitcoinSigningInput.with { + $0.amount = 600 + $0.hashType = TWBitcoinSigHashTypeAll.rawValue + $0.toAddress = toAddress + $0.changeAddress = changeAddress + $0.byteFee = 2 + $0.coinType = coin.rawValue + } + input.utxo.append(utxo) + input.privateKey.append(secretPrivateKeyBtc.data) + + // Calculate fee (plan a tranaction) + let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: coin) + print("Planned fee: ", plan.fee, "amount:", plan.amount, "avail_amount:", plan.availableAmount, "change:", plan.change) + + // Set the precomputed plan + input.plan = plan + input.amount = plan.amount + + let outputBtc: BitcoinSigningOutput = AnySigner.sign(input: input, coin: coin) + + print("Signed transaction:") + print(" data: ", outputBtc.encoded.hexString) } } diff --git a/src/Aeternity/Address.cpp b/src/Aeternity/Address.cpp index ff256597044..101ba527854 100644 --- a/src/Aeternity/Address.cpp +++ b/src/Aeternity/Address.cpp @@ -13,7 +13,7 @@ using namespace TW::Aeternity; /// Determines whether a string makes a valid address. -bool Address::isValid(const std::string &string) { +bool Address::isValid(const std::string& string) { if (string.empty()) { return false; } @@ -34,7 +34,7 @@ Address::Address(const PublicKey &publicKey) { } /// Initializes an address from a string representation. -Address::Address(const std::string &string) { +Address::Address(const std::string& string) { if (!isValid(string)) { throw std::invalid_argument("Invalid address"); } @@ -48,11 +48,11 @@ std::string Address::string() const { return Identifiers::prefixAccountPubkey + Base58::bitcoin.encodeCheck(bytes); } -bool Address::checkType(const std::string &type) { +bool Address::checkType(const std::string& type) { return type == Identifiers::prefixAccountPubkey; } -bool Address::checkPayload(const std::string &payload) { +bool Address::checkPayload(const std::string& payload) { unsigned long base58 = Base58::bitcoin.decodeCheck(payload).size(); return base58 == size; } diff --git a/src/Aeternity/Address.h b/src/Aeternity/Address.h index 0f8b4e24724..07e9b732939 100644 --- a/src/Aeternity/Address.h +++ b/src/Aeternity/Address.h @@ -15,10 +15,10 @@ class Address { Data bytes; /// Determines whether a string makes a valid address. - static bool isValid(const std::string &string); + static bool isValid(const std::string& string); /// Initializes an address from a string representation. - explicit Address(const std::string &string); + explicit Address(const std::string& string); /// Initializes an address from a public key. explicit Address(const PublicKey &publicKey); @@ -28,8 +28,8 @@ class Address { private: - static bool checkType(const std::string &type); - static bool checkPayload(const std::string &payload); + static bool checkType(const std::string& type); + static bool checkPayload(const std::string& payload); }; inline bool operator==(const Address& lhs, const Address& rhs) { diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index 02a6c0412a2..7c9adf1fafa 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -48,7 +48,7 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey &privateKey, Transaction return createProtoOutput(signature, signedEncodedTx); } -Data Signer::buildRlpTxRaw(Data &txRaw, Data &sigRaw) { +Data Signer::buildRlpTxRaw(Data& txRaw, Data& sigRaw) { auto rlpTxRaw = Data(); auto signaturesList = Data(); append(signaturesList, Ethereum::RLP::encode(sigRaw)); @@ -61,7 +61,7 @@ Data Signer::buildRlpTxRaw(Data &txRaw, Data &sigRaw) { return Ethereum::RLP::encodeList(rlpTxRaw); } -Data Signer::buildMessageToSign(Data &txRaw) { +Data Signer::buildMessageToSign(Data& txRaw) { auto data = Data(); Data bytes(Identifiers::networkId.begin(), Identifiers::networkId.end()); append(data, bytes); @@ -69,7 +69,7 @@ Data Signer::buildMessageToSign(Data &txRaw) { return data; } -Proto::SigningOutput Signer::createProtoOutput(std::string &signature, const std::string &signedTx) { +Proto::SigningOutput Signer::createProtoOutput(std::string& signature, const std::string& signedTx) { auto output = Proto::SigningOutput(); output.set_signature(signature); @@ -77,7 +77,7 @@ Proto::SigningOutput Signer::createProtoOutput(std::string &signature, const std return output; } -std::string Signer::encodeBase64WithChecksum(const std::string &prefix, const TW::Data &rawTx) { +std::string Signer::encodeBase64WithChecksum(const std::string& prefix, const TW::Data& rawTx) { auto checksum = Hash::sha256(Hash::sha256(rawTx)); std::vector checksumPart(checksum.begin(), checksum.begin() + checkSumSize); diff --git a/src/Aeternity/Signer.h b/src/Aeternity/Signer.h index eae11aa4bc4..dd289e5487d 100644 --- a/src/Aeternity/Signer.h +++ b/src/Aeternity/Signer.h @@ -21,14 +21,14 @@ class Signer { private: static const uint8_t checkSumSize = 4; - static Data buildRlpTxRaw(Data &txRaw, Data &sigRaw); + static Data buildRlpTxRaw(Data& txRaw, Data& sigRaw); - static Data buildMessageToSign(Data &txRaw); + static Data buildMessageToSign(Data& txRaw); - static Proto::SigningOutput createProtoOutput(std::string &signature, const std::string &signedTx); + static Proto::SigningOutput createProtoOutput(std::string& signature, const std::string& signedTx); /// Encode a byte array into base64 with prefix and a checksum - static std::string encodeBase64WithChecksum(const std::string &prefix, const TW::Data &rawTx); + static std::string encodeBase64WithChecksum(const std::string& prefix, const TW::Data& rawTx); }; } // namespace TW::Aeternity diff --git a/src/Aeternity/Transaction.cpp b/src/Aeternity/Transaction.cpp index 198df6c3af0..416531544b5 100644 --- a/src/Aeternity/Transaction.cpp +++ b/src/Aeternity/Transaction.cpp @@ -26,11 +26,11 @@ Data Transaction::encode() { append(encoded, encodeSafeZero(nonce)); append(encoded, Ethereum::RLP::encode(payload)); - const Data &raw = Ethereum::RLP::encodeList(encoded); + const Data& raw = Ethereum::RLP::encodeList(encoded); return raw; } -TW::Data Transaction::buildTag(const std::string &address) { +TW::Data Transaction::buildTag(const std::string& address) { auto payload = address.substr(Identifiers::prefixTransaction.size(), address.size()); auto data = Data(); diff --git a/src/Aeternity/Transaction.h b/src/Aeternity/Transaction.h index 979d9b8312d..6bace4629b8 100644 --- a/src/Aeternity/Transaction.h +++ b/src/Aeternity/Transaction.h @@ -31,11 +31,11 @@ class Transaction { uint64_t nonce; Transaction( - std::string &sender_id, - std::string &recipientId, + std::string& sender_id, + std::string& recipientId, uint256_t amount, uint256_t fee, - std::string &payload, + std::string& payload, uint64_t ttl, uint64_t nonce ) @@ -51,7 +51,7 @@ 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); + 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 diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index d01874a0ad6..a4a3a4adf14 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -23,6 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { /* 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())); Signer::sign(key, transaction); diff --git a/src/Aion/Transaction.cpp b/src/Aion/Transaction.cpp index 19f2bd9c4c0..017dbe96896 100644 --- a/src/Aion/Transaction.cpp +++ b/src/Aion/Transaction.cpp @@ -18,8 +18,7 @@ Data Transaction::encode() const noexcept { append(encoded, Ethereum::RLP::encode(to.bytes)); append(encoded, Ethereum::RLP::encode(amount)); append(encoded, Ethereum::RLP::encode(payload)); - append(encoded, - Ethereum::RLP::encode(uint128_t(155157377101))); // Huge timestamp + 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 diff --git a/src/Aion/Transaction.h b/src/Aion/Transaction.h index e1c8013e5bf..dc975dcaac9 100644 --- a/src/Aion/Transaction.h +++ b/src/Aion/Transaction.h @@ -22,18 +22,20 @@ class Transaction { uint128_t gasLimit; Address to; uint128_t amount; + uint128_t timestamp; std::vector payload; /// Transaction signature. std::vector signature; Transaction(uint128_t nonce, uint128_t gasPrice, uint128_t gasLimit, Address to, - uint128_t amount, Data payload) + uint128_t amount, uint128_t timestamp, const Data& payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) , gasLimit(std::move(gasLimit)) , to(std::move(to)) , amount(std::move(amount)) + , timestamp(std::move(timestamp)) , payload(std::move(payload)) {} public: diff --git a/src/Algorand/BinaryCoding.h b/src/Algorand/BinaryCoding.h index 87895a97daa..8a3bce78823 100644 --- a/src/Algorand/BinaryCoding.h +++ b/src/Algorand/BinaryCoding.h @@ -11,7 +11,9 @@ namespace TW::Algorand { -static inline void encodeString(std::string string, Data &data) { +#pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" + +static inline void encodeString(std::string string, Data& data) { // encode string header auto bytes = Data(string.begin(), string.end()); if (bytes.size() < 0x20) { @@ -25,7 +27,7 @@ static inline void encodeString(std::string string, Data &data) { // str 16 data.push_back(static_cast(0xda)); encode16BE(static_cast(bytes.size()), data); - } else if (bytes.size() < 0x100000000) { + } else if (bytes.size() < 0x100000000) { // depending on size_t size on platform, may be always true // str 32 data.push_back(static_cast(0xdb)); encode32BE(static_cast(bytes.size()), data); @@ -36,7 +38,7 @@ static inline void encodeString(std::string string, Data &data) { append(data, bytes); } -static inline void encodeNumber(uint64_t number, Data &data) { +static inline void encodeNumber(uint64_t number, Data& data) { if (number < 0x80) { // positive fixint data.push_back(static_cast(number)); @@ -59,7 +61,7 @@ static inline void encodeNumber(uint64_t number, Data &data) { } } -static inline void encodeBytes(const Data &bytes, Data &data) { +static inline void encodeBytes(const Data& bytes, Data& data) { auto size = bytes.size(); if (size < 0x100) { // bin 8 diff --git a/src/Algorand/Transaction.cpp b/src/Algorand/Transaction.cpp index a2f6cdd7820..ac608af5b3e 100644 --- a/src/Algorand/Transaction.cpp +++ b/src/Algorand/Transaction.cpp @@ -71,7 +71,7 @@ Data Transaction::serialize() const { return data; } -Data Transaction::serialize(Data &signature) const { +Data Transaction::serialize(Data& signature) const { /* Algorand transaction and signature are encoded with msgpack: { "sig": diff --git a/src/Algorand/Transaction.h b/src/Algorand/Transaction.h index a545d65d48f..f7b9c3dd694 100644 --- a/src/Algorand/Transaction.h +++ b/src/Algorand/Transaction.h @@ -27,7 +27,7 @@ class Transaction { Data genesisHash; Transaction(Address &from, Address &to, uint64_t fee, uint64_t amount, uint64_t firstRound, - uint64_t lastRound, Data ¬e, std::string type, std::string &genesisIdg, Data &genesisHash) + 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) @@ -36,7 +36,7 @@ class Transaction { public: Data serialize() const; - Data serialize(Data &signature) const; + Data serialize(Data& signature) const; }; } // namespace TW::Algorand diff --git a/src/Base32.h b/src/Base32.h index dd94800f3fa..7119a9944be 100644 --- a/src/Base32.h +++ b/src/Base32.h @@ -45,7 +45,10 @@ inline std::string encode(const Data& val, const char* alphabet = nullptr) { } // perform the base32 encode char* retval = base32_encode(val.data(), inLen, buf, outLen, alphabet); - assert(retval != nullptr); + if (retval == nullptr) { + // return empty string if failed + return std::string(); + } // make sure there is a terminator ath the end buf[outLen - 1] = '\0'; return std::string(buf); diff --git a/src/Base58.cpp b/src/Base58.cpp index 207809114c3..c0e430f50a0 100644 --- a/src/Base58.cpp +++ b/src/Base58.cpp @@ -11,6 +11,7 @@ #include #include +#include using namespace TW; diff --git a/src/Bech32Address.h b/src/Bech32Address.h index a5aea665805..23b4c06a640 100644 --- a/src/Bech32Address.h +++ b/src/Bech32Address.h @@ -48,7 +48,7 @@ class Bech32Address { Bech32Address(const std::string& hrp, HasherType hasher, const PublicKey& publicKey); void setHrp(const std::string& hrp_in) { hrp = std::move(hrp_in); } - void setKey(Data keyHash_in) { keyHash = std::move(keyHash_in); } + void setKey(const Data& keyHash_in) { keyHash = std::move(keyHash_in); } inline const std::string& getHrp() const { return hrp; } diff --git a/src/Binance/Address.cpp b/src/Binance/Address.cpp index 311c05306e8..481f2eda9ca 100644 --- a/src/Binance/Address.cpp +++ b/src/Binance/Address.cpp @@ -8,8 +8,21 @@ #include "Address.h" #include +#include using 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; + } + } + return result; +} diff --git a/src/Binance/Address.h b/src/Binance/Address.h index 412fac043c0..2fb7c1fab0d 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -16,13 +16,14 @@ namespace TW::Binance { class Address: public Bech32Address { public: static const std::string hrp; // HRP_BINANCE + static const std::string hrpValidator; // HRP_BINANCE - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr); Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) {} + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2_RIPEMD, publicKey) {} diff --git a/src/Binance/Serialization.cpp b/src/Binance/Serialization.cpp index 9cf72597680..a6493e68d83 100644 --- a/src/Binance/Serialization.cpp +++ b/src/Binance/Serialization.cpp @@ -7,20 +7,27 @@ #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 = std::vector(bytes.begin(), bytes.end()); - auto address = Address(data); - return address.string(); + auto data = Data(bytes.begin(), bytes.end()); + return Address(data).string(); } -json Binance::signatureJSON(const Binance::Proto::SigningInput& input) { +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(); @@ -32,7 +39,7 @@ json Binance::signatureJSON(const Binance::Proto::SigningInput& input) { return j; } -json Binance::orderJSON(const Binance::Proto::SigningInput& input) { +json Binance::orderJSON(const Proto::SigningInput& input) { json j; if (input.has_trade_order()) { j["id"] = input.trade_order().id(); @@ -80,40 +87,78 @@ json Binance::orderJSON(const Binance::Proto::SigningInput& input) { } 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()}, + }; } return j; } -json Binance::inputsJSON(const Binance::Proto::SendOrder& order) { +json Binance::inputsJSON(const Proto::SendOrder& order) { json j = json::array(); for (auto& input : order.inputs()) { - json sj; - sj["address"] = addressString(input.address()); - sj["coins"] = tokensJSON(input.coins()); - j.push_back(sj); + j.push_back({ + {"address", addressString(input.address())}, + {"coins", tokensJSON(input.coins())} + }); } return j; } -json Binance::outputsJSON(const Binance::Proto::SendOrder& order) { +json Binance::outputsJSON(const Proto::SendOrder& order) { json j = json::array(); for (auto& output : order.outputs()) { - json sj; - sj["address"] = addressString(output.address()); - sj["coins"] = tokensJSON(output.coins()); - j.push_back(sj); + 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 ::google::protobuf::RepeatedPtrField& tokens) { +json Binance::tokensJSON(const RepeatedPtrField& tokens) { json j = json::array(); for (auto& token : tokens) { - json sj; - sj["denom"] = token.denom(); - sj["amount"] = token.amount(); - j.push_back(sj); + j.push_back(tokenJSON(token)); } return j; } diff --git a/src/Binance/Serialization.h b/src/Binance/Serialization.h index 5baf29dec85..9b06682fbec 100644 --- a/src/Binance/Serialization.h +++ b/src/Binance/Serialization.h @@ -11,10 +11,11 @@ namespace TW::Binance { -nlohmann::json signatureJSON(const Binance::Proto::SigningInput& input); -nlohmann::json orderJSON(const Binance::Proto::SigningInput& input); -nlohmann::json inputsJSON(const Binance::Proto::SendOrder& order); -nlohmann::json outputsJSON(const Binance::Proto::SendOrder& order); -nlohmann::json tokensJSON(const ::google::protobuf::RepeatedPtrField& tokens); +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 index d5eddf7cd3e..a847cb24a8c 100644 --- a/src/Binance/Signer.cpp +++ b/src/Binance/Signer.cpp @@ -35,6 +35,10 @@ 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}; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); @@ -120,6 +124,18 @@ Data Signer::encodeOrder() const { } 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 { return {}; } diff --git a/src/Bitcoin/CashAddress.cpp b/src/Bitcoin/CashAddress.cpp index 1d22447c961..43eaa408b6c 100644 --- a/src/Bitcoin/CashAddress.cpp +++ b/src/Bitcoin/CashAddress.cpp @@ -78,7 +78,9 @@ CashAddress::CashAddress(const PublicKey& publicKey) { size_t outlen = 0; auto success = cash_addr_to_data(bytes.data(), &outlen, payload.data(), 21) != 0; - assert(success && outlen == CashAddress::size); + if (!success || outlen != CashAddress::size) { + throw std::invalid_argument("unable to cash_addr_to_data"); + } } std::string CashAddress::string() const { diff --git a/src/Bitcoin/Entry.cpp b/src/Bitcoin/Entry.cpp index a64de869007..42c39c99cd1 100644 --- a/src/Bitcoin/Entry.cpp +++ b/src/Bitcoin/Entry.cpp @@ -22,6 +22,7 @@ bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2p case TWCoinTypeMonacoin: case TWCoinTypeQtum: case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: return SegwitAddress::isValid(address, hrp) || Address::isValid(address, {{p2pkh}, {p2sh}}); @@ -60,6 +61,7 @@ string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byt case TWCoinTypeDigiByte: case TWCoinTypeLitecoin: case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: return SegwitAddress(publicKey, 0, hrp).string(); case TWCoinTypeBitcoinCash: diff --git a/src/Bitcoin/Entry.h b/src/Bitcoin/Entry.h index 51e0de06ad8..09d5ff27d81 100644 --- a/src/Bitcoin/Entry.h +++ b/src/Bitcoin/Entry.h @@ -18,6 +18,7 @@ class Entry: public CoinEntry { return { TWCoinTypeBitcoin, TWCoinTypeBitcoinCash, + TWCoinTypeBitcoinGold, TWCoinTypeDash, TWCoinTypeDigiByte, TWCoinTypeDogecoin, diff --git a/src/Bitcoin/FeeCalculator.cpp b/src/Bitcoin/FeeCalculator.cpp new file mode 100644 index 00000000000..27c364da705 --- /dev/null +++ b/src/Bitcoin/FeeCalculator.cpp @@ -0,0 +1,70 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "FeeCalculator.h" + +using namespace TW; + +namespace TW::Bitcoin { + +DefaultFeeCalculator DefaultFeeCalculator::instance; + +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; +} + +class ZCashFeeCalculator : public FeeCalculator { +public: + int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override { return 10000; } + int64_t calculateSingleInput(int64_t byteFee) const override { return 0; } +}; + +class GroestlcoinFeeCalculator : public FeeCalculator { +public: + int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override { return 20000; } + int64_t calculateSingleInput(int64_t byteFee) const override { return 0; } +}; + +class DecredFeeCalculator : public FeeCalculator { +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; + } + + int64_t calculateSingleInput(int64_t byteFee) const override { + return int64_t(166) * byteFee; + } +}; + +DefaultFeeCalculator defaultFeeCalculator; +ZCashFeeCalculator zcashFeeCalculator; +GroestlcoinFeeCalculator groestlcoinFeeCalculator; +DecredFeeCalculator decredFeeCalculator; + +FeeCalculator& getFeeCalculator(TWCoinType coinType) { + switch (coinType) { + case TWCoinTypeZelcash: + case TWCoinTypeZcash: + return zcashFeeCalculator; + + case TWCoinTypeGroestlcoin: + return groestlcoinFeeCalculator; + + case TWCoinTypeDecred: + return decredFeeCalculator; + + default: + return defaultFeeCalculator; + } +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/FeeCalculator.h b/src/Bitcoin/FeeCalculator.h new file mode 100644 index 00000000000..67e2c65bb37 --- /dev/null +++ b/src/Bitcoin/FeeCalculator.h @@ -0,0 +1,32 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include + +namespace TW::Bitcoin { + +/// 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; +}; + +/// Default Bitcoin transaction fee calculator, non-segwit. +class DefaultFeeCalculator : 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 DefaultFeeCalculator instance; +}; + +/// Return the fee calculator for the given coin. +FeeCalculator& getFeeCalculator(TWCoinType coinType); + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index bf1ce32aad7..443b2aacf4a 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -42,6 +42,11 @@ bool Script::isPayToScriptHash() const { bool Script::isPayToWitnessScriptHash() const { // Extra-fast test for pay-to-witness-script-hash + return bytes.size() == 34 && bytes[0] == OP_0 && bytes[1] == 0x20; +} + +bool Script::isPayToWitnessPublicKeyHash() const { + // Extra-fast test for pay-to-witness-public-key-hash return bytes.size() == 22 && bytes[0] == OP_0 && bytes[1] == 0x14; } @@ -55,7 +60,7 @@ bool Script::isWitnessProgram() const { return bytes[1] + 2 == bytes.size(); } -bool Script::matchPayToPubkey(Data& result) const { +bool Script::matchPayToPublicKey(Data& result) const { if (bytes.size() == PublicKey::secp256k1ExtendedSize + 2 && bytes[0] == PublicKey::secp256k1ExtendedSize && bytes.back() == OP_CHECKSIG) { result.clear(); @@ -73,7 +78,7 @@ bool Script::matchPayToPubkey(Data& result) const { return false; } -bool Script::matchPayToPubkeyHash(Data& result) const { +bool Script::matchPayToPublicKeyHash(Data& result) const { if (bytes.size() == 25 && bytes[0] == OP_DUP && bytes[1] == OP_HASH160 && bytes[2] == 20 && bytes[23] == OP_EQUALVERIFY && bytes[24] == OP_CHECKSIG) { result.clear(); @@ -93,30 +98,21 @@ bool Script::matchPayToScriptHash(Data& result) const { } bool Script::matchPayToWitnessPublicKeyHash(Data& result) const { - if (bytes.size() == 22 && bytes[0] == OP_0 && bytes[1] == 0x14) { - result.clear(); - std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); - return true; + if (!isPayToWitnessPublicKeyHash()) { + return false; } - return false; + result.clear(); + std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); + return true; } bool Script::matchPayToWitnessScriptHash(Data& result) const { - if (bytes.size() == 34 && bytes[0] == OP_0 && bytes[1] == 0x20) { - result.clear(); - std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); - return true; - } - return false; -} - -/// Decodes a small integer -static inline int decodeNumber(uint8_t opcode) { - if (opcode == OP_0) { - return 0; + if (!isPayToWitnessScriptHash()) { + return false; } - assert(opcode >= OP_1 && opcode <= OP_16); - return static_cast(opcode) - static_cast(OP_1 - 1); + result.clear(); + std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); + return true; } bool Script::matchMultisig(std::vector& keys, int& required) const { @@ -185,7 +181,7 @@ bool Script::getScriptOp(size_t& index, uint8_t& opcode, Data& operand) const { if (bytes.size() - index < 1) { return false; } - size = index; + size = bytes[index]; index += 1; } else if (opcode == OP_PUSHDATA2) { if (bytes.size() - index < 2) { @@ -237,14 +233,17 @@ Script Script::buildPayToWitnessProgram(const Data& program) { script.bytes.push_back(OP_0); script.bytes.push_back(static_cast(program.size())); script.bytes.insert(script.bytes.end(), program.begin(), program.end()); + assert(script.bytes.size() == 22 || script.bytes.size() == 34); return script; } -Script Script::buildPayToWitnessPubkeyHash(const Data& hash) { +Script Script::buildPayToWitnessPublicKeyHash(const Data& hash) { + assert(hash.size() == 20); return Script::buildPayToWitnessProgram(hash); } Script Script::buildPayToWitnessScriptHash(const Data& scriptHash) { + assert(scriptHash.size() == 32); return Script::buildPayToWitnessProgram(scriptHash); } @@ -253,7 +252,7 @@ void Script::encode(Data& data) const { std::copy(std::begin(bytes), std::end(bytes), std::back_inserter(data)); } -Script Script::buildForAddress(const std::string& string, enum TWCoinType coin) { +Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType coin) { if (Address::isValid(string)) { auto address = Address(string); auto p2pkh = TW::p2pkhPrefix(coin); @@ -279,7 +278,7 @@ Script Script::buildForAddress(const std::string& string, enum TWCoinType coin) } else if (CashAddress::isValid(string)) { auto address = CashAddress(string); auto bitcoinAddress = address.legacyAddress(); - return buildForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); + 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)) { diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index f6b5de13427..06db939ccce 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -13,6 +13,7 @@ #include #include +#include namespace TW::Bitcoin { @@ -29,7 +30,7 @@ class Script { Script(It begin, It end) : bytes(begin, end) {} /// Initializaes a script with a collection of raw bytes by moving. - explicit Script(Data&& bytes) : bytes(bytes) {} + explicit Script(const Data& bytes) : bytes(bytes) {} /// Whether the script is empty. bool empty() const { return bytes.empty(); } @@ -43,14 +44,17 @@ class Script { /// 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. bool isWitnessProgram() const; /// Matches the script to a pay-to-public-key (P2PK) script. - bool matchPayToPubkey(Data& publicKey) const; + bool matchPayToPublicKey(Data& publicKey) const; /// Matches the script to a pay-to-public-key-hash (P2PKH). - bool matchPayToPubkeyHash(Data& keyHash) const; + bool matchPayToPublicKeyHash(Data& keyHash) const; /// Matches the script to a pay-to-script-hash (P2SH). bool matchPayToScriptHash(Data& scriptHash) const; @@ -58,7 +62,7 @@ class Script { /// Matches the script to a pay-to-witness-public-key-hash (P2WPKH). bool matchPayToWitnessPublicKeyHash(Data& keyHash) const; - /// Matches the script to a pay-to-witness-script-hash (P2WSH). + /// Matches the script to a pay-to-witness-script-hash (P2WSH). Returns the script hash, a SHA256 of the witness script. bool matchPayToWitnessScriptHash(Data& scriptHash) const; /// Matches the script to a multisig script. @@ -75,20 +79,20 @@ class Script { /// Builds a pay-to-witness-public-key-hash (P2WPKH) script from a public /// key hash. - static Script buildPayToWitnessPubkeyHash(const Data& hash); + static Script buildPayToWitnessPublicKeyHash(const Data& hash); /// Builds a pay-to-witness-script-hash (P2WSH) script from a script hash. static Script buildPayToWitnessScriptHash(const Data& scriptHash); - /// Builds a pay-to-public-key-hash (P2PKH) script appropriate for the given + /// Builds a appropriate lock script for the given /// address. - static Script buildForAddress(const std::string& address, enum TWCoinType coin); + static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin); /// Encodes the script. void encode(Data& data) const; /// Encodes a small integer - static uint8_t encodeNumber(int n) { + static inline uint8_t encodeNumber(int n) { assert(n >= 0 && n <= 16); if (n == 0) { return OP_0; @@ -96,8 +100,17 @@ class Script { return OP_1 + uint8_t(n - 1); } - private: + /// Decodes a small integer + static inline int decodeNumber(uint8_t opcode) { + if (opcode == OP_0) { + return 0; + } + assert(opcode >= OP_1 && opcode <= OP_16); + return static_cast(opcode) - static_cast(OP_1 - 1); + } + /// Extracts a single opcode at the given index including its operand. + /// Public for testability. /// /// \param index [in/out] index where the operation starts, on return the /// index of the next operation. \param opcode [out] the opcode. \param diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h new file mode 100644 index 00000000000..b41c1bf88dd --- /dev/null +++ b/src/Bitcoin/SigHashType.h @@ -0,0 +1,35 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +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; + +// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +inline uint32_t hashTypeForCoin(enum TWCoinType coinType) { + // set fork hash type for BCH + switch (coinType) { + case TWCoinTypeBitcoinCash: + return (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork; + case TWCoinTypeBitcoinGold: + return (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG; + default: + return TWBitcoinSigHashTypeAll; + } +} + +inline bool hashTypeIsSingle(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeSingle; } + +inline bool hashTypeIsNone(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeNone; } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SignatureVersion.h b/src/Bitcoin/SignatureVersion.h index 87669836277..fa3363eec3f 100644 --- a/src/Bitcoin/SignatureVersion.h +++ b/src/Bitcoin/SignatureVersion.h @@ -6,8 +6,6 @@ #pragma once -#include - namespace TW::Bitcoin { enum SignatureVersion { BASE, diff --git a/src/Bitcoin/Signer.cpp b/src/Bitcoin/Signer.cpp index 64a9a41d109..c9ac3119030 100644 --- a/src/Bitcoin/Signer.cpp +++ b/src/Bitcoin/Signer.cpp @@ -32,14 +32,13 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { *output.mutable_transaction() = tx.proto(); Data encoded; - auto hasWitness = std::any_of(tx.inputs.begin(), tx.inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); - tx.encode(hasWitness, encoded); + signer.encodeTx(tx, encoded); output.set_encoded(encoded.data(), encoded.size()); Data txHashData = encoded; - if (hasWitness) { + if (tx.hasWitness()) { txHashData.clear(); - tx.encode(false, txHashData); + tx.encode(txHashData, Transaction::SegwitFormatMode::NonSegwit); } auto txHash = Hash::sha256d(txHashData.data(), txHashData.size()); std::reverse(txHash.begin(), txHash.end()); diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index 47d1071a313..bfcf6d32151 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -6,6 +6,7 @@ #include "SegwitAddress.h" #include "Transaction.h" +#include "SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -13,13 +14,14 @@ #include +using namespace TW; using namespace TW::Bitcoin; -std::vector Transaction::getPreImage(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, uint64_t amount) const { +Data Transaction::getPreImage(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const { assert(index < inputs.size()); - auto data = std::vector{}; + Data data; // Version encode32LE(version, data); @@ -34,7 +36,7 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i // Input nSequence (none/all, depending on flags) if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && - !TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + !hashTypeIsSingle(hashType) && !hashTypeIsNone(hashType)) { auto hashSequence = getSequenceHash(); std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); } else { @@ -51,11 +53,11 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i encode32LE(inputs[index].sequence, data); // Outputs (none/one/all, depending on flags) - if (!TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + if (!hashTypeIsSingle(hashType) && !hashTypeIsNone(hashType)) { auto hashOutputs = getOutputsHash(); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); - } else if (TWBitcoinSigHashTypeIsSingle(hashType) && index < outputs.size()) { - auto outputData = std::vector{}; + } else if (hashTypeIsSingle(hashType) && index < outputs.size()) { + Data outputData; outputs[index].encode(outputData); auto hashOutputs = TW::Hash::hash(hasher, outputData); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); @@ -72,8 +74,8 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i return data; } -std::vector Transaction::getPrevoutHash() const { - auto data = std::vector{}; +Data Transaction::getPrevoutHash() const { + Data data; for (auto& input : inputs) { auto& outpoint = reinterpret_cast(input.previousOutput); outpoint.encode(data); @@ -82,8 +84,8 @@ std::vector Transaction::getPrevoutHash() const { return hash; } -std::vector Transaction::getSequenceHash() const { - auto data = std::vector{}; +Data Transaction::getSequenceHash() const { + Data data; for (auto& input : inputs) { encode32LE(input.sequence, data); } @@ -91,8 +93,8 @@ std::vector Transaction::getSequenceHash() const { return hash; } -std::vector Transaction::getOutputsHash() const { - auto data = std::vector{}; +Data Transaction::getOutputsHash() const { + Data data; for (auto& output : outputs) { output.encode(data); } @@ -100,37 +102,54 @@ std::vector Transaction::getOutputsHash() const { return hash; } -void Transaction::encode(bool witness, std::vector& data) const { +void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { + bool useWitnessFormat = true; + switch (segwitFormat) { + case NonSegwit: useWitnessFormat = false; break; + case IfHasWitness: useWitnessFormat = hasWitness(); break; + case Segwit: useWitnessFormat = true; break; + } + encode32LE(version, data); - if (witness) { + if (useWitnessFormat) { // Use extended format in case witnesses are to be serialized. - data.push_back(0); - data.push_back(1); + 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 (witness) { - for (auto& input : inputs) { - input.encodeWitness(data); - } + if (useWitnessFormat) { + encodeWitness(data); } - encode32LE(lockTime, data); + encode32LE(lockTime, data); // nLockTime +} + +void Transaction::encodeWitness(Data& data) const { + for (auto& input : inputs) { + input.encodeWitness(data); + } +} + +bool Transaction::hasWitness() const { + return std::any_of(inputs.begin(), inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); } -std::vector Transaction::getSignatureHash(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, uint64_t amount, - enum SignatureVersion version) const { +Data Transaction::getSignatureHash(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount, + enum SignatureVersion version) const { switch (version) { case BASE: return getSignatureHashBase(scriptCode, index, hashType); @@ -140,20 +159,20 @@ std::vector Transaction::getSignatureHash(const Script& scriptCode, siz } /// Generates the signature hash for Witness version 0 scripts. -std::vector Transaction::getSignatureHashWitnessV0(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, - uint64_t amount) const { +Data Transaction::getSignatureHashWitnessV0(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, + uint64_t amount) const { auto preimage = getPreImage(scriptCode, index, hashType, amount); auto hash = TW::Hash::hash(hasher, preimage); return hash; } /// Generates the signature hash for for scripts other than witness scripts. -std::vector Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType) const { +Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const { assert(index < inputs.size()); - auto data = std::vector{}; + Data data; encode32LE(version, data); @@ -164,8 +183,8 @@ std::vector Transaction::getSignatureHashBase(const Script& scriptCode, serializeInput(subindex, scriptCode, index, hashType, data); } - auto hashNone = (hashType & 0x1f) == TWBitcoinSigHashTypeNone; - auto hashSingle = (hashType & 0x1f) == TWBitcoinSigHashTypeSingle; + auto hashNone = hashTypeIsNone(hashType); + auto hashSingle = hashTypeIsSingle(hashType); auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); encodeVarInt(serializedOutputCount, data); for (auto subindex = 0; subindex < serializedOutputCount; subindex += 1) { @@ -188,7 +207,7 @@ std::vector Transaction::getSignatureHashBase(const Script& scriptCode, } void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, std::vector& data) const { + enum TWBitcoinSigHashType hashType, Data& data) const { // In case of SIGHASH_ANYONECANPAY, only the input being signed is // serialized if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0) { @@ -205,8 +224,8 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size } // Serialize the nSequence - auto hashNone = (hashType & 0x1f) == TWBitcoinSigHashTypeNone; - auto hashSingle = (hashType & 0x1f) == TWBitcoinSigHashTypeSingle; + auto hashNone = hashTypeIsNone(hashType); + auto hashSingle = hashTypeIsSingle(hashType); if (subindex != index && (hashSingle || hashNone)) { encode32LE(0, data); } else { diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index 19d27b93ddd..5da4a7faade 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/Transaction.h @@ -6,10 +6,12 @@ #pragma once +#include #include "Script.h" #include "TransactionInput.h" #include "TransactionOutput.h" #include "../Hash.h" +#include "../Data.h" #include "SignatureVersion.h" #include @@ -17,6 +19,7 @@ namespace TW::Bitcoin { struct Transaction { +public: /// Transaction data format version (note, this is signed) int32_t version = 1; @@ -40,6 +43,10 @@ struct Transaction { TW::Hash::Hasher hasher = TW::Hash::sha256d; + /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) + int previousEstimatedVirtualSize = 0; + +public: Transaction() = default; Transaction(int32_t version, uint32_t lockTime, TW::Hash::Hasher hasher = TW::Hash::sha256d) @@ -49,33 +56,45 @@ struct Transaction { bool empty() const { return inputs.empty() && outputs.empty(); } /// Generates the signature pre-image. - std::vector getPreImage(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, - uint64_t amount) const; - std::vector getPrevoutHash() const; - std::vector getSequenceHash() const; - std::vector getOutputsHash() const; + Data getPreImage(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const; + Data getPrevoutHash() const; + Data getSequenceHash() const; + Data getOutputsHash() const; + + enum SegwitFormatMode { + NonSegwit, + IfHasWitness, + Segwit + }; /// Encodes the transaction into the provided buffer. - void encode(bool witness, std::vector& data) const; + 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); } + + /// Encodes the witness part of the transaction into the provided buffer. + void encodeWitness(Data& data) const; + + bool hasWitness() const; /// Generates the signature hash for this transaction. - std::vector getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, - uint64_t amount, enum SignatureVersion version) const; + Data getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, + uint64_t amount, enum SignatureVersion version) const; - void serializeInput(size_t subindex, const Script&, size_t index, enum TWBitcoinSigHashType hashType, - std::vector& data) const; + void serializeInput(size_t subindex, const Script&, size_t index, enum TWBitcoinSigHashType hashType, Data& data) const; /// Converts to Protobuf model Proto::Transaction proto() const; - private: +private: /// Generates the signature hash for Witness version 0 scripts. - std::vector getSignatureHashWitnessV0(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, uint64_t amount) const; + Data getSignatureHashWitnessV0(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const; /// Generates the signature hash for for scripts other than witness scripts. - std::vector getSignatureHashBase(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType) const; + Data getSignatureHashBase(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const; }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp new file mode 100644 index 00000000000..e4ac68fcfdc --- /dev/null +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -0,0 +1,119 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TransactionBuilder.h" +#include "TransactionSigner.h" + +#include "../Coin.h" + +#include +#include + +namespace TW::Bitcoin { + +/// Estimate encoded size by simple formula +int64_t estimateSimpleFee(FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const Bitcoin::Proto::SigningInput& input) { + return feeCalculator.calculate(plan.utxos.size(), outputSize, input.byte_fee()); +} + +/// Estimate encoded size by invoking sign(sizeOnly), get actual size +int64_t estimateSegwitFee(FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const Bitcoin::Proto::SigningInput& input) { + TWPurpose coinPurpose = TW::purpose(static_cast(input.coin_type())); + if (coinPurpose != TWPurposeBIP84) { + // not segwit, return default simple estimate + return estimateSimpleFee(feeCalculator, plan, outputSize, input); + } + + // duplicate input, with the current plan + auto inputWithPlan = std::move(input); + *inputWithPlan.mutable_plan() = plan.proto(); + + auto signer = TransactionSigner(std::move(inputWithPlan), true); + auto result = signer.sign(); + if (!result) { + // signing failed; return default simple estimate + return estimateSimpleFee(feeCalculator, plan, outputSize, input); + } + + // Obtain the encoded size + auto transaction = result.payload(); + Data dataNonSegwit; + transaction.encode(dataNonSegwit, Transaction::SegwitFormatMode::NonSegwit); + int64_t sizeNonSegwit = dataNonSegwit.size(); + uint64_t vSize = 0; + // Check if there is segwit + if (!transaction.hasWitness()) { + // no segwit, virtual size is defined as non-segwit size + vSize = sizeNonSegwit; + } else { + Data dataWitness; + transaction.encodeWitness(dataWitness); + int64_t witnessSize = 2 + dataWitness.size(); + // compute virtual size: (smaller) non-segwit + 1/4 of the diff (witness-only) + // (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; + + return fee; +} + +TransactionPlan TransactionBuilder::plan(const Bitcoin::Proto::SigningInput& input) { + auto plan = TransactionPlan(); + plan.amount = input.amount(); + + auto output_size = 2; + auto& feeCalculator = getFeeCalculator(static_cast(input.coin_type())); + auto unspentSelector = UnspentSelector(feeCalculator); + + // select UTXOs + if (!input.use_max_amount()) { + output_size = 2; // output + change + plan.utxos = unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); + } else { + output_size = 1; // no change + plan.utxos = unspentSelector.selectMaxAmount(input.utxo(), input.byte_fee()); + } + // Note: if utxos.size() == 0, all fields will be computed to 0 + plan.availableAmount = UnspentSelector::sum(plan.utxos); + + // Compute fee. + // must preliminary set change so that there is a second output + if (!input.use_max_amount()) { + plan.amount = input.amount(); + plan.fee = 0; + plan.change = plan.availableAmount - plan.amount; + } 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) + plan.fee = std::min(plan.availableAmount, plan.fee); + assert(plan.fee >= 0 && plan.fee <= plan.availableAmount); + + // adjust/compute amount + if (!input.use_max_amount()) { + // reduce amount if needed + plan.amount = std::max(Amount(0), std::min(plan.amount, plan.availableAmount - plan.fee)); + } else { + // max available amount + plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); + } + assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); + + // compute change + plan.change = plan.availableAmount - plan.amount - plan.fee; + assert(plan.change >= 0 && plan.change <= plan.availableAmount); + assert(!input.use_max_amount() || plan.change == 0); // change is 0 in max amount case + + assert(plan.amount + plan.change + plan.fee == plan.availableAmount); + + return plan; +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionBuilder.h b/src/Bitcoin/TransactionBuilder.h index dcdd0e4335d..a4221a65251 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -19,53 +19,13 @@ namespace TW::Bitcoin { class TransactionBuilder { public: /// Plans a transaction by selecting UTXOs and calculating fees. - static TransactionPlan plan(const Bitcoin::Proto::SigningInput& input) { - auto plan = TransactionPlan(); - plan.amount = input.amount(); - - auto output_size = 2; - auto calculator = - UnspentCalculator::getCalculator(static_cast(input.coin_type())); - auto unspentSelector = UnspentSelector(calculator); - if (input.use_max_amount() && UnspentSelector::sum(input.utxo()) == plan.amount) { - output_size = 1; - Amount newAmount = 0; - auto input_size = 0; - - for (auto utxo : input.utxo()) { - if (utxo.amount() > - unspentSelector.calculator.calculateSingleInput(input.byte_fee())) { - input_size++; - newAmount += utxo.amount(); - } - } - - plan.amount = newAmount - unspentSelector.calculator.calculate(input_size, output_size, - input.byte_fee()); - plan.amount = std::max(Amount(0), plan.amount); - } - - plan.utxos = - unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); - plan.fee = - unspentSelector.calculator.calculate(plan.utxos.size(), output_size, input.byte_fee()); - - plan.availableAmount = UnspentSelector::sum(plan.utxos); - - if (plan.amount > plan.availableAmount - plan.fee) { - plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); - } - - plan.change = plan.availableAmount - plan.amount - plan.fee; - - return plan; - } + static TransactionPlan plan(const Bitcoin::Proto::SigningInput& input); /// Builds a transaction by selecting UTXOs and calculating fees. template static Transaction build(const TransactionPlan& plan, const std::string& toAddress, const std::string& changeAddress, enum TWCoinType coin) { - auto lockingScriptTo = Script::buildForAddress(toAddress, coin); + auto lockingScriptTo = Script::lockScriptForAddress(toAddress, coin); if (lockingScriptTo.empty()) { return {}; } @@ -74,7 +34,7 @@ class TransactionBuilder { tx.outputs.push_back(TransactionOutput(plan.amount, lockingScriptTo)); if (plan.change > 0) { - auto lockingScriptChange = Script::buildForAddress(changeAddress, coin); + auto lockingScriptChange = Script::lockScriptForAddress(changeAddress, coin); tx.outputs.push_back(TransactionOutput(plan.change, lockingScriptChange)); } diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 6260242854d..e2533807ba8 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -9,6 +9,7 @@ #include "TransactionInput.h" #include "TransactionOutput.h" #include "UnspentSelector.h" +#include "SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -21,38 +22,48 @@ using namespace TW::Bitcoin; template Result TransactionSigner::sign() { + if (transaction.inputs.size() == 0 || plan.utxos.size() == 0) { + return Result::failure("Missing inputs or UTXOs"); + } + signedInputs.clear(); std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); - const bool hashSingle = - ((input.hash_type() & ~TWBitcoinSigHashTypeAnyoneCanPay) == TWBitcoinSigHashTypeSingle); - for (auto i = 0; i < plan.utxos.size(); i += 1) { - auto& utxo = plan.utxos[i]; - + 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()); - auto result = sign(script, i, utxo); - if (!result) { - return Result::failure(result.error()); + 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; - std::vector witnessStack; uint32_t signatureVersion = [this]() { if ((input.hash_type() & TWBitcoinSigHashTypeFork) != 0) { @@ -62,15 +73,15 @@ Result TransactionSigner::sign(Script scr } }(); auto result = signStep(script, index, utxo, signatureVersion); - if (result) { - results = result.payload(); - } else { + 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.front().begin(), results.front().end()); + script = Script(results[0]); auto result = signStep(script, index, utxo, signatureVersion); if (!result) { return Result::failure(result.error()); @@ -80,26 +91,24 @@ Result TransactionSigner::sign(Script scr 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) { - witnessStack = result.payload(); - } else { - witnessStack.clear(); + if (!result) { + return Result::failure(result.error()); } + witnessStack = result.payload(); results.clear(); } else if (script.matchPayToWitnessScriptHash(data)) { - auto witnessScript = Script(results[0].begin(), results[0].end()); + auto witnessScript = Script(results[0]); auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (result) { - witnessStack = result.payload(); - } else { - witnessStack.clear(); + 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. @@ -118,7 +127,7 @@ Result TransactionSigner::sign(Script scr template Result> TransactionSigner::signStep( - Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo, uint32_t version) { + Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo, uint32_t version) const { Transaction transactionToSign(transaction); transactionToSign.inputs = signedInputs; transactionToSign.outputs = transaction.outputs; @@ -134,7 +143,8 @@ Result> TransactionSigner::si return Result>::failure("Missing redeem script."); } return Result>::success({redeemScript}); - } else if (script.matchPayToWitnessScriptHash(data)) { + } + if (script.matchPayToWitnessScriptHash(data)) { auto scripthash = TW::Hash::ripemd(data); auto redeemScript = scriptForScriptHash(scripthash); if (redeemScript.empty()) { @@ -142,12 +152,15 @@ Result> TransactionSigner::si return Result>::failure("Missing redeem script."); } return Result>::success({redeemScript}); - } else if (script.matchPayToWitnessPublicKeyHash(data)) { + } + if (script.matchPayToWitnessPublicKeyHash(data)) { return Result>::success({data}); - } else if (script.isWitnessProgram()) { + } + if (script.isWitnessProgram()) { // Error: Invalid sutput script return Result>::failure("Invalid output script."); - } else if (script.matchMultisig(keys, required)) { + } + if (script.matchMultisig(keys, required)) { auto results = std::vector{{}}; // workaround CHECKMULTISIG bug for (auto& pubKey : keys) { if (results.size() >= required + 1) { @@ -155,7 +168,7 @@ Result> TransactionSigner::si } auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(pubKey)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && !estimationMode) { // Error: missing key return Result>::failure("Missing private key."); } @@ -169,10 +182,11 @@ Result> TransactionSigner::si } results.resize(required + 1); return Result>::success(std::move(results)); - } else if (script.matchPayToPubkey(data)) { + } + if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(data)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && !estimationMode) { // Error: Missing key return Result>::failure("Missing private key."); } @@ -183,46 +197,53 @@ Result> TransactionSigner::si return Result>::failure("Failed to sign."); } return Result>::success({signature}); - } else if (script.matchPayToPubkeyHash(data)) { + } + if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); - if (key.empty()) { - // Error: Missing keyxs + if (key.empty() && !estimationMode) { + // Error: Missing keys return Result>::failure("Missing private key."); } - auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); auto signature = createSignature(transactionToSign, script, key, index, utxo.amount(), version); if (signature.empty()) { // Error: Failed to sign return Result>::failure("Failed to sign."); } + if (key.empty() && estimationMode) { + // estimation mode, key is missing: use placeholder for public key + return Result>::success({signature, Data(PublicKey::secp256k1Size)}); + } + auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); return Result>::success({signature, pubkey.bytes}); - } else { - // Error: Invalid output script - return Result>::failure("Invalid output script."); } + // Error: Invalid output script + return Result>::failure("Invalid output script."); } template Data TransactionSigner::createSignature(const Transaction& transaction, const Script& script, const Data& key, size_t index, Amount amount, - uint32_t version) { - auto sighash = transaction.getSignatureHash(script, index, static_cast(input.hash_type()), 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(Data(begin(sighash), end(sighash)), TWCurveSECP256k1); - if (sig.empty()) { - return {}; + auto sig = pk.signAsDER(sighash, TWCurveSECP256k1); + if (!sig.empty()) { + sig.push_back(static_cast(input.hash_type())); } - sig.push_back(static_cast(input.hash_type())); return sig; } template Data TransactionSigner::pushAll(const std::vector& results) { - auto data = Data{}; + Data data; for (auto& result : results) { if (result.empty()) { data.push_back(OP_0); @@ -250,7 +271,7 @@ Data TransactionSigner::keyForPublicKeyHash(con for (auto& key : input.private_key()) { auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(publicKey.bytes)); - if (std::equal(std::begin(keyHash), std::end(keyHash), std::begin(hash), std::end(hash))) { + if (keyHash == hash) { return Data(key.begin(), key.end()); } } @@ -259,7 +280,7 @@ Data TransactionSigner::keyForPublicKeyHash(con template Data TransactionSigner::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 diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index 972bca1eb46..a7969eb7236 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -43,9 +43,13 @@ class TransactionSigner { /// List of signed inputs. std::vector signedInputs; + bool estimationMode = false; + public: /// Initializes a transaction signer with signing input. - TransactionSigner(const Bitcoin::Proto::SigningInput& input) : input(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 { @@ -61,13 +65,18 @@ class TransactionSigner { /// \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> signStep(Script script, size_t index, - const Proto::UnspentTransaction& utxo, uint32_t version); + 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); - Data pushAll(const std::vector& results); + 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; diff --git a/src/Bitcoin/UnspentCalculator.cpp b/src/Bitcoin/UnspentCalculator.cpp deleted file mode 100644 index c998a5c7b0f..00000000000 --- a/src/Bitcoin/UnspentCalculator.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "UnspentCalculator.h" - -using namespace TW; -using namespace TW::Bitcoin; - -UnspentCalculator UnspentCalculator::getCalculator(TWCoinType coinType) { - switch (coinType) { - case TWCoinTypeZelcash: - case TWCoinTypeZcash: { - auto calc = [](size_t inputs, size_t outputs, int64_t byteFee) -> int64_t { return 10000; }; - auto calcInput = [](int64_t byteFee) -> int64_t { return 0; }; - return UnspentCalculator(calc, calcInput); - } - case TWCoinTypeGroestlcoin: { - auto calc = [](size_t inputs, size_t outputs, int64_t byteFee) -> int64_t { return 20000; }; - auto calcInput = [](int64_t byteFee) -> int64_t { return 0; }; - return UnspentCalculator(calc, calcInput); - } - case TWCoinTypeDecred: { - auto calc = [](size_t inputs, size_t outputs, int64_t byteFee) -> int64_t { - const auto txsize = ((166 * inputs) + (38 * outputs) + 12); - return int64_t(txsize) * byteFee; - }; - auto calcInput = [](int64_t byteFee) -> int64_t { - return int64_t(166) * byteFee; - }; - return UnspentCalculator(calc, calcInput); - } - default: - return UnspentCalculator(); - } -} - -int64_t UnspentCalculator::calculateFee(int64_t inputs, int64_t outputs, int64_t byteFee) { - const auto txsize = ((148 * inputs) + (34 * outputs) + 10); - return int64_t(txsize) * byteFee; -} - -int64_t UnspentCalculator::calculateSingleInputFee(int64_t byteFee) { - return int64_t(148) * byteFee; -} diff --git a/src/Bitcoin/UnspentCalculator.h b/src/Bitcoin/UnspentCalculator.h deleted file mode 100644 index ed90c28996e..00000000000 --- a/src/Bitcoin/UnspentCalculator.h +++ /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. - -#pragma once - -#include -#include - -namespace TW::Bitcoin { - -using FeeCalculator = std::function; -using SingleInputFeeCalculator = std::function; - -class UnspentCalculator { - public: - static UnspentCalculator getCalculator(TWCoinType coinType); - - FeeCalculator calculate; - SingleInputFeeCalculator calculateSingleInput; - - UnspentCalculator() - : calculate(UnspentCalculator::calculateFee) - , calculateSingleInput(UnspentCalculator::calculateSingleInputFee) {} - UnspentCalculator(FeeCalculator calculateFee, - SingleInputFeeCalculator calculateSingleInputFee) - : calculate(std::move(calculateFee)), calculateSingleInput(std::move(calculateSingleInputFee)) {} - - private: - static int64_t calculateFee(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1); - static int64_t calculateSingleInputFee(int64_t byteFee); -}; - -} // namespace TW::Bitcoin diff --git a/src/Bitcoin/UnspentSelector.cpp b/src/Bitcoin/UnspentSelector.cpp index e87e0f32047..d14c8cd0efb 100644 --- a/src/Bitcoin/UnspentSelector.cpp +++ b/src/Bitcoin/UnspentSelector.cpp @@ -7,6 +7,7 @@ #include "UnspentSelector.h" #include +#include using namespace TW; using namespace TW::Bitcoin; @@ -20,12 +21,13 @@ struct Selection { }; // Filters utxos that are dust +template std::vector -UnspentSelector::filterDustInput(std::vector selectedUtxos, - int64_t byteFee) { +UnspentSelector::filterDustInput(const T& selectedUtxos, int64_t byteFee) { + auto inputFeeLimit = feeCalculator.calculateSingleInput(byteFee); std::vector filteredUtxos; - for (auto utxo : selectedUtxos) { - if (utxo.amount() > calculator.calculateSingleInput(byteFee)) { + for (auto utxo: selectedUtxos) { + if (utxo.amount() > inputFeeLimit) { filteredUtxos.push_back(utxo); } } @@ -53,15 +55,16 @@ static inline auto slice(const T& elements, size_t sliceSize) { template std::vector UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs) { - // if target value is zero, fee is zero + // if target value is zero, no UTXOs are needed if (targetValue == 0) { return {}; } // total values of utxos should be greater than targetValue - if (sum(utxos) < targetValue || utxos.empty()) { + if (utxos.empty() || sum(utxos) < targetValue) { return {}; } + assert(utxos.size() >= 1); // definitions for the following caluculation const auto doubleTargetValue = targetValue * 2; @@ -82,12 +85,12 @@ UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, in return doubleTargetValue - val; }; - // 1. Find a combination of the fewest outputs that is + // 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 <= sortedUtxos.size(); numInputs += 1) { - const auto fee = calculator.calculate(numInputs, numOutputs, byteFee); + const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); const auto targetWithFeeAndDust = targetValue + fee + dustThreshold; auto slices = slice(sortedUtxos, static_cast(numInputs)); slices.erase(std::remove_if(slices.begin(), slices.end(), @@ -106,10 +109,9 @@ UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, in } } - // 2. If not, find a combination of outputs that may produce dust change. - numOutputs = 1; + // 2. If not, find a valid combination of outputs even if they produce dust change. for (int64_t numInputs = 1; numInputs <= sortedUtxos.size(); numInputs += 1) { - const auto fee = calculator.calculate(numInputs, numOutputs, byteFee); + const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); const auto targetWithFee = targetValue + fee; auto slices = slice(sortedUtxos, static_cast(numInputs)); slices.erase( @@ -126,9 +128,13 @@ UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, in return {}; } -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 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 index a7c80873306..60f43ebe79b 100644 --- a/src/Bitcoin/UnspentSelector.h +++ b/src/Bitcoin/UnspentSelector.h @@ -9,7 +9,8 @@ #include #include -#include "UnspentCalculator.h" +#include "FeeCalculator.h" +#include #include "../proto/Bitcoin.pb.h" namespace TW::Bitcoin { @@ -27,23 +28,27 @@ class UnspentSelector { std::vector select(const T& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs = 2); - UnspentCalculator calculator; + /// 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); - UnspentSelector() : calculator(UnspentCalculator()) {} - explicit UnspentSelector(UnspentCalculator calculator) : calculator(std::move(calculator)) {} + /// Construct, using provided feeCalculator (see getFeeCalculator()). + explicit UnspentSelector(FeeCalculator& feeCalculator) : feeCalculator(feeCalculator) {} + UnspentSelector() : UnspentSelector(getFeeCalculator(TWCoinTypeBitcoin)) {} - public: template static inline int64_t sum(const T& utxos) { int64_t sum = 0; - for (auto& utxo : utxos) + for (auto& utxo : utxos) { sum += utxo.amount(); + } return sum; } private: - std::vector - filterDustInput(std::vector selectedUtxos, int64_t byteFee); + FeeCalculator& feeCalculator; + template std::vector filterDustInput(const T& selectedUtxos, int64_t byteFee); }; } // namespace TW::Bitcoin diff --git a/src/Cardano/AddressV3.h b/src/Cardano/AddressV3.h index 2c9655041be..2097396523d 100644 --- a/src/Cardano/AddressV3.h +++ b/src/Cardano/AddressV3.h @@ -86,7 +86,7 @@ class AddressV3 { Data data() const; private: - AddressV3() : legacyAddressV2(nullptr) {} + AddressV3() : discrimination(Discrim_Production), kind(Kind_Single), legacyAddressV2(nullptr) {} }; inline bool operator==(const AddressV3& lhs, const AddressV3& rhs) { diff --git a/src/Cbor.cpp b/src/Cbor.cpp index ae2fccf1ab8..a22e8213469 100644 --- a/src/Cbor.cpp +++ b/src/Cbor.cpp @@ -162,8 +162,9 @@ Decode Decode::skipClone(uint32_t offset) const { Decode::TypeDesc Decode::getTypeDesc() const { TypeDesc typeDesc; typeDesc.isIndefiniteValue = false; - typeDesc.majorType = (MajorType)(byte(0) >> 5); - auto minorType = (TW::byte)((uint8_t)byte(0) & 0x1F); + typeDesc.majorType = (MajorType)(getByte(0) >> 5); + assert((int)typeDesc.majorType >= 0 && (int)typeDesc.majorType <= 7); + auto minorType = (TW::byte)((uint8_t)getByte(0) & 0x1F); if (minorType < 24) { // direct value typeDesc.byteCount = 1; @@ -172,31 +173,31 @@ Decode::TypeDesc Decode::getTypeDesc() const { } if (minorType == 24) { typeDesc.byteCount = 1 + 1; - typeDesc.value = byte(1); + typeDesc.value = getByte(1); return typeDesc; } if (minorType == 25) { typeDesc.byteCount = 1 + 2; - typeDesc.value = (uint16_t)(((uint16_t)byte(1) << 8) + (uint16_t)byte(2)); + typeDesc.value = (uint16_t)(((uint16_t)getByte(1) << 8) + (uint16_t)getByte(2)); return typeDesc; } if (minorType == 26) { typeDesc.byteCount = 1 + 4; - typeDesc.value = (uint32_t)(((uint32_t)byte(1) << 24) + ((uint32_t)byte(2) << 16) + ((uint32_t)byte(3) << 8) + (uint32_t)byte(4)); + typeDesc.value = (uint32_t)(((uint32_t)getByte(1) << 24) + ((uint32_t)getByte(2) << 16) + ((uint32_t)getByte(3) << 8) + (uint32_t)getByte(4)); return typeDesc; } if (minorType == 27) { typeDesc.byteCount = 1 + 8; typeDesc.value = (uint64_t)( - ((uint64_t)byte(1) << 56) + - ((uint64_t)byte(2) << 48) + - ((uint64_t)byte(3) << 40) + - ((uint64_t)byte(4) << 32) + - ((uint64_t)byte(5) << 24) + - ((uint64_t)byte(6) << 16) + - ((uint64_t)byte(7) << 8) + - ((uint64_t)byte(8))); + ((uint64_t)getByte(1) << 56) + + ((uint64_t)getByte(2) << 48) + + ((uint64_t)getByte(3) << 40) + + ((uint64_t)getByte(4) << 32) + + ((uint64_t)getByte(5) << 24) + + ((uint64_t)getByte(6) << 16) + + ((uint64_t)getByte(7) << 8) + + ((uint64_t)getByte(8))); return typeDesc; } if (minorType >= 28 && minorType <= 30) { @@ -225,13 +226,12 @@ uint32_t Decode::getTotalLen() const { return getCompoundLength(1); case MT_map: return getCompoundLength(2); + default: case MT_tag: { uint32_t dataLen = skipClone(typeDesc.byteCount).getTotalLen(); return typeDesc.byteCount + dataLen; } - default: - throw std::invalid_argument("CBOR length type not supported"); } } @@ -373,11 +373,9 @@ bool Decode::isValid() const { return true; } + default: case MT_tag: return skipClone(typeDesc.byteCount).isValid(); - - default: - return false; } } catch (exception& ex) { return false; @@ -440,6 +438,7 @@ string Decode::dumpToStringInternal() const { s << "tag " << typeDesc.value << " " << getTagElement().dumpToStringInternal(); break; + default: case MT_special: // float or simple if (typeDesc.isIndefiniteValue) { // skip break command @@ -447,9 +446,6 @@ string Decode::dumpToStringInternal() const { s << "spec " << typeDesc.value; } break; - - default: - throw std::invalid_argument("CBOR dump: type not supported"); } return s.str(); } diff --git a/src/Cbor.h b/src/Cbor.h index 653bd23bfb9..71de1adb2e0 100644 --- a/src/Cbor.h +++ b/src/Cbor.h @@ -118,7 +118,7 @@ class Decode { /// Skip ahead: form other Decode data with offset Decode skipClone(uint32_t offset) const; /// Get the Nth byte - inline TW::byte byte(uint32_t idx) const { + inline TW::byte getByte(uint32_t idx) const { if (subStart + idx >= data->origData.size()) { throw std::invalid_argument("CBOR data too short"); } return data->origData[subStart + idx]; } diff --git a/src/Coin.cpp b/src/Coin.cpp index c6135ddbec0..448b21edc02 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -12,6 +12,7 @@ #include #include +#include // Includes for entry points for coin implementations #include "Aeternity/Entry.h" @@ -51,6 +52,7 @@ #include "Waves/Entry.h" #include "Zcash/Entry.h" #include "Zilliqa/Entry.h" +#include "Elrond/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -58,6 +60,7 @@ using namespace std; // Map with coin entry dispatchers, key is coin type map dispatchMap = {}; +mutex dispatchMapMutex; // List of supported coint types set coinTypes = {}; @@ -100,8 +103,14 @@ void setupDispatchers() { new Waves::Entry(), new Zcash::Entry(), new Zilliqa::Entry(), + new Elrond::Entry(), }; // end_of_coin_entries_marker_do_not_modify + lock_guard guard(dispatchMapMutex); + if (dispatchMap.size() > 0) { + // already set up, skip + return; + } dispatchMap.clear(); coinTypes.clear(); for (auto d : dispatchers) { @@ -109,11 +118,12 @@ void setupDispatchers() { for (auto c : dispCoins) { assert(dispatchMap.find(c) == dispatchMap.end()); // each coin must appear only once dispatchMap[c] = d; - auto setResult = coinTypes.emplace(c); - assert(setResult.second == true); // each coin must appear only once + if (coinTypes.emplace(c).second != true) { + // each coin must appear only once + abort(); + }; } } - return; // Note: dispatchers are created at first use, and never freed } @@ -121,6 +131,8 @@ inline void setupDispatchersIfNeeded() { if (dispatchMap.size() == 0) { setupDispatchers(); } + assert(dispatchMap.size() > 0); + // it is set up by this time, and will not get modified } CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -147,7 +159,7 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { return dispatcher->validateAddress(coin, string, p2pkh, p2sh, hrp); } -std::string TW::normalizeAddress(TWCoinType coin, const std::string &address) { +std::string TW::normalizeAddress(TWCoinType coin, const std::string& address) { if (!TW::validateAddress(coin, address)) { // invalid address, not normalizing return ""; @@ -192,6 +204,18 @@ bool TW::supportsJSONSigning(TWCoinType coinType) { return dispatcher->supportsJSONSigning(); } +void TW::anyCoinEncode(TWCoinType coinType, const Data& dataIn, Data& dataOut) { + auto dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + dispatcher->encodeRawTx(coinType, dataIn, dataOut); +} + +void TW::anyCoinDecode(TWCoinType coinType, const Data& dataIn, Data& dataOut) { + auto dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + dispatcher->decodeRawTx(coinType, dataIn, dataOut); +} + void TW::anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut) { auto dispatcher = coinDispatcher(coinType); assert(dispatcher != nullptr); @@ -254,6 +278,10 @@ Hash::Hasher TW::base58Hasher(TWCoinType coin) { return getCoinInfo(coin).base58Hasher; } +uint32_t TW::slip44Id(TWCoinType coin) { + return getCoinInfo(coin).slip44; +} + TWString *_Nullable TWCoinTypeConfigurationGetSymbol(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).symbol); } diff --git a/src/Coin.h b/src/Coin.h index e5db19ba4ae..d48516a8203 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -80,10 +80,16 @@ enum TWHRP hrp(TWCoinType coin); // Note: use output parameter to avoid unneeded copies void anyCoinSign(TWCoinType coinType, const Data& dataIn, Data& dataOut); +uint32_t slip44Id(TWCoinType coin); + std::string anySignJSON(TWCoinType coinType, const std::string& json, const Data& key); bool supportsJSONSigning(TWCoinType coinType); +void anyCoinEncode(TWCoinType coinType, const Data& dataIn, Data& dataOut); + +void anyCoinDecode(TWCoinType coinType, const Data& dataIn, Data& dataOut); + void anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut); struct CoinInfo { @@ -106,6 +112,7 @@ struct CoinInfo { int decimals; const char* explorerTransactionUrl; const char* explorerAccountUrl; + uint32_t slip44; }; } // namespace TW diff --git a/src/CoinEntry.h b/src/CoinEntry.h index 6fefcf4fc49..e66c054d709 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -32,6 +32,9 @@ class CoinEntry { 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 ""; } + // Sign and encode broadcastable raw transaction + virtual void encodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const { return; } + virtual void decodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) 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; } @@ -48,6 +51,14 @@ void signTemplate(const Data& dataIn, Data& dataOut) { dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } +template +void encodeTemplate(const Data& dataIn, Data& dataOut) { + auto input = Input(); + input.ParseFromArray(dataIn.data(), (int)dataIn.size()); + auto encoded = Signer::sign(input).encoded(); + dataOut.insert(dataOut.end(), encoded.begin(), encoded.end()); +} + // Note: use output parameter to avoid unneeded copies template void planTemplate(const Data& dataIn, Data& dataOut) { diff --git a/src/Cosmos/Address.h b/src/Cosmos/Address.h index 67d91040c07..49bf640d5cb 100644 --- a/src/Cosmos/Address.h +++ b/src/Cosmos/Address.h @@ -20,7 +20,7 @@ class Address: public Bech32Address { Address() : Bech32Address("") {} /// Initializes an address with a key hash. - Address(const std::string& hrp, Data keyHash) : Bech32Address(hrp, keyHash) {} + 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) {} diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index 0eceffcdf3d..a625522a59c 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -19,6 +19,7 @@ class Entry: public CoinEntry { TWCoinTypeCosmos, TWCoinTypeKava, TWCoinTypeTerra, + TWCoinTypeBandChain, }; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; diff --git a/src/Decred/Entry.h b/src/Decred/Entry.h index 1ee2fbf40e6..56b67534e2c 100644 --- a/src/Decred/Entry.h +++ b/src/Decred/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual 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; - // normalizeAddress is not used 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; diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index 2a7b99ca0a3..0eb4e9b2a2e 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -8,6 +8,8 @@ #include "TransactionInput.h" #include "TransactionOutput.h" +#include "../Bitcoin/SigHashType.h" +#include "../Bitcoin/TransactionSigner.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -45,12 +47,18 @@ Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noe } Result Signer::sign() { + if (txPlan.utxos.size() == 0 || transaction.inputs.size() == 0) { + return Result::failure("Missing inputs or UTXOs"); + } + signedInputs.clear(); std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); - const bool hashSingle = - ((input.hash_type() & ~TWBitcoinSigHashTypeAnyoneCanPay) == TWBitcoinSigHashTypeSingle); + if (txPlan.utxos.size() == 0) { + return Result::failure("Plan without UTXOs"); + } + const auto hashSingle = Bitcoin::hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < txPlan.utxos.size(); i += 1) { auto& utxo = txPlan.utxos[i]; @@ -73,6 +81,8 @@ Result Signer::sign() { } Result Signer::sign(Bitcoin::Script script, size_t index) { + assert(index < transaction.inputs.size()); + Bitcoin::Script redeemScript; std::vector results; @@ -96,7 +106,7 @@ Result Signer::sign(Bitcoin::Script script, size_t index) { results.push_back(redeemScript.bytes); } - return Result::success(Bitcoin::Script(pushAll(results))); + return Result::success(Bitcoin::Script(Bitcoin::TransactionSigner::pushAll(results))); } Result> Signer::signStep(Bitcoin::Script script, size_t index) { @@ -108,7 +118,7 @@ Result> Signer::signStep(Bitcoin::Script script, size_t index) std::vector keys; int required; - if (script.matchPayToPubkey(data)) { + if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(data)); auto key = keyForPublicKeyHash(keyHash); if (key.empty()) { @@ -121,7 +131,7 @@ Result> Signer::signStep(Bitcoin::Script script, size_t index) return Result>::failure("Failed to sign."); } return Result>::success({signature}); - } else if (script.matchPayToPubkeyHash(data)) { + } else if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); if (key.empty()) { // Error: Missing keyxs @@ -182,30 +192,6 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri return signature; } -Data Signer::pushAll(const std::vector& results) { - auto 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(Bitcoin::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; -} - Data Signer::keyForPublicKeyHash(const Data& hash) const { for (auto& key : input.private_key()) { auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); diff --git a/src/Decred/Signer.h b/src/Decred/Signer.h index 4d01bf512c7..a6ee7da31b1 100644 --- a/src/Decred/Signer.h +++ b/src/Decred/Signer.h @@ -76,7 +76,6 @@ class Signer { Result> signStep(Bitcoin::Script script, size_t index); Data createSignature(const Transaction& transaction, const Bitcoin::Script& script, const Data& key, size_t index); - Data pushAll(const std::vector& results); /// 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 f1b746f474f..9be21fbe88f 100644 --- a/src/Decred/Transaction.cpp +++ b/src/Decred/Transaction.cpp @@ -6,6 +6,7 @@ #include "Transaction.h" +#include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -23,10 +24,6 @@ static const uint32_t sigHashSerializePrefix = 1; // Indicates the serialization only contains witness data. static const uint32_t sigHashSerializeWitness = 3; -// Defines the number of bits of the hash type which is used to identify which -// outputs are signed. -static const byte sigHashMask = 0x1f; - std::size_t sigHashWitnessSize(const std::vector& inputs, const Bitcoin::Script& signScript); } // namespace @@ -35,7 +32,7 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz enum TWBitcoinSigHashType hashType) const { assert(index < inputs.size()); - if (TWBitcoinSigHashTypeIsSingle(hashType) && index >= outputs.size()) { + if (Bitcoin::hashTypeIsSingle(hashType) && index >= outputs.size()) { throw std::invalid_argument("attempt to sign single input at index " "larger than the number of outputs"); } @@ -48,7 +45,7 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz } auto outputsToSign = outputs; - switch (hashType & sigHashMask) { + switch (hashType & Bitcoin::SigHashMask) { case TWBitcoinSigHashTypeNone: outputsToSign = {}; break; @@ -93,7 +90,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT input.previousOutput.encode(preimage); auto sequence = input.sequence; - if ((TWBitcoinSigHashTypeIsNone(hashType) || TWBitcoinSigHashTypeIsSingle(hashType)) && + if ((Bitcoin::hashTypeIsNone(hashType) || Bitcoin::hashTypeIsSingle(hashType)) && i != signIndex) { sequence = 0; } @@ -106,7 +103,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT auto& output = outputsToSign[i]; auto value = output.value; auto pkScript = output.script; - if (TWBitcoinSigHashTypeIsSingle(hashType) && i != index) { + if (Bitcoin::hashTypeIsSingle(hashType) && i != index) { value = -1; pkScript = {}; } diff --git a/src/Decred/Transaction.h b/src/Decred/Transaction.h index 69742710246..57aa1650ca0 100644 --- a/src/Decred/Transaction.h +++ b/src/Decred/Transaction.h @@ -6,6 +6,7 @@ #pragma once +#include #include "TransactionInput.h" #include "TransactionOutput.h" #include "Bitcoin/Script.h" diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index 3804d380049..9c612ac8288 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -9,7 +9,6 @@ #include "Transaction.h" #include "../Bitcoin/TransactionPlan.h" #include "../Bitcoin/TransactionBuilder.h" -#include "../Bitcoin/UnspentSelector.h" #include "../proto/Bitcoin.pb.h" #include "../proto/Decred.pb.h" @@ -29,7 +28,7 @@ struct TransactionBuilder { static Transaction build(const Bitcoin::TransactionPlan& plan, const std::string& toAddress, const std::string& changeAddress) { auto coin = TWCoinTypeDecred; - auto lockingScriptTo = Bitcoin::Script::buildForAddress(toAddress, coin); + auto lockingScriptTo = Bitcoin::Script::lockScriptForAddress(toAddress, coin); if (lockingScriptTo.empty()) { return {}; } @@ -38,7 +37,7 @@ struct TransactionBuilder { tx.outputs.emplace_back(TransactionOutput(plan.amount, /* version: */ 0, lockingScriptTo)); if (plan.change > 0) { - auto lockingScriptChange = Bitcoin::Script::buildForAddress(changeAddress, coin); + auto lockingScriptChange = Bitcoin::Script::lockScriptForAddress(changeAddress, coin); tx.outputs.emplace_back( TransactionOutput(plan.change, /* version: */ 0, lockingScriptChange)); } @@ -48,7 +47,6 @@ struct TransactionBuilder { auto input = TransactionInput(); input.previousOutput = utxo.out_point(); input.sequence = utxo.out_point().sequence(); - input.sequence = utxo.out_point().sequence(); tx.inputs.push_back(std::move(input)); } diff --git a/src/DerivationPath.cpp b/src/DerivationPath.cpp index 269a6a5c06a..e0f97ed8c71 100644 --- a/src/DerivationPath.cpp +++ b/src/DerivationPath.cpp @@ -24,7 +24,7 @@ DerivationPath::DerivationPath(const std::string& string) { while (it != end) { uint32_t value; - if (std::sscanf(it, "%d", &value) != 1) { + if (std::sscanf(it, "%ud", &value) != 1) { throw std::invalid_argument("Invalid component"); } while (it != end && isdigit(*it)) { diff --git a/src/DerivationPath.h b/src/DerivationPath.h index 9a254da1d0e..be6b6602d07 100644 --- a/src/DerivationPath.h +++ b/src/DerivationPath.h @@ -55,12 +55,12 @@ struct DerivationPath { indices[0] = DerivationPathIndex(v, /* hardened: */ true); } - TWCoinType coin() const { + uint32_t coin() const { if (indices.size() <= 1) { return TWCoinTypeBitcoin; } - return static_cast(indices[1].value); + return indices[1].value; } - void setCoin(TWCoinType v) { + void setCoin(uint32_t v) { if (indices.size() <= 1) { return; } indices[1] = DerivationPathIndex(v, /* hardened: */ true); } @@ -100,9 +100,9 @@ struct DerivationPath { explicit DerivationPath(std::vector indices) : indices(std::move(indices)) {} /// Creates a `DerivationPath` by BIP44 components. - DerivationPath(TWPurpose purpose, TWCoinType coin, uint32_t account, uint32_t change, - uint32_t address) { - indices = std::vector(5); + DerivationPath(TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, + uint32_t address) + : indices(std::vector(5)) { setPurpose(purpose); setCoin(coin); setAccount(account); diff --git a/src/EOS/Asset.cpp b/src/EOS/Asset.cpp index 80d7e159665..17d03be7976 100644 --- a/src/EOS/Asset.cpp +++ b/src/EOS/Asset.cpp @@ -12,11 +12,11 @@ using namespace TW::EOS; -static const int64_t precision = 1000; -static const uint8_t maxDecimals = 18; +static const int64_t Precision = 1000; +static const uint8_t MaxDecimals = 18; Asset::Asset(int64_t amount, uint8_t decimals, const std::string& symbol) { - if (decimals > maxDecimals) { + if (decimals > MaxDecimals) { throw std::invalid_argument("Too many decimals!"); } this->symbol |= decimals; @@ -112,7 +112,7 @@ std::string Asset::string() const { int charsWritten = snprintf(buffer, maxBufferSize, "%.*f %s", decimals, - static_cast(amount) / precision, + static_cast(amount) / Precision, getSymbol().c_str()); if (charsWritten < 0 || charsWritten > maxBufferSize) { diff --git a/src/EOS/Transaction.cpp b/src/EOS/Transaction.cpp index 57138378b2d..e7ae58700aa 100644 --- a/src/EOS/Transaction.cpp +++ b/src/EOS/Transaction.cpp @@ -18,7 +18,7 @@ using namespace TW; using namespace TW::EOS; using json = nlohmann::json; -Signature::Signature(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!"); } diff --git a/src/EOS/Transaction.h b/src/EOS/Transaction.h index d2413e8aee1..5d6660c462a 100644 --- a/src/EOS/Transaction.h +++ b/src/EOS/Transaction.h @@ -25,7 +25,7 @@ class Signature { static const size_t DataSize = 65; static const size_t ChecksumSize = 4; - Signature(Data sig, Type type); + Signature(const Data& sig, Type type); virtual ~Signature() { } void serialize(Data& os) const noexcept; std::string string() const noexcept; @@ -36,7 +36,7 @@ class Extension { uint16_t type; Data buffer; - Extension(uint16_t type, Data buffer) : type(type), buffer(buffer) { } + Extension(uint16_t type, const Data& buffer) : type(type), buffer(buffer) { } virtual ~Extension() { } void serialize(Data& os) const noexcept; nlohmann::json serialize() const noexcept; diff --git a/src/Elrond/Address.cpp b/src/Elrond/Address.cpp new file mode 100644 index 00000000000..1af84ace200 --- /dev/null +++ b/src/Elrond/Address.cpp @@ -0,0 +1,17 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..1f96edc6700 --- /dev/null +++ b/src/Elrond/Address.h @@ -0,0 +1,38 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 new file mode 100644 index 00000000000..76b806ff128 --- /dev/null +++ b/src/Elrond/Entry.cpp @@ -0,0 +1,31 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..78a5f60a39f --- /dev/null +++ b/src/Elrond/Entry.h @@ -0,0 +1,25 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 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 new file mode 100644 index 00000000000..eaccaabf417 --- /dev/null +++ b/src/Elrond/Serialization.cpp @@ -0,0 +1,68 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..e56d8a5bf87 --- /dev/null +++ b/src/Elrond/Serialization.h @@ -0,0 +1,21 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 new file mode 100644 index 00000000000..20519c8b80a --- /dev/null +++ b/src/Elrond/Signer.cpp @@ -0,0 +1,38 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..046f5e52d67 --- /dev/null +++ b/src/Elrond/Signer.h @@ -0,0 +1,28 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 7492fdb8b54..2b89542f94b 100644 --- a/src/Encrypt.cpp +++ b/src/Encrypt.cpp @@ -11,10 +11,10 @@ namespace TW::Encrypt { -size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode) { +size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode) { if (origSize % blockSize == 0) { // even blocks - if (paddingMode == PadWithPaddingSize) { + if (paddingMode == TWAESPaddingModePKCS7) { return blockSize; } return 0; @@ -23,7 +23,7 @@ size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode) { return blockSize - origSize % blockSize; } -Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode) { +Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode) { aes_encrypt_ctx ctx; if (aes_encrypt_key(key.data(), static_cast(key.size()), &ctx) == EXIT_FAILURE) { throw std::invalid_argument("Invalid key"); @@ -41,7 +41,7 @@ Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd // last block if (idx < resultSize) { uint8_t padded[blockSize] = {0}; - if (paddingMode == PadWithPaddingSize) { + if (paddingMode == TWAESPaddingModePKCS7) { std::memset(padded, static_cast(padding), blockSize); } std::memcpy(padded, data.data() + idx, data.size() - idx); @@ -51,7 +51,7 @@ Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd return result; } -Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode) { +Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode) { const size_t blockSize = AES_BLOCK_SIZE; if (data.size() % blockSize != 0) { throw std::invalid_argument("Invalid data size"); @@ -68,7 +68,7 @@ Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd aes_cbc_decrypt(data.data() + i, result.data() + i, blockSize, iv.data(), &ctx); } - if (paddingMode == PadWithPaddingSize && result.size() > 0) { + if (paddingMode == TWAESPaddingModePKCS7 && result.size() > 0) { // need to remove padding assert(result.size() > 0); const byte paddingSize = result[result.size() - 1]; @@ -89,7 +89,7 @@ Data AESCTREncrypt(const Data& key, const Data& data, Data& iv) { } Data result(data.size()); - aes_ctr_encrypt(data.data(), result.data(), data.size(), iv.data(), aes_ctr_cbuf_inc, &ctx); + aes_ctr_encrypt(data.data(), result.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); return result; } @@ -100,7 +100,7 @@ Data AESCTRDecrypt(const Data& key, const Data& data, Data& iv) { } Data result(data.size()); - aes_ctr_decrypt(data.data(), result.data(), data.size(), iv.data(), aes_ctr_cbuf_inc, &ctx); + aes_ctr_decrypt(data.data(), result.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); return result; } diff --git a/src/Encrypt.h b/src/Encrypt.h index 01c2ef43ac1..5b514b3b8c5 100644 --- a/src/Encrypt.h +++ b/src/Encrypt.h @@ -6,17 +6,13 @@ #pragma once +#include #include "Data.h" namespace TW::Encrypt { -enum PaddingMode { - PadWithZeros = 0, // padding value is zero - PadWithPaddingSize // padding value is the number of padding bytes; for even size add an extra block (PKCS#7) -}; - /// Determind needed padding size (used internally) -size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode); +size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode); /// Encrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// @@ -25,7 +21,7 @@ size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode); /// \param iv initialization vector. /// \param paddingMode If PadWithZeroes (default), data is padded with 0's to even block size. /// If PadWithPaddingSize, pad value is padding size, and even size input is padded with an extra block. -Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode = PadWithZeros); +Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode = TWAESPaddingModeZero); /// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// @@ -34,7 +30,7 @@ Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd /// \param iv initialization vector. /// \param paddingMode If PadWithZeroes (default), padding is not removed. /// If PadWithPaddingSize, padding is removed. -Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode = PadWithZeros); +Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode = TWAESPaddingModeZero); /// Encrypts a block of data using AES in Counter (CTR) mode. /// diff --git a/src/Ethereum/ABI.h b/src/Ethereum/ABI.h index 2217e9096ad..e00285c5a21 100644 --- a/src/Ethereum/ABI.h +++ b/src/Ethereum/ABI.h @@ -13,3 +13,4 @@ #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 index c23686be3de..5357965f894 100644 --- a/src/Ethereum/ABI/Array.cpp +++ b/src/Ethereum/ABI/Array.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Array.h" +#include "ParamFactory.h" #include "ValueEncoder.h" #include @@ -27,58 +28,47 @@ std::string ParamArray::getFirstType() const { 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); - - size_t headSize = 0; - for (auto i = 0; i < n; ++i) { - auto p = _params.getParamUnsafe(i); - if (p->isDynamic()) { - headSize += 32; - } else { - headSize += p->getSize(); - } - } - - size_t dynamicOffset = 0; - for (auto i = 0; i < n; ++i) { - auto p = _params.getParamUnsafe(i); - if (p->isDynamic()) { - ValueEncoder::encodeUInt256(uint256_t(headSize + dynamicOffset), data); - dynamicOffset += p->getSize(); - } else { - p->encode(data); - } - } - - for (auto i = 0; i < n; ++i) { - auto p = _params.getParamUnsafe(i); - if (p->isDynamic()) { - p->encode(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; } + 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 != static_cast(len)) { return false; } - // read values + if (len256 != uint256_t(len)) { + return false; + } + // check number of values auto n = _params.getCount(); - if (n != len) { - // Element number mismatch: the proto has to have exact same number of values as in the encoded form - // Note: this could be handles in a smarter way, and create more elements as needed + if (n == 0 || n > len) { + // Encoded length is less than params count, unsafe to continue decoding return false; } - for (auto i = 0; i < n; ++i) { - if (!_params.getParamUnsafe(i)->decode(encoded, offset_inout)) { 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 true; -} \ No newline at end of file + return res; +} diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h index 362b10ff402..4adc96a05ad 100644 --- a/src/Ethereum/ABI/Array.h +++ b/src/Ethereum/ABI/Array.h @@ -29,7 +29,7 @@ class ParamArray: public ParamCollection 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 { return _params.getSize(); } + 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; diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp index 289cc66dd06..5fb23381ef5 100644 --- a/src/Ethereum/ABI/Bytes.cpp +++ b/src/Ethereum/ABI/Bytes.cpp @@ -23,12 +23,18 @@ bool ParamByteArray::decodeBytes(const Data& encoded, Data& decoded, size_t& off size_t origOffset = offset_inout; // read len uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { return false; } + 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 != static_cast(len)) { return false; } + if (len256 != uint256_t(len)) { + return false; + } // check if there is enough data - if (encoded.size() < offset_inout + len) { return false; } + if (encoded.size() < offset_inout + len) { + return false; + } // read data decoded = Data(encoded.begin() + offset_inout, encoded.begin() + offset_inout + len); offset_inout += len; @@ -44,14 +50,18 @@ void ParamByteArrayFix::encode(Data& data) const { append(data, Data(padding)); } -bool ParamByteArrayFix::decodeBytesFix(const Data& encoded, size_t n, Data& decoded, size_t& offset_inout) { +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()); + 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); @@ -65,7 +75,9 @@ void ParamString::encodeString(const std::string& decoded, Data& 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; } + if (!ParamByteArray::decodeBytes(encoded, decodedData, offset_inout)) { + return false; + } decoded = std::string(decodedData.begin(), decodedData.end()); return true; } diff --git a/src/Ethereum/ABI/Bytes.h b/src/Ethereum/ABI/Bytes.h index c2b85e2e5b1..69eb5aaede8 100644 --- a/src/Ethereum/ABI/Bytes.h +++ b/src/Ethereum/ABI/Bytes.h @@ -64,7 +64,7 @@ class ParamString: public ParamCollection public: ParamString() = default; ParamString(std::string val): ParamCollection() { setVal(val); } - void setVal(std::string& val) { _str = 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()); } diff --git a/src/Ethereum/ABI/Function.h b/src/Ethereum/ABI/Function.h index cf3585096bd..11d020c8ab7 100644 --- a/src/Ethereum/ABI/Function.h +++ b/src/Ethereum/ABI/Function.h @@ -26,7 +26,7 @@ class Function { ParamSet _outParams; Function(std::string name) : name(std::move(name)) {} - Function(std::string name, std::vector> inParams) + 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. diff --git a/src/Ethereum/ABI/ParamFactory.cpp b/src/Ethereum/ABI/ParamFactory.cpp new file mode 100644 index 00000000000..c19c0e934f1 --- /dev/null +++ b/src/Ethereum/ABI/ParamFactory.cpp @@ -0,0 +1,86 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 + +using namespace std; +using namespace boost::algorithm; + +namespace TW::Ethereum::ABI { + +static int parseBitSize(const std::string& type) { + if (type.size() == 0){ + return 256; + } + int size = stoi(type); + if (size < 8 || size > 256 || 256 % size != 0) { + 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); +} + +std::shared_ptr ParamFactory::make(const std::string& type) { + shared_ptr param; + if (type == "address") { + 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 ParamFactory::getValue(const std::shared_ptr& param, const std::string& type) { + std::string result = ""; + if (type == "address") { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getData()); + } 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; +} + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamFactory.h b/src/Ethereum/ABI/ParamFactory.h new file mode 100644 index 00000000000..5db745ae5cb --- /dev/null +++ b/src/Ethereum/ABI/ParamFactory.h @@ -0,0 +1,25 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +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); +}; + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp index 7591e9fc39a..627570b8fc8 100644 --- a/src/Ethereum/ABI/Parameters.cpp +++ b/src/Ethereum/ABI/Parameters.cpp @@ -7,25 +7,29 @@ #include "Parameters.h" #include "ValueEncoder.h" -#include #include +#include using namespace TW::Ethereum::ABI; ParamSet::~ParamSet() { - _params.clear(); + _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; } + 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); } + for (auto p : params) { + addParam(p); + } } bool ParamSet::getParam(int paramIndex, std::shared_ptr& param_out) const { @@ -52,8 +56,10 @@ std::shared_ptr ParamSet::getParamUnsafe(int paramIndex) const { std::string ParamSet::getType() const { std::string t = "("; int cnt = 0; - for(auto p: _params) { - if (cnt > 0) t += ","; + for (auto p : _params) { + if (cnt > 0) { + t += ","; + } t += p->getType(); ++cnt; } @@ -62,16 +68,21 @@ std::string ParamSet::getType() const { } size_t ParamSet::getSize() const { + // 2-pass encoding size_t s = 0; - for(auto p: _params) { + for (auto p: _params) { + if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { + // offset used + s += 32; + } s += p->getSize(); } - return 32 + ValueEncoder::paddedTo32(s); + return ValueEncoder::paddedTo32(s); } size_t ParamSet::getHeadSize() const { size_t s = 0; - for(auto p: _params) { + for (auto p : _params) { if (p->isDynamic()) { s += 32; } else { @@ -87,7 +98,7 @@ void ParamSet::encode(Data& data) const { size_t dynamicOffset = 0; // pass 1: small values or indices - for(auto p: _params) { + for (auto p : _params) { if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { // include only offset ValueEncoder::encodeUInt256(uint256_t(headSize + dynamicOffset), data); @@ -96,32 +107,38 @@ void ParamSet::encode(Data& data) const { // encode small data p->encode(data); } - } + } // pass 2: dynamic values - for(auto p: _params) { + 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) { + for (auto p : _params) { if (p->isDynamic()) { uint256_t index; - if (!ABI::decode(encoded, index, offset_inout)) { return false; } + if (!ABI::decode(encoded, index, offset_inout)) { + return false; + } // index is read but not used } else { - if (!p->decode(encoded, offset_inout)) { return false; } + if (!p->decode(encoded, offset_inout)) { + return false; + } } } - // pass2: large values - for(auto p: _params) { + // pass2: large values + for (auto p : _params) { if (p->isDynamic()) { - if (!p->decode(encoded, offset_inout)) { return false; } + if (!p->decode(encoded, offset_inout)) { + return false; + } } } return true; diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h index c6622ee90f6..5bac50ea89f 100644 --- a/src/Ethereum/ABI/Parameters.h +++ b/src/Ethereum/ABI/Parameters.h @@ -36,9 +36,11 @@ class ParamSet { /// Return the function type signature, of the form "baz(int32,uint256)" std::string getType() const; size_t getSize() const; - size_t getHeadSize() 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, "(,,...)". diff --git a/src/Ethereum/ABI/ValueDecoder.cpp b/src/Ethereum/ABI/ValueDecoder.cpp new file mode 100644 index 00000000000..cb79ecd418c --- /dev/null +++ b/src/Ethereum/ABI/ValueDecoder.cpp @@ -0,0 +1,17 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "ValueDecoder.h" + +namespace TW::Ethereum::ABI { + +uint256_t ValueDecoder::decodeUInt256(Data& data) { + if (data.size() > 32) { + return load(subData(data, 0, 32)); + } + return load(data); +} +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueDecoder.h b/src/Ethereum/ABI/ValueDecoder.h new file mode 100644 index 00000000000..e073933f864 --- /dev/null +++ b/src/Ethereum/ABI/ValueDecoder.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 + +namespace TW::Ethereum::ABI { + +class ValueDecoder { +public: + static uint256_t decodeUInt256(Data& data); +}; +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ContractCall.cpp b/src/Ethereum/ContractCall.cpp new file mode 100644 index 00000000000..ee87292c395 --- /dev/null +++ b/src/Ethereum/ContractCall.cpp @@ -0,0 +1,110 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "ContractCall.h" +#include "ABI.h" +#include "HexCoding.h" +#include "uint256.h" +#include +#include + +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); + } +} + +static vector getArrayValue(Function& func, const string& type, int idx) { + auto baseType = string(type.begin(), type.end() - 2); + shared_ptr param; + func.getInParam(idx, param); + auto array = dynamic_pointer_cast(param); + auto count = array->getCount(); + auto result = vector(); + for (int i = 0; i < count; i++) { + result.push_back(ParamFactory::getValue(array->getParam(i), baseType)); + } + return result; +} + +static string getValue(Function& func, const string& type, int idx) { + shared_ptr param; + func.getInParam(idx, param); + return ParamFactory::getValue(param, type); +} + +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) { + 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"]); + } + + // decode inputs + size_t offset = 0; + auto success = func.decodeInput(call, offset); + if (!success) { + return {}; + } + + // build output json + auto decoded = json{ + {"function", func.getType()}, + {"inputs", buildInputs(func, registry)}, + }; + return decoded.dump(); +} + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ContractCall.h b/src/Ethereum/ContractCall.h new file mode 100644 index 00000000000..ca7b5c44718 --- /dev/null +++ b/src/Ethereum/ContractCall.h @@ -0,0 +1,16 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 +#include +#include + +namespace TW::Ethereum::ABI { + std::optional decodeCall(const Data& call, const nlohmann::json& abi); +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 35297782d23..bedc2d7e6b9 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "RLP.h" using namespace TW::Ethereum; using namespace std; @@ -32,3 +33,16 @@ void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) con string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +void Entry::encodeRawTx(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + encodeTemplate(dataIn, dataOut); +} + +void Entry::decodeRawTx(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + try { + auto data = RLP::decodeRawTransaction(dataIn); + dataOut.insert(dataOut.end(), data.begin(), data.end()); + } catch(...) { + return; + } +} diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index fa32492ed0d..e852c119edf 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -23,6 +23,8 @@ class Entry: public CoinEntry { TWCoinTypePOANetwork, TWCoinTypeThunderToken, TWCoinTypeTomoChain, + TWCoinTypeSmartChainLegacy, + TWCoinTypeSmartChain, }; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; @@ -31,6 +33,8 @@ class Entry: public CoinEntry { virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual bool supportsJSONSigning() const { return true; } virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + virtual void encodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + virtual void decodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index 2c543cc9309..51d24062abb 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -7,12 +7,18 @@ #include "RLP.h" #include "../Data.h" +#include "../uint256.h" +#include "../BinaryCoding.h" +#include "../HexCoding.h" +#include #include using namespace TW; using namespace TW::Ethereum; +using json = nlohmann::json; + Data RLP::encode(const uint256_t& value) noexcept { using boost::multiprecision::cpp_int; @@ -74,27 +80,27 @@ Data RLP::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexce Data RLP::putint(uint64_t i) noexcept { // clang-format off - if (i < (1l << 8)) + if (i < (1ULL << 8)) return {static_cast(i)}; - if (i < (1l << 16)) + if (i < (1ULL << 16)) return { static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 24)) + if (i < (1ULL << 24)) return { static_cast(i >> 16), static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 32)) + if (i < (1ULL << 32)) return { static_cast(i >> 24), static_cast(i >> 16), static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 40)) + if (i < (1ULL << 40)) return { static_cast(i >> 32), static_cast(i >> 24), @@ -102,7 +108,7 @@ Data RLP::putint(uint64_t i) noexcept { static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 48)) + if (i < (1ULL << 48)) return { static_cast(i >> 40), static_cast(i >> 32), @@ -111,7 +117,7 @@ Data RLP::putint(uint64_t i) noexcept { static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 56)) + if (i < (1ULL << 56)) return { static_cast(i >> 48), static_cast(i >> 40), @@ -134,3 +140,137 @@ Data RLP::putint(uint64_t i) noexcept { }; // clang-format on } + +Data RLP::decodeRawTransaction(const Data& data) { + auto decoded = decode(data).decoded; + if (decoded.size() < 9) { + return {}; + } + auto result = json { + {"nonce", hexEncoded(decoded[0])}, + {"gasPrice", hexEncoded(decoded[1])}, + {"gas", hexEncoded(decoded[2])}, + {"to", hexEncoded(decoded[3])}, + {"value", hexEncoded(decoded[4])}, + {"input", hexEncoded(decoded[5])}, + {"v", hexEncoded(decoded[6])}, + {"r", hexEncoded(decoded[7])}, + {"s", hexEncoded(decoded[8])}, + }.dump(); + return Data(result.begin(), result.end()); +} + +static RLP::DecodedItem 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; +} + +static uint64_t 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); +} + +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"); +} diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index fd593e2304a..3d54b08f611 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.h @@ -94,6 +94,17 @@ struct RLP { /// 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; + }; + + /// Decodes raw transaction to json data + static Data decodeRawTransaction(const Data& data); + + /// Decodes data, remainder from RLP encoded data + static DecodedItem decode(const Data& data); }; } // namespace TW::Ethereum diff --git a/src/Ethereum/Signer.cpp b/src/Ethereum/Signer.cpp index 2dd09f11b15..bbeeb7c8157 100644 --- a/src/Ethereum/Signer.cpp +++ b/src/Ethereum/Signer.cpp @@ -44,7 +44,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { } std::tuple Signer::values(const uint256_t &chainID, - const Data &signature) noexcept { + 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); @@ -62,7 +62,7 @@ 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); } diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h index 50b279f5f9c..b041b64417f 100644 --- a/src/Ethereum/Signer.h +++ b/src/Ethereum/Signer.h @@ -46,13 +46,13 @@ class Signer { /// /// @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 static std::tuple values(const uint256_t &chainID, - const Data &signature) noexcept; + const Data& signature) noexcept; protected: /// Computes the transaction hash. diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h index e16d5d8f17c..d5af92b3ed8 100644 --- a/src/Ethereum/Transaction.h +++ b/src/Ethereum/Transaction.h @@ -26,7 +26,7 @@ class Transaction { uint256_t r = uint256_t(); uint256_t s = uint256_t(); - Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, Data to, uint256_t amount, + Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, uint256_t amount, Data payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) diff --git a/src/FIO/Action.h b/src/FIO/Action.h index b88931a71f6..c79ef65a500 100644 --- a/src/FIO/Action.h +++ b/src/FIO/Action.h @@ -92,7 +92,7 @@ class AddPubAddressData { std::string tpid; std::string actor; - AddPubAddressData(const std::string& fioAddress, std::vector addresses, + AddPubAddressData(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) {} diff --git a/src/FIO/Actor.cpp b/src/FIO/Actor.cpp new file mode 100644 index 00000000000..7502f1605b8 --- /dev/null +++ b/src/FIO/Actor.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Actor.h" + +#include + +using namespace TW::FIO; +using namespace std; + +string Actor::actor(const Address& addr) +{ + uint64_t shortenedKey = shortenKey(addr.bytes); + string name13 = name(shortenedKey); + // trim to 12 chracters + return name13.substr(0, 12); +} + +bool Actor::validate(const std::string& addr) { + regex pattern(R"(\b([a-z1-5]{3,})[.@]?\b)"); + smatch match; + return regex_search(addr, match, pattern); +} + +uint64_t Actor::shortenKey(const 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 + + auto shuffle = len == 12 ? 0 : 5 * (12 - len) - 1; + res |= trimmed_char << shuffle; + + len++; i++; + } + return res; +} + +string Actor::name(uint64_t shortKey) { + static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; + + string str(13,'.'); //We are forcing the string to be 13 characters + + uint64_t tmp = shortKey; + 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); + } + + return str; +} diff --git a/src/FIO/Actor.h b/src/FIO/Actor.h new file mode 100644 index 00000000000..2024d756c05 --- /dev/null +++ b/src/FIO/Actor.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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::FIO { +/// Helper class for Actor name generation from address +class Actor { + public: + /// Generate the actor name of the address + static std::string actor(const Address& addr); + + /// Check if the address is valid + static bool validate(const std::string& addr); + + /// Used internally, derive shortened uint64 key from adddr bytes + static uint64_t shortenKey(const std::array& addrKey); + /// Used internally, derive name from uint64 shortened key + static std::string name(uint64_t shortKey); +}; +} // namespace TW::FIO diff --git a/src/FIO/Encryption.cpp b/src/FIO/Encryption.cpp index 5b10a8ca229..dbea7aac7b1 100644 --- a/src/FIO/Encryption.cpp +++ b/src/FIO/Encryption.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -44,7 +45,7 @@ Data Encryption::checkEncrypt(const Data& secret, const Data& message, Data& iv) TW::append(ivOrig, iv); // Encrypt. Padding is done (PKCS#7) - const Data C = Encrypt::AESCBCEncrypt(Ke, message, iv, Encrypt::PadWithPaddingSize); + const Data C = Encrypt::AESCBCEncrypt(Ke, message, iv, TWAESPaddingModePKCS7); // HMAC. Include in the HMAC input everything that impacts the decryption. Data hmacIn(0); @@ -82,7 +83,7 @@ Data Encryption::checkDecrypt(const Data& secret, const Data& message) { } // Decrypt, unpadding is done - const Data unencrypted = Encrypt::AESCBCDecrypt(Ke, C, iv, Encrypt::PadWithPaddingSize); + const Data unencrypted = Encrypt::AESCBCDecrypt(Ke, C, iv, TWAESPaddingModePKCS7); return unencrypted; } diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index 3e09738bb4e..43abd7df8d3 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -6,6 +6,7 @@ #include "Signer.h" #include "Address.h" +#include "Actor.h" #include "../Base58.h" #include "../Hash.h" @@ -61,46 +62,4 @@ int Signer::isCanonical(uint8_t by, uint8_t sig[64]) { && !(sig[32] == 0 && !(sig[33] & 0x80)); } -string Actor::actor(const Address& addr) -{ - uint64_t shortenedKey = shortenKey(addr.bytes); - string name13 = name(shortenedKey); - // trim to 12 chracters - return name13.substr(0, 12); -} - -uint64_t Actor::shortenKey(const 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 - - auto shuffle = len == 12 ? 0 : 5 * (12 - len) - 1; - res |= trimmed_char << shuffle; - - len++; i++; - } - return res; -} - -string Actor::name(uint64_t shortKey) { - static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; - - string str(13,'.'); //We are forcing the string to be 13 characters - - uint64_t tmp = shortKey; - 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); - } - - return str; -} - } // namespace TW::FIO diff --git a/src/FIO/Signer.h b/src/FIO/Signer.h index b7595e7ae8e..7604ca4f953 100644 --- a/src/FIO/Signer.h +++ b/src/FIO/Signer.h @@ -38,16 +38,4 @@ class Signer { static int isCanonical(uint8_t by, uint8_t sig[64]); }; -/// Helper class for Actor name generation from address -class Actor { - public: - /// Generate the actor name of the address - static std::string actor(const Address& addr); - - /// Used internally, derive shortened uint64 key from adddr bytes - static uint64_t shortenKey(const std::array& addrKey); - /// Used internally, derive name from uint64 shortened key - static std::string name(uint64_t shortKey); -}; - } // namespace TW::FIO diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index c7ec086af47..71eaa4764ea 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -6,6 +6,7 @@ #include "TransactionBuilder.h" +#include "Actor.h" #include "Encryption.h" #include "NewFundsRequest.h" #include "Signer.h" diff --git a/src/Filecoin/Entry.cpp b/src/Filecoin/Entry.cpp index 8980a773fd8..39db5385ded 100644 --- a/src/Filecoin/Entry.cpp +++ b/src/Filecoin/Entry.cpp @@ -14,14 +14,20 @@ 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 { +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 { +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/Filecoin/Entry.h b/src/Filecoin/Entry.h index cb370ba5e3e..74435732123 100644 --- a/src/Filecoin/Entry.h +++ b/src/Filecoin/Entry.h @@ -11,13 +11,18 @@ 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: +/// 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 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 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::Filecoin diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index adadc8b9c29..ed5363ba63e 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -5,6 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #include "Signer.h" +#include "HexCoding.h" +#include using namespace TW; using namespace TW::Filecoin; @@ -38,3 +40,11 @@ Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce auto signature = privateKey.sign(toSign, TWCurveSECP256k1); return Data(signature.begin(), signature.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()); + auto output = Signer::sign(input); + return hex(output.encoded()); +} diff --git a/src/Filecoin/Signer.h b/src/Filecoin/Signer.h index 4509374f882..3d20617bcb4 100644 --- a/src/Filecoin/Signer.h +++ b/src/Filecoin/Signer.h @@ -23,6 +23,9 @@ 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; }; diff --git a/src/Filecoin/Transaction.cpp b/src/Filecoin/Transaction.cpp index 6168a36da0c..b3f18267920 100644 --- a/src/Filecoin/Transaction.cpp +++ b/src/Filecoin/Transaction.cpp @@ -37,11 +37,19 @@ const Data cidPrefix = { }; Cbor::Encode Transaction::message() const { - return Cbor::Encode::array( - {Cbor::Encode::bytes(to.bytes), Cbor::Encode::bytes(from.bytes), Cbor::Encode::uint(nonce), - Cbor::Encode::bytes(encodeVaruint(value)), Cbor::Encode::bytes(encodeVaruint(gasPrice)), - Cbor::Encode::bytes(encodeVaruint(gasLimit)), Cbor::Encode::uint(0), - Cbor::Encode::bytes(Data())}); + 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(encodeVaruint(value)), // value + Cbor::Encode::bytes(encodeVaruint(gasPrice)), // gas price + cborGasLimit, // gas limit + Cbor::Encode::uint(0), // abi.MethodNum (0 => send) + Cbor::Encode::bytes(Data()) // data (empty) + }); } Data Transaction::cid() const { diff --git a/src/Filecoin/Transaction.h b/src/Filecoin/Transaction.h index 8885816ff1e..a2e73a64f66 100644 --- a/src/Filecoin/Transaction.h +++ b/src/Filecoin/Transaction.h @@ -26,7 +26,7 @@ class Transaction { uint256_t value; // Miner fee uint256_t gasPrice; - uint256_t gasLimit; + int64_t gasLimit; // Transaction type; 0 for simple transfers uint64_t method; // Transaction data; empty for simple transfers diff --git a/src/Groestlcoin/Entry.h b/src/Groestlcoin/Entry.h index 07f6fb9a980..a8d4d7f7a9d 100644 --- a/src/Groestlcoin/Entry.h +++ b/src/Groestlcoin/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual 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; - // normalizeAddress is not used 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; diff --git a/src/Groestlcoin/Signer.cpp b/src/Groestlcoin/Signer.cpp index 2525d59ed93..01e16a7256f 100644 --- a/src/Groestlcoin/Signer.cpp +++ b/src/Groestlcoin/Signer.cpp @@ -34,14 +34,13 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { *output.mutable_transaction() = tx.proto(); Data encoded; - auto hasWitness = std::any_of(tx.inputs.begin(), tx.inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); - tx.encode(hasWitness, encoded); + signer.encodeTx(tx, encoded); output.set_encoded(encoded.data(), encoded.size()); Data txHashData = encoded; - if (hasWitness) { + if (tx.hasWitness()) { txHashData.clear(); - tx.encode(false, txHashData); + tx.encode(txHashData, Transaction::SegwitFormatMode::NonSegwit); } auto txHash = Hash::sha256(txHashData.data(), txHashData.size()); std::reverse(txHash.begin(), txHash.end()); diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 9d13c0af0d6..dc4f7beb803 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -54,10 +54,11 @@ HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase) HDWallet::HDWallet(const Data& data, const std::string& passphrase) : seed(), mnemonic(), passphrase(passphrase) { std::array mnemonic_chars; - mnemonic_from_data(data.data(), data.size(), mnemonic_chars.data()); - mnemonic_to_seed(mnemonic_chars.data(), passphrase.c_str(), seed.data(), nullptr); - mnemonic = mnemonic_chars.data(); - updateEntropy(); + if (mnemonic_from_data(data.data(), data.size(), mnemonic_chars.data())) { + mnemonic_to_seed(mnemonic_chars.data(), passphrase.c_str(), seed.data(), nullptr); + mnemonic = mnemonic_chars.data(); + updateEntropy(); + } } HDWallet::~HDWallet() { @@ -86,8 +87,8 @@ PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { return PrivateKey(data); } -PrivateKey HDWallet::getKey(const DerivationPath& derivationPath) const { - const auto curve = TWCoinTypeCurve(derivationPath.coin()); +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); switch (privateKeyType) { @@ -109,7 +110,7 @@ PrivateKey HDWallet::getKey(const DerivationPath& derivationPath) const { std::string HDWallet::deriveAddress(TWCoinType coin) const { const auto derivationPath = TW::derivationPath(coin); - return TW::deriveAddress(coin, getKey(derivationPath)); + return TW::deriveAddress(coin, getKey(coin, derivationPath)); } std::string HDWallet::getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { @@ -139,8 +140,7 @@ std::string HDWallet::getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, T return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); } -std::optional HDWallet::getPublicKeyFromExtended(const std::string &extended, const DerivationPath& path) { - const auto coin = path.coin(); +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); @@ -148,33 +148,24 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string &e if (!deserialize(extended, curve, hasher, &node)) { return {}; } + if (node.curve->params == nullptr) { + return {}; + } hdnode_public_ckd(&node, path.change()); hdnode_public_ckd(&node, path.address()); hdnode_fill_public_key(&node); - switch (curve) { - case TWCurveSECP256k1: + // These public key type are not applicable. Handled above, as node.curve->params is null + assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519Extended && curve != TWCurveCurve25519); + if (curve == TWCurveSECP256k1) { return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); - case TWCurveED25519: - return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeED25519); - case TWCurveED25519Blake2bNano: - return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeED25519Blake2b); - case TWCurveED25519Extended: - { - // concatenate public key and chain code (2x32 bytes) - Data concat(node.public_key, node.public_key + 32); - append(concat, Data(node.chain_code, node.chain_code + 32)); - return PublicKey(concat, TWPublicKeyTypeED25519Extended); - } - case TWCurveCurve25519: - return PublicKey(Data(node.public_key, node.public_key + 32), TWPublicKeyTypeCURVE25519); - case TWCurveNIST256p1: + } else if (curve == TWCurveNIST256p1) { return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeNIST256p1); } + return {}; } -std::optional HDWallet::getPrivateKeyFromExtended(const std::string &extended, const DerivationPath& path) { - const auto coin = path.coin(); +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); @@ -224,9 +215,14 @@ std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version return Base58::bitcoin.encodeCheck(node_data, hasher); } -bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode *node) { +bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node) { memset(node, 0, sizeof(HDNode)); - node->curve = get_curve_by_name(curveName(curve)); + const char* curveNameStr = curveName(curve); + if (curveNameStr == nullptr || ::strlen(curveNameStr) == 0) { + return false; + } + node->curve = get_curve_by_name(curveNameStr); + assert(node->curve != nullptr); const auto node_data = Base58::bitcoin.decodeCheck(extended, hasher); if (node_data.size() != 78) { @@ -292,8 +288,13 @@ const char* curveName(TWCurve curve) { return ED25519_NAME; case TWCurveED25519Blake2bNano: return ED25519_BLAKE2B_NANO_NAME; + case TWCurveED25519Extended: + return ED25519_CARDANO_NAME; case TWCurveNIST256p1: return NIST256P1_NAME; + case TWCurveCurve25519: + return CURVE25519_NAME; + case TWCurveNone: default: return ""; } diff --git a/src/HDWallet.h b/src/HDWallet.h index fb2dd62e4ee..ce43a762d70 100644 --- a/src/HDWallet.h +++ b/src/HDWallet.h @@ -71,7 +71,7 @@ class HDWallet { PrivateKey getMasterKeyExtension(TWCurve curve) const; /// Returns the private key at the given derivation path. - PrivateKey getKey(const DerivationPath& derivationPath) const; + PrivateKey getKey(const TWCoinType coin, const DerivationPath& derivationPath) const; /// Derives the address for a coin. std::string deriveAddress(TWCoinType coin) const; @@ -83,10 +83,10 @@ class HDWallet { std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const; /// Computes the public key from an exteded public key representation. - static std::optional getPublicKeyFromExtended(const std::string &extended, const DerivationPath& path); + static std::optional getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); /// Computes the private key from an exteded private key representation. - static std::optional getPrivateKeyFromExtended(const std::string &extended, const DerivationPath& path); + static std::optional getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); public: // Private key type (later could be moved out of HDWallet) diff --git a/src/Harmony/Address.h b/src/Harmony/Address.h index 2c65defc17e..a36d8507ec1 100644 --- a/src/Harmony/Address.h +++ b/src/Harmony/Address.h @@ -18,12 +18,12 @@ class Address: public Bech32Address { static const std::string hrp; // HRP_HARMONY - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) { + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) { if (getKeyHash().size() != Address::size) { throw std::invalid_argument("invalid address data"); } diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index a75d8110f75..0543facad72 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -13,7 +13,7 @@ using namespace TW; using namespace TW::Harmony; std::tuple Signer::values(const uint256_t &chainID, - const Data &signature) noexcept { + const Data& signature) noexcept { auto r = load(Data(signature.begin(), signature.begin() + 32)); auto s = load(Data(signature.begin() + 32, signature.begin() + 64)); auto v = load(Data(signature.begin() + 64, signature.begin() + 65)); @@ -22,13 +22,13 @@ std::tuple Signer::values(const uint256_t &chai } std::tuple -Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept { +Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept { auto signature = privateKey.sign(hash, TWCurveSECP256k1); return values(chainID, signature); } template -Proto::SigningOutput Signer::prepareOutput(const Data &encoded, const T &transaction) noexcept { +Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T &transaction) noexcept { auto protoOutput = Proto::SigningOutput(); auto v = store(transaction.v); @@ -322,7 +322,7 @@ Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput &input } template -void Signer::sign(const PrivateKey &privateKey, const Data &hash, T &transaction) const noexcept { +void Signer::sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept { auto tuple = sign(chainID, privateKey, hash); transaction.r = std::get<0>(tuple); transaction.s = std::get<1>(tuple); diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index a7aa10d5cbc..70d048838b9 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -52,23 +52,23 @@ class Signer { explicit Signer(uint256_t chainID) : chainID(std::move(chainID)) {} template - static Proto::SigningOutput prepareOutput(const Data &encoded, const T &transaction) noexcept; + static Proto::SigningOutput prepareOutput(const Data& encoded, const T &transaction) noexcept; /// Signs the given transaction. template - void sign(const PrivateKey &privateKey, const Data &hash, T &transaction) const noexcept; + void sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept; /// Signs a hash with the given private key for the given chain identifier. /// /// @returns the r, s, and v values of the transaction signature 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 static std::tuple values(const uint256_t &chainID, - const Data &signature) noexcept; + const Data& signature) noexcept; std::string txnAsRLPHex(Transaction &transaction) const noexcept; diff --git a/src/Harmony/Transaction.h b/src/Harmony/Transaction.h index c06e4359513..ac43aa253f0 100644 --- a/src/Harmony/Transaction.h +++ b/src/Harmony/Transaction.h @@ -32,7 +32,7 @@ class Transaction { uint256_t s = uint256_t(); Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t fromShardID, - uint256_t toShardID, Address to, uint256_t amount, Data payload) + uint256_t toShardID, Address to, uint256_t amount, const Data& payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) , gasLimit(std::move(gasLimit)) diff --git a/src/HexCoding.cpp b/src/HexCoding.cpp deleted file mode 100644 index 03f43d298b3..00000000000 --- a/src/HexCoding.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 "HexCoding.h" - -#include - -std::tuple TW::value(uint8_t c) { - if (c >= '0' && c <= '9') - return std::make_tuple(c - '0', true); - if (c >= 'a' && c <= 'z') - return std::make_tuple(c - 'a' + 10, true); - if (c >= 'A' && c <= 'Z') - return std::make_tuple(c - 'A' + 10, true); - - // Invalid digit - return std::make_tuple(0, false); -} diff --git a/src/HexCoding.h b/src/HexCoding.h index 33f9aecd93d..198691bbe60 100644 --- a/src/HexCoding.h +++ b/src/HexCoding.h @@ -8,6 +8,8 @@ #include "Data.h" +#include + #include #include #include @@ -42,6 +44,12 @@ inline std::string hex(const T& collection) { return hex(std::begin(collection), std::end(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"); +} + /// Converts a `uint64_t` value to a hexadecimal string. inline std::string hex(uint64_t value) { auto bytes = reinterpret_cast(&value); @@ -61,32 +69,13 @@ inline Data parse_hex(const Iter begin, const Iter end) { if (end - begin >= 2 && *begin == '0' && *(begin + 1) == 'x') { it += 2; } - - Data result; - result.reserve(((end - begin) + 1) / 2); - - while (it != end) { - auto high = value(*it); - if (!std::get<1>(high)) { - return {}; - } - it += 1; - - if (it == end) { - result.push_back(std::get<0>(high)); - break; - } - - auto low = value(*it); - if (!std::get<1>(low)) { - return {}; - } - it += 1; - - result.push_back(static_cast((std::get<0>(high) << 4) | std::get<0>(low))); + try { + std::string temp; + boost::algorithm::unhex(it, end, std::back_inserter(temp)); + return Data(temp.begin(), temp.end()); + } catch (...) { + return {}; } - - return result; } /// Parses a string of hexadecimal values. diff --git a/src/IoTeX/Address.h b/src/IoTeX/Address.h index f34c353e704..a76743a4250 100644 --- a/src/IoTeX/Address.h +++ b/src/IoTeX/Address.h @@ -18,12 +18,12 @@ class Address: public Bech32Address { static const std::string hrp; // HRP_IOTEX - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) { + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) { if (getKeyHash().size() != Address::size) { throw std::invalid_argument("invalid address data"); } diff --git a/src/IoTeX/Protobuf/.gitignore b/src/IoTeX/Protobuf/.gitignore deleted file mode 100644 index 687ffbb426e..00000000000 --- a/src/IoTeX/Protobuf/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.cc -*.h diff --git a/src/IoTeX/Protobuf/action.proto b/src/IoTeX/Protobuf/action.proto deleted file mode 100644 index 921d8c38807..00000000000 --- a/src/IoTeX/Protobuf/action.proto +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2018 IoTeX -// This is an alpha (internal) release and is not suitable for production. This -// source code is provided 'as is' and no warranties are given as to title or -// non-infringement, merchantability or fitness for purpose and, to the extent -// permitted by law, all liability for your use of the code is disclaimed. This -// source code is governed by Apache License 2.0 that can be found in the -// LICENSE file. - -syntax = "proto3"; -package iotextypes; - -message Transfer { - // used by state-based model - string amount = 1; - string recipient = 2; - bytes payload = 3; -} - -message Execution { - string amount = 1; - string contract = 2; - bytes data = 3; -} - -message ActionCore { - uint32 version = 1; - uint64 nonce = 2; - uint64 gasLimit = 3; - string gasPrice = 4; - oneof action { - Transfer transfer = 10; - Execution execution = 12; - } -} - -message Action { - ActionCore core = 1; - bytes senderPubKey = 2; - bytes signature = 3; -} diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index e7fb547d025..6b0e6d6ec1a 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -24,7 +24,7 @@ Data Signer::sign() const { } Proto::SigningOutput Signer::build() const { - auto signedAction = iotextypes::Action(); + auto signedAction = Proto::Action(); signedAction.mutable_core()->MergeFrom(action); auto key = PrivateKey(input.privatekey()); auto pk = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended).bytes; @@ -32,7 +32,7 @@ Proto::SigningOutput Signer::build() const { auto sig = key.sign(hash(), TWCurveSECP256k1); signedAction.set_signature(sig.data(), sig.size()); - auto output = IoTeX::Proto::SigningOutput(); + auto output = Proto::SigningOutput(); auto serialized = signedAction.SerializeAsString(); output.set_encoded(serialized); auto h = Hash::keccak256(serialized); @@ -44,41 +44,7 @@ Data Signer::hash() const { return Hash::keccak256(action.SerializeAsString()); } -static Data encodeStaking(const Proto::Staking& staking) { - Data encoded; - if (staking.has_stake()) { - auto& stake = staking.stake(); - stakingStake(TW::data(stake.candidate()), stake.duration(), stake.nondecay(), TW::data(stake.data()), encoded); - } else if (staking.has_unstake()) { - auto& unstake = staking.unstake(); - stakingUnstake(unstake.piggy_index(), TW::data(unstake.data()), encoded); - } else if (staking.has_withdraw()) { - auto& withdraw = staking.withdraw(); - stakingWithdraw(withdraw.piggy_index(), TW::data(withdraw.data()), encoded); - } else if (staking.has_movestake()) { - auto& move = staking.movestake(); - stakingMoveStake(move.piggy_index(), TW::data(move.candidate()), TW::data(move.data()), encoded); - } else if (staking.has_addstake()) { - auto& add = staking.addstake(); - stakingAddStake(add.piggy_index(), TW::data(add.data()), encoded); - } - return encoded; -} void Signer::toActionCore() { - if (input.has_staking()) { - action.set_version(input.version()); - action.set_nonce(input.nonce()); - action.set_gaslimit(input.gaslimit()); - action.set_gasprice(input.gasprice()); - auto& staking = input.staking(); - auto encoded = encodeStaking(staking); - auto& execution = *action.mutable_execution(); - execution.set_amount(staking.amount()); - execution.set_contract(staking.contract()); - execution.set_data(encoded.data(), encoded.size()); - } else { - // ActionCore is almost same as SigningInput, missing field privateKey = 5; - action.ParseFromString(input.SerializeAsString()); - action.DiscardUnknownFields(); - } + action.ParseFromString(input.SerializeAsString()); + action.DiscardUnknownFields(); } diff --git a/src/IoTeX/Signer.h b/src/IoTeX/Signer.h index d1e5d8caf33..31fc2c8a15f 100644 --- a/src/IoTeX/Signer.h +++ b/src/IoTeX/Signer.h @@ -9,7 +9,6 @@ #include "Data.h" #include "proto/IoTeX.pb.h" -#include "Protobuf/action.pb.h" namespace TW::IoTeX { @@ -20,7 +19,7 @@ class Signer { static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; public: Proto::SigningInput input; - iotextypes::ActionCore action; + Proto::ActionCore action; /// Initializes a transaction signer Signer(const Proto::SigningInput& input) : input(input) { toActionCore(); } diff --git a/src/IoTeX/Staking.cpp b/src/IoTeX/Staking.cpp index 938be61ed9d..493b3a86013 100644 --- a/src/IoTeX/Staking.cpp +++ b/src/IoTeX/Staking.cpp @@ -5,56 +5,106 @@ // file LICENSE at the root of the source code distribution tree. #include "Staking.h" - -#include "Ethereum/ABI/Function.h" #include "Data.h" -#include "uint256.h" +#include "HexCoding.h" +using namespace TW; namespace TW::IoTeX { -using namespace TW::Ethereum::ABI; +const char* FromData(const Data& data) { + auto s = new std::string(data.begin(), data.end()); + s->append(data.size(), '\0'); + auto ss = reinterpret_cast(s); + return ss->data(); +} -void stakingStake(const Data& candidate, uint64_t stakeDuration, bool nonDecay, const Data& dataIn, Data& dataOut) { - Function func("createPygg"); - func.addInParam(std::make_shared(12, candidate)); - func.addInParam(std::make_shared(uint256_t(stakeDuration))); - func.addInParam(std::make_shared(nonDecay)); - func.addInParam(std::make_shared(dataIn)); +Data dataFromString(const std::string& d) { + Data data; + std::copy(d.c_str(), d.c_str() + d.length(), back_inserter(data)); + return data; +} - func.encode(dataOut); +Data stakingCreate(const Data& candidate, const Data& amount, uint32_t duration, bool autoStake, + const Data& payload) { + auto action = IoTeX::Proto::Staking_Create(); + action.set_candidatename(FromData(candidate)); + action.set_stakedamount(FromData(amount)); + action.set_stakedduration(duration); + action.set_autostake(autoStake); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingUnstake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut) { - Function func("unstake"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(dataIn)); +Data stakingAddDeposit(uint64_t index, const Data& amount, const Data& payload) { + auto action = IoTeX::Proto::Staking_AddDeposit(); + action.set_bucketindex(index); + action.set_amount(FromData(amount)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); +} - func.encode(dataOut); +Data stakingUnstake(uint64_t index, const Data& payload) { + auto action = IoTeX::Proto::Staking_Reclaim(); + action.set_bucketindex(index); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingWithdraw(uint64_t pyggIndex, const Data& dataIn, Data& dataOut) { - Function func("withdraw"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(dataIn)); +Data stakingWithdraw(uint64_t index, const Data& payload) { + auto action = IoTeX::Proto::Staking_Reclaim(); + action.set_bucketindex(index); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); +} - func.encode(dataOut); +Data stakingRestake(uint64_t index, uint32_t duration, bool autoStake, const Data& payload) { + auto action = IoTeX::Proto::Staking_Restake(); + action.set_bucketindex(index); + action.set_stakedduration(duration); + action.set_autostake(autoStake); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingAddStake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut) { - Function func("storeToPygg"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(dataIn)); +Data stakingChangeCandidate(uint64_t index, const Data& candidate, const Data& payload) { + auto action = IoTeX::Proto::Staking_ChangeCandidate(); + action.set_bucketindex(index); + action.set_candidatename(FromData(candidate)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); +} - func.encode(dataOut); +Data stakingTransfer(uint64_t index, const Data& voterAddress, const Data& payload) { + auto action = IoTeX::Proto::Staking_TransferOwnership(); + action.set_bucketindex(index); + action.set_voteraddress(FromData(voterAddress)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingMoveStake(uint64_t pyggIndex, const Data& candidate, const Data& dataIn, Data& dataOut) { - Function func("revote"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(12, candidate)); - func.addInParam(std::make_shared(dataIn)); +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(); + cbi->set_name(FromData(name)); + cbi->set_operatoraddress(FromData(operatorAddress)); + cbi->set_rewardaddress(FromData(rewardAddress)); - func.encode(dataOut); + auto action = IoTeX::Proto::Staking_CandidateRegister(); + action.set_allocated_candidate(cbi); + action.set_stakedamount(FromData(amount)); + action.set_stakedduration(duration); + action.set_autostake(autoStake); + action.set_owneraddress(FromData(ownerAddress)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } +Data candidateUpdate(const Data& name, const Data& operatorAddress, const Data& rewardAddress) { + auto action = IoTeX::Proto::Staking_CandidateBasicInfo(); + action.set_name(FromData(name)); + action.set_operatoraddress(FromData(operatorAddress)); + action.set_rewardaddress(FromData(rewardAddress)); + return dataFromString(action.SerializeAsString()); +} } // namespace TW::IoTeX diff --git a/src/IoTeX/Staking.h b/src/IoTeX/Staking.h index 9d5a6846be1..73a5e737473 100644 --- a/src/IoTeX/Staking.h +++ b/src/IoTeX/Staking.h @@ -7,22 +7,37 @@ #pragma once #include "Data.h" - +#include "proto/IoTeX.pb.h" namespace TW::IoTeX { -/// Function to generate Stake message -void stakingStake(const Data& candidate, uint64_t stakeDuration, bool nonDecay, const Data& dataIn, Data& dataOut); +/// Function to generate Create message +Data stakingCreate(const Data& candidate, const Data& amount, uint32_t duration, bool autoStake, + const Data& payload); + +/// Function to generate AddDeposit message +Data stakingAddDeposit(uint64_t index, const Data& amount, const Data& payload); /// Function to generate Unstake message -void stakingUnstake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut); +Data stakingUnstake(uint64_t index, const Data& payload); /// Function to generate Withdraw message -void stakingWithdraw(uint64_t pyggIndex, const Data& dataIn, Data& dataOut); +Data stakingWithdraw(uint64_t index, const Data& payload); + +/// Function to generate Restake message +Data stakingRestake(uint64_t index, uint32_t duration, bool autoStake, const Data& payload); + +/// Function to generate ChangeCandidate message +Data stakingChangeCandidate(uint64_t index, const Data& candidate, const Data& payload); + +/// Function to generate Transfer message +Data stakingTransfer(uint64_t index, const Data& voterAddress, const Data& payload); -/// Function to generate AddStake message -void stakingAddStake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut); +/// Function to generate candidate register message +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); -/// Function to generate MoveStake message -void stakingMoveStake(uint64_t pyggIndex, const Data& candidate, const Data& dataIn, Data& dataOut); +/// Function to generate candidate update message +Data candidateUpdate(const Data& name, const Data& operatorAddress, const Data& rewardAddress); } // namespace TW::IoTeX diff --git a/src/Keystore/Account.cpp b/src/Keystore/Account.cpp index 5a08b7b94bf..c27b7dbfca7 100644 --- a/src/Keystore/Account.cpp +++ b/src/Keystore/Account.cpp @@ -14,12 +14,13 @@ using namespace TW; using 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 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"; } // namespace CodingKeys Account::Account(const nlohmann::json& json) { @@ -33,6 +34,13 @@ Account::Account(const nlohmann::json& json) { derivationPath = DerivationPath(json[CodingKeys::derivationPath].get()); } + if (json.find(CodingKeys::coin) == json.end()) { + // legacy format, get coin from derivation path + coin = TWCoinType(uint32_t(derivationPath.indices[1].value)); + } else { + coin = TWCoinType(json[CodingKeys::coin].get()); + } + if (json.count(CodingKeys::address) != 0 && json[CodingKeys::address].is_string()) { address = json[CodingKeys::address].get(); } else { @@ -49,6 +57,7 @@ nlohmann::json Account::json() const { nlohmann::json j; j[CodingKeys::address] = address; j[CodingKeys::derivationPath] = derivationPath.string(); + j[CodingKeys::coin] = coin; if (!extendedPublicKey.empty()) { j[CodingKeys::extendedPublicKey] = extendedPublicKey; } diff --git a/src/Keystore/Account.h b/src/Keystore/Account.h index bf9b455b9ea..fe5c137fe9a 100644 --- a/src/Keystore/Account.h +++ b/src/Keystore/Account.h @@ -26,13 +26,14 @@ class Account { std::string extendedPublicKey; /// Coin this account is for. - TWCoinType coin() const { return derivationPath.coin(); } + TWCoinType coin; Account() = default; - Account(std::string address, DerivationPath derivationPath, std::string extendedPublicKey = "") + Account(std::string address, TWCoinType coin, DerivationPath derivationPath, std::string extendedPublicKey = "") : address(std::move(address)) , derivationPath(std::move(derivationPath)) - , extendedPublicKey(std::move(extendedPublicKey)) {} + , extendedPublicKey(std::move(extendedPublicKey)) + , coin(coin) {} /// Initializes `Account` with a JSON object. Account(const nlohmann::json& json); diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 44f4af3f921..6dcdc1a7e6f 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -20,7 +20,7 @@ using namespace TW; using namespace TW::Keystore; template -static Data computeMAC(Iter begin, Iter end, Data key) { +static Data computeMAC(Iter begin, Iter end, const Data& key) { auto data = Data(); data.reserve((end - begin) + key.size()); data.insert(data.end(), begin, end); @@ -28,7 +28,7 @@ static Data computeMAC(Iter begin, Iter end, Data key) { return Hash::keccak256(data); } -EncryptionParameters::EncryptionParameters(const std::string& password, Data data) : mac() { +EncryptionParameters::EncryptionParameters(const Data& password, const Data& data) : mac() { auto scryptParams = boost::get(kdfParams); auto derivedKey = Data(scryptParams.desiredKeyLength); scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), @@ -36,35 +36,36 @@ EncryptionParameters::EncryptionParameters(const std::string& password, Data dat scryptParams.desiredKeyLength); aes_encrypt_ctx ctx; - auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); - assert(result != EXIT_FAILURE); + auto result = aes_encrypt_key128(derivedKey.data(), &ctx); + assert(result == EXIT_SUCCESS); + if (result == EXIT_SUCCESS) { + Data iv = cipherParams.iv; + encrypted = Data(data.size()); + aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - Data iv = cipherParams.iv; - encrypted = Data(data.size()); - aes_ctr_encrypt(data.data(), encrypted.data(), data.size(), iv.data(), aes_ctr_cbuf_inc, &ctx); - - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + } } EncryptionParameters::~EncryptionParameters() { std::fill(encrypted.begin(), encrypted.end(), 0); } -Data EncryptionParameters::decrypt(const std::string& password) const { +Data EncryptionParameters::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(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), + 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(reinterpret_cast(password.data()), password.size(), pbkdf2Params.salt.data(), + pbkdf2_hmac_sha256(password.data(), password.size(), pbkdf2Params.salt.data(), pbkdf2Params.salt.size(), pbkdf2Params.iterations, derivedKey.data(), pbkdf2Params.defaultDesiredKeyLength); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); @@ -83,7 +84,7 @@ Data EncryptionParameters::decrypt(const std::string& password) const { auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); assert(result != EXIT_FAILURE); - aes_ctr_decrypt(encrypted.data(), decrypted.data(), encrypted.size(), iv.data(), + aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); } else if (cipher == "aes-128-cbc") { aes_decrypt_ctx ctx; diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index db2e49cc1ba..e1e68933977 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -46,7 +46,7 @@ struct EncryptionParameters { EncryptionParameters() = default; /// Initializes `EncryptionParameters` with standard values. - EncryptionParameters(Data encrypted, AESParameters cipherParams, boost::variant kdfParams, Data mac) + EncryptionParameters(const Data& encrypted, AESParameters cipherParams, boost::variant kdfParams, const Data& mac) : encrypted(std::move(encrypted)) , cipherParams(std::move(cipherParams)) , kdfParams(std::move(kdfParams)) @@ -54,13 +54,13 @@ struct EncryptionParameters { /// Initializes `EncryptionParameters` by encrypting data with a password /// using standard values. - EncryptionParameters(const std::string& password, Data data); + EncryptionParameters(const Data& password, const Data& data); /// Initializes `EncryptionParameters` with a JSON object. EncryptionParameters(const nlohmann::json& json); /// Decrypts the payload with the given password. - Data decrypt(const std::string& password) const; + Data decrypt(const Data& password) const; /// Saves `this` as a JSON object. nlohmann::json json() const; diff --git a/src/Keystore/PBKDF2Parameters.h b/src/Keystore/PBKDF2Parameters.h index b99ab79d786..0d473b5615b 100644 --- a/src/Keystore/PBKDF2Parameters.h +++ b/src/Keystore/PBKDF2Parameters.h @@ -35,7 +35,7 @@ struct PBKDF2Parameters { PBKDF2Parameters(); /// Initializes `PBKDF2Parameters` with all values. - PBKDF2Parameters(Data salt, uint32_t iterations, std::size_t desiredKeyLength) + PBKDF2Parameters(const Data& salt, uint32_t iterations, std::size_t desiredKeyLength) : salt(std::move(salt)), desiredKeyLength(desiredKeyLength), iterations(iterations) {} /// Initializes `PBKDF2Parameters` with a JSON object. diff --git a/src/Keystore/ScryptParameters.cpp b/src/Keystore/ScryptParameters.cpp index 685301fba3d..16ccd489c4c 100644 --- a/src/Keystore/ScryptParameters.cpp +++ b/src/Keystore/ScryptParameters.cpp @@ -16,8 +16,10 @@ ScryptParameters::ScryptParameters() : salt(32) { random_buffer(salt.data(), salt.size()); } +#pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" + std::optional ScryptParameters::validate() const { - if (desiredKeyLength > ((static_cast(1) << 32) - 1) * 32) { + 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)) { diff --git a/src/Keystore/ScryptParameters.h b/src/Keystore/ScryptParameters.h index 460dc4b8e64..10e7c019bd6 100644 --- a/src/Keystore/ScryptParameters.h +++ b/src/Keystore/ScryptParameters.h @@ -66,7 +66,7 @@ struct ScryptParameters { /// Initializes `ScryptParameters` with all values. /// /// @throws ScryptValidationError if the parameters are invalid. - ScryptParameters(Data salt, uint32_t n, uint32_t r, uint32_t p, std::size_t desiredKeyLength) + ScryptParameters(const 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) { diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 476fa336d7e..6c0032b6752 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -27,7 +27,7 @@ using namespace TW; using namespace TW::Keystore; -StoredKey StoredKey::createWithMnemonic(const std::string& name, const std::string& password, const std::string& mnemonic) { +StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic) { if (!HDWallet::isValid(mnemonic)) { throw std::invalid_argument("Invalid mnemonic"); } @@ -37,7 +37,7 @@ StoredKey StoredKey::createWithMnemonic(const std::string& name, const std::stri return key; } -StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const std::string& password) { +StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password) { const auto wallet = TW::HDWallet(128, ""); const auto& mnemonic = wallet.mnemonic; assert(HDWallet::isValid(mnemonic)); @@ -46,24 +46,24 @@ StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const std return key; } -StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const std::string& password, const std::string& mnemonic, TWCoinType coin) { +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(derivationPath)); + 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, derivationPath, extendedKey); + key.accounts.emplace_back(address, coin, derivationPath, extendedKey); return key; } -StoredKey StoredKey::createWithPrivateKey(const std::string& name, const std::string& password, const Data& privateKeyData) { +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::createWithPrivateKeyAddDefaultAddress(const std::string& name, const std::string& password, TWCoinType coin, const Data& privateKeyData) { +StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData) { const auto curve = TW::curve(coin); if (!PrivateKey::isValid(privateKeyData, curve)) { throw std::invalid_argument("Invalid private key data"); @@ -73,18 +73,18 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na const auto derivationPath = TW::derivationPath(coin); const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); - key.accounts.emplace_back(address, derivationPath); + key.accounts.emplace_back(address, coin, derivationPath); return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const std::string& password, Data data) +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()); } -const HDWallet StoredKey::wallet(const std::string& password) const { +const HDWallet StoredKey::wallet(const Data& password) const { if (type != StoredKeyType::mnemonicPhrase) { throw std::invalid_argument("Invalid account requested."); } @@ -95,7 +95,7 @@ const HDWallet StoredKey::wallet(const std::string& password) const { const Account* StoredKey::account(TWCoinType coin) const { for (auto& account : accounts) { - if (account.coin() == coin) { + if (account.coin == coin) { return &account; } } @@ -109,7 +109,7 @@ const Account* StoredKey::account(TWCoinType coin, const HDWallet* wallet) { assert(wallet != nullptr); for (auto& account : accounts) { - if (account.coin() == coin) { + if (account.coin == coin) { if (account.address.empty()) { account.address = wallet->deriveAddress(coin); } @@ -123,45 +123,43 @@ const Account* StoredKey::account(TWCoinType coin, const HDWallet* wallet) { const auto version = TW::xpubVersion(coin); const auto extendedPublicKey = wallet->getExtendedPublicKey(derivationPath.purpose(), coin, version); - accounts.emplace_back(address, derivationPath, extendedPublicKey); + accounts.emplace_back(address, coin, derivationPath, extendedPublicKey); return &accounts.back(); } -void StoredKey::addAccount(const std::string& address, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) { - accounts.emplace_back(address, derivationPath, extetndedPublicKey); +void StoredKey::addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) { + accounts.emplace_back(address, coin, derivationPath, extetndedPublicKey); } void StoredKey::removeAccount(TWCoinType coin) { accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool { - return account.coin() == coin; - } - ), accounts.end()); + return account.coin == coin; + }), accounts.end()); } - -const PrivateKey StoredKey::privateKey(TWCoinType coin, const std::string& password) { +const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { switch (type) { case StoredKeyType::mnemonicPhrase: { const auto wallet = this->wallet(password); const auto account = *this->account(coin, &wallet); - return wallet.getKey(account.derivationPath); + return wallet.getKey(coin, account.derivationPath); } case StoredKeyType::privateKey: return PrivateKey(payload.decrypt(password)); } } -void StoredKey::fixAddresses(const std::string& password) { +void StoredKey::fixAddresses(const Data& password) { switch (type) { case StoredKeyType::mnemonicPhrase: { const auto wallet = this->wallet(password); for (auto& account : accounts) { - if (!account.address.empty() && TW::validateAddress(account.coin(), account.address)) { + if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) { continue; } const auto& derivationPath = account.derivationPath; - const auto key = wallet.getKey(derivationPath); - account.address = TW::deriveAddress(derivationPath.coin(), key); + const auto key = wallet.getKey(account.coin, derivationPath); + account.address = TW::deriveAddress(account.coin, key); } } break; @@ -169,10 +167,10 @@ void StoredKey::fixAddresses(const std::string& password) { case StoredKeyType::privateKey: { auto key = PrivateKey(payload.decrypt(password)); for (auto& account : accounts) { - if (!account.address.empty() && TW::validateAddress(account.coin(), account.address)) { + if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) { continue; } - account.address = TW::deriveAddress(account.coin(), key); + account.address = TW::deriveAddress(account.coin, key); } } break; @@ -191,23 +189,23 @@ StoredKey StoredKey::createWithJson(const nlohmann::json& json) { } 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"; + 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 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) { @@ -248,7 +246,7 @@ void StoredKey::loadJson(const nlohmann::json& json) { coin = json[CodingKeys::coin].get(); } auto address = json[CodingKeys::address].get(); - accounts.emplace_back(address, DerivationPath(TWPurposeBIP44, coin, 0, 0, 0)); + accounts.emplace_back(address, coin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0)); } } diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index c742ba5423a..4ec24ad2211 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -43,23 +43,23 @@ class StoredKey { /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonic(const std::string& name, const std::string& password, const std::string& mnemonic); + static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic); /// 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 std::string& password); + static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password); /// 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 std::string& password, const std::string& mnemonic, TWCoinType coin); + static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin); /// 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 std::string& password, const Data& privateKeyData); + static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData); /// 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 std::string& password, TWCoinType coin, const Data& privateKeyData); + static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData); /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -67,7 +67,7 @@ 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 std::string& password) const; + const HDWallet wallet(const Data& password) const; /// Returns the account for a specific coin, creating it if necessary and /// the provided wallet is not `nullptr`. @@ -77,7 +77,7 @@ class StoredKey { const Account* account(TWCoinType coin) const; /// Add an account - void addAccount(const std::string& address, const DerivationPath& derivationPath, const std::string& extetndedPublicKey); + void addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey); /// Remove the account for a specific coin void removeAccount(TWCoinType coin); @@ -86,7 +86,7 @@ class StoredKey { /// /// @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 std::string& password); + const PrivateKey privateKey(TWCoinType coin, const Data& password); /// Loads and decrypts a stored key from a file. /// @@ -110,7 +110,7 @@ class StoredKey { /// /// 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 std::string& password); + void fixAddresses(const Data& password); private: /// Default constructor, private @@ -119,7 +119,7 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This contstructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const std::string& password, Data data); + StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data); }; } // namespace TW::Keystore diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index 9e69a1951cf..4b6e24826de 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -30,7 +30,7 @@ static void writeU128(Data& data, const std::string& numberData) { 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)); } diff --git a/src/NEO/Address.h b/src/NEO/Address.h index bd2afc2a44a..d3d78ab7088 100644 --- a/src/NEO/Address.h +++ b/src/NEO/Address.h @@ -23,13 +23,13 @@ class Address : public TW::Base58Address { static const byte version = 0x17; /// Determines whether a string makes a valid NEO address. - static bool isValid(const std::string &string); + static bool isValid(const std::string& string); /// Initializes a NEO address with a string representation. - explicit Address(const std::string &string) : TW::Base58Address(string) {} + explicit Address(const std::string& string) : TW::Base58Address(string) {} /// Initializes a NEO address with a collection of bytes. - explicit Address(const Data &data) : TW::Base58Address(data) {} + 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); @@ -40,7 +40,7 @@ class Address : public TW::Base58Address { /// Initializes a NEO address without a public key. explicit Address(); - Data toScriptHash(const Data &data) const; + Data toScriptHash(const Data& data) const; Data toScriptHash() const; }; diff --git a/src/NEO/CoinReference.h b/src/NEO/CoinReference.h index 2b040923ede..d21b8865630 100644 --- a/src/NEO/CoinReference.h +++ b/src/NEO/CoinReference.h @@ -29,7 +29,7 @@ class CoinReference : public Serializable { return Hash::sha256Size + prevIndexSize; } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { prevHash = load(readBytes(data, Hash::sha256Size, initial_pos)); prevIndex = decode16LE(data.data() + initial_pos + Hash::sha256Size); } diff --git a/src/NEO/Entry.h b/src/NEO/Entry.h index 784786b99cc..f381a4c6cab 100644 --- a/src/NEO/Entry.h +++ b/src/NEO/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual 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; - // normalizeAddress is not used 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; diff --git a/src/NEO/ISerializable.h b/src/NEO/ISerializable.h index 3106328cdc3..64e30ee0754 100644 --- a/src/NEO/ISerializable.h +++ b/src/NEO/ISerializable.h @@ -17,7 +17,7 @@ class ISerializable { virtual ~ISerializable() {} virtual int64_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, int initial_pos = 0) = 0; }; } // namespace TW::NEO diff --git a/src/NEO/MinerTransaction.h b/src/NEO/MinerTransaction.h index 01e3235fb06..ca73b306958 100644 --- a/src/NEO/MinerTransaction.h +++ b/src/NEO/MinerTransaction.h @@ -15,7 +15,7 @@ class MinerTransaction : public Transaction { public: uint32_t nonce; - virtual int deserializeExclusiveData(const Data &data, int initial_pos = 0) { + virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { nonce = decode32LE(data.data() + initial_pos); return initial_pos + 4; } diff --git a/src/NEO/ReadData.cpp b/src/NEO/ReadData.cpp index e419f944319..f7444e6af3c 100644 --- a/src/NEO/ReadData.cpp +++ b/src/NEO/ReadData.cpp @@ -8,14 +8,14 @@ #include "ReadData.h" -TW::Data TW::readBytes(const TW::Data &from, int max, int initial_pos) { +TW::Data TW::readBytes(const TW::Data& from, int max, int 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); } -TW::Data TW::readVarBytes(const Data &from, int initial_pos, uint32_t* dataRead) { +TW::Data TW::readVarBytes(const Data& from, int initial_pos, uint32_t* dataRead) { uint64_t size = readVar(from, initial_pos); auto shift = varIntSize(size); if (dataRead) { @@ -24,7 +24,7 @@ TW::Data TW::readVarBytes(const Data &from, int initial_pos, uint32_t* dataRead) return readBytes(from, int(size), initial_pos + int(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, int initial_pos, const uint64_t &max) { byte fb = from[initial_pos]; uint64_t value; if (fb == 0xFD) { @@ -43,11 +43,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, int 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, int 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 80b86693f02..7f7e0a0b4c7 100644 --- a/src/NEO/ReadData.h +++ b/src/NEO/ReadData.h @@ -13,14 +13,14 @@ 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, int max, int initial_pos = 0); +Data readVarBytes(const Data& from, int 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, 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); -Data writeVarBytes(const Data &from, int initial_pos = 0); +Data writeVarBytes(const Data& from, int initial_pos = 0); template static std::vector concat(const std::vector& v1, const std::vector& v2) { diff --git a/src/NEO/Script.cpp b/src/NEO/Script.cpp index 6e89610335b..a168dfe74bb 100644 --- a/src/NEO/Script.cpp +++ b/src/NEO/Script.cpp @@ -8,7 +8,7 @@ namespace TW::NEO { -Data Script::CreateSignatureRedeemScript(Data publicKey) { +Data Script::CreateSignatureRedeemScript(const Data& publicKey) { Data result; result.push_back((byte)PUSHBYTES21); result.insert(result.end(), publicKey.begin(), publicKey.end()); @@ -16,7 +16,7 @@ Data Script::CreateSignatureRedeemScript(Data publicKey) { return result; } -Data Script::CreateInvocationScript(Data signature) { +Data Script::CreateInvocationScript(const Data& signature) { Data result; result.push_back((byte)PUSHBYTES40); result.insert(result.end(), signature.begin(), signature.end()); diff --git a/src/NEO/Script.h b/src/NEO/Script.h index 5cbd4a59d82..64d47982754 100644 --- a/src/NEO/Script.h +++ b/src/NEO/Script.h @@ -11,8 +11,8 @@ namespace TW::NEO { class Script { public: - static Data CreateSignatureRedeemScript(Data publicKey); - static Data CreateInvocationScript(Data signature); + static Data CreateSignatureRedeemScript(const Data& publicKey); + static Data CreateInvocationScript(const Data& signature); }; } // namespace TW::NEO diff --git a/src/NEO/Serializable.h b/src/NEO/Serializable.h index 423253b861b..51ec20edf73 100644 --- a/src/NEO/Serializable.h +++ b/src/NEO/Serializable.h @@ -44,12 +44,9 @@ class Serializable : public ISerializable { } template - static inline int deserialize(std::vector &resp, const Data &data, int initial_pos = 0) { + static inline int deserialize(std::vector &resp, const Data& data, int initial_pos = 0) { uint64_t size = readVar(data, initial_pos, INT_MAX); - if (size < 0) { - throw std::invalid_argument("Serializable::deserialize ArgumentOutOfRangeException"); - } - + // assert(size >= 0); initial_pos += varIntSize(size); for (uint64_t i = 0; i < size; ++i) { T value; diff --git a/src/NEO/Transaction.cpp b/src/NEO/Transaction.cpp index 897ec967714..6c352b36894 100644 --- a/src/NEO/Transaction.cpp +++ b/src/NEO/Transaction.cpp @@ -21,7 +21,7 @@ int64_t Transaction::size() const { return serialize().size(); } -void Transaction::deserialize(const Data &data, int initial_pos) { +void Transaction::deserialize(const Data& data, int initial_pos) { type = (TransactionType) data[initial_pos++]; version = data[initial_pos++]; initial_pos = deserializeExclusiveData(data, initial_pos); @@ -33,7 +33,7 @@ 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 * Transaction::deserializeFrom(const Data& data, int initial_pos) { Transaction * resp = nullptr; switch ((TransactionType) data[initial_pos]) { case TransactionType::TT_MinerTransaction: diff --git a/src/NEO/Transaction.h b/src/NEO/Transaction.h index 5fb387f5f47..50a311e3d9a 100644 --- a/src/NEO/Transaction.h +++ b/src/NEO/Transaction.h @@ -28,18 +28,18 @@ class Transaction : public Serializable { virtual ~Transaction() {} int64_t size() const override; - void deserialize(const Data &data, int initial_pos = 0) override; + void deserialize(const Data& data, int initial_pos = 0) override; Data serialize() const override; bool operator==(const Transaction &other) const; - virtual int deserializeExclusiveData(const Data &data, int initial_pos = 0) { return initial_pos; } + virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { return initial_pos; } virtual Data serializeExclusiveData() const { return Data(); } Data getHash() const; uint256_t getHashUInt256() const; - static Transaction * deserializeFrom(const Data &data, int initial_pos = 0); + static Transaction * deserializeFrom(const Data& data, int initial_pos = 0); }; } // namespace TW::NEO diff --git a/src/NEO/TransactionAttribute.h b/src/NEO/TransactionAttribute.h index 3978484a614..7a5187e65c0 100644 --- a/src/NEO/TransactionAttribute.h +++ b/src/NEO/TransactionAttribute.h @@ -24,7 +24,7 @@ class TransactionAttribute : public Serializable { return 1 + data.size(); } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { if (data.size() < initial_pos + 1) { throw std::invalid_argument("Invalid data for deserialization"); } diff --git a/src/NEO/TransactionOutput.h b/src/NEO/TransactionOutput.h index 85d05d7d297..1c27d3f3781 100644 --- a/src/NEO/TransactionOutput.h +++ b/src/NEO/TransactionOutput.h @@ -31,7 +31,7 @@ class TransactionOutput : public Serializable { return store(assetId).size() + valueSize + store(scriptHash).size(); } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { assetId = load(readBytes(data, assetIdSize, initial_pos)); value = decode64LE(data.data() + initial_pos + assetIdSize); scriptHash = load(readBytes(data, scriptHashSize, initial_pos + assetIdSize + valueSize)); diff --git a/src/NEO/Witness.h b/src/NEO/Witness.h index 808037b7dcf..6f0e7c9d139 100644 --- a/src/NEO/Witness.h +++ b/src/NEO/Witness.h @@ -23,7 +23,7 @@ class Witness : public Serializable { return invocationScript.size() + verificationScript.size(); } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { uint32_t size; invocationScript = readVarBytes(data, initial_pos, &size); verificationScript = readVarBytes(data, initial_pos + size); diff --git a/src/Nebulas/Address.cpp b/src/Nebulas/Address.cpp index acdc595d17d..bbc81447f43 100644 --- a/src/Nebulas/Address.cpp +++ b/src/Nebulas/Address.cpp @@ -11,7 +11,7 @@ using namespace TW::Nebulas; -bool Address::isValid(const std::string &string) { +bool Address::isValid(const std::string& string) { auto data = Base58::bitcoin.decode(string); if (data.size() != (size_t)Address::size) { return false; @@ -30,7 +30,7 @@ bool Address::isValid(const std::string &string) { return ::memcmp(dataSha3.data(), checksum.data(), 4) == 0; } -Address::Address(const std::string &string) { +Address::Address(const std::string& string) { if (!isValid(string)) { throw std::invalid_argument("Invalid address string"); } @@ -39,7 +39,7 @@ Address::Address(const std::string &string) { std::copy(data.begin(), data.end(), bytes.begin()); } -Address::Address(const Data &data) { +Address::Address(const Data& data) { if (!Base58Address::isValid(data)) { throw std::invalid_argument("Invalid address data"); } diff --git a/src/Polkadot/Extrinsic.cpp b/src/Polkadot/Extrinsic.cpp index 9aae2d2e0a4..c05dea2e6ed 100644 --- a/src/Polkadot/Extrinsic.cpp +++ b/src/Polkadot/Extrinsic.cpp @@ -5,12 +5,52 @@ // 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 const std::string balanceTransfer = "Balances.transfer"; +static const std::string stakingBond = "Staking.bond"; +static const std::string stakingBondExtra = "Staking.bond_extra"; +static const std::string stakingUnbond = "Staking.unbond"; +static const std::string stakingWithdrawUnbond = "Staking.withdraw_unbonded"; +static const std::string stakingNominate = "Staking.nominate"; +static const std::string stakingChill = "Staking.chill"; + +static std::map polkadotCallIndices = { + {balanceTransfer, Data{0x05, 0x00}}, + {stakingBond, Data{0x07, 0x00}}, + {stakingBondExtra, Data{0x07, 0x01}}, + {stakingUnbond, Data{0x07, 0x02}}, + {stakingWithdrawUnbond, Data{0x07, 0x03}}, + {stakingNominate, Data{0x07, 0x05}}, + {stakingChill, Data{0x07, 0x06}}, +}; + +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]; + } +} Data Extrinsic::encodeEraNonceTip() const { Data data; @@ -24,14 +64,15 @@ Data Extrinsic::encodeEraNonceTip() const { } Data Extrinsic::encodeCall(const Proto::SigningInput& input) { - // call index from MetadataV10 + // call index from MetadataV11 Data data; + auto network = TWSS58AddressType(input.network()); if (input.has_balance_call()) { auto transfer = input.balance_call().transfer(); - auto address = SS58Address(transfer.to_address(), byte(input.network())); + auto address = SS58Address(transfer.to_address(), network); auto value = load(transfer.value()); // call index - append(data, {0x04, 0x00}); + append(data, getCallIndex(network, balanceTransfer)); // destination append(data, encodeAddress(address)); // value @@ -39,11 +80,11 @@ Data Extrinsic::encodeCall(const Proto::SigningInput& input) { } else if (input.has_staking_call()) { auto staking = input.staking_call(); if (staking.has_bond()) { - auto address = SS58Address(staking.bond().validator(), byte(input.network())); + auto address = SS58Address(staking.bond().controller(), byte(input.network())); auto value = load(staking.bond().value()); auto reward = byte(staking.bond().reward_destination()); // call index - append(data, {0x06, 0x00}); + append(data, getCallIndex(network, stakingBond)); // validator append(data, encodeAddress(address)); // value @@ -53,30 +94,33 @@ Data Extrinsic::encodeCall(const Proto::SigningInput& input) { } else if (staking.has_bond_extra()) { auto value = load(staking.unbond().value()); // call index - append(data, {0x06, 0x01}); + append(data, getCallIndex(network, stakingBondExtra)); // value append(data, encodeCompact(value)); } else if (staking.has_unbond()) { auto value = load(staking.unbond().value()); // call index - append(data, {0x06, 0x02}); + append(data, getCallIndex(network, stakingUnbond)); // value append(data, encodeCompact(value)); } else if (staking.has_withdraw_unbonded()) { + auto spans = staking.withdraw_unbonded().slashing_spans(); // call index - append(data, {0x06, 0x03}); + append(data, getCallIndex(network, stakingWithdrawUnbond)); + // num_slashing_spans + encode32LE(spans, data); } else if (staking.has_nominate()) { std::vector addresses; for (auto& n : staking.nominate().nominators()) { - addresses.push_back(SS58Address(n, byte(input.network()))); + addresses.push_back(SS58Address(n, network)); } // call index - append(data, {0x06, 0x05}); + append(data, getCallIndex(network, stakingNominate)); // nominators append(data, encodeAddresses(addresses)); } else if (staking.has_chill()) { // call index - append(data, {0x06, 0x06}); + append(data, getCallIndex(network, stakingChill)); } } return data; @@ -90,6 +134,8 @@ Data Extrinsic::encodePayload() const { append(data, encodeEraNonceTip()); // specVersion encode32LE(specVersion, data); + // transactionVersion + encode32LE(version, data); // genesis hash append(data, genesisHash); // block hash @@ -100,7 +146,7 @@ Data Extrinsic::encodePayload() const { Data Extrinsic::encodeSignature(const PublicKey& signer, const Data& signature) const { Data data; // version header - append(data, Data{ static_cast(version | signedBit)}); + append(data, Data{extrinsicFormat | signedBit}); // signer public key append(data, encodeAddress(signer)); // signature type diff --git a/src/Polkadot/Extrinsic.h b/src/Polkadot/Extrinsic.h index 3e25a39bc6e..2d3c759f8aa 100644 --- a/src/Polkadot/Extrinsic.h +++ b/src/Polkadot/Extrinsic.h @@ -14,6 +14,7 @@ namespace TW::Polkadot { +// ExtrinsicV4 class Extrinsic { public: Data blockHash; @@ -21,7 +22,7 @@ class Extrinsic { uint64_t nonce; // Runtime spec version uint32_t specVersion; - // Extrinsic version + // transaction version uint32_t version; // balances::TakeFees uint256_t tip; @@ -35,7 +36,7 @@ class Extrinsic { , genesisHash(input.genesis_hash().begin(), input.genesis_hash().end()) , nonce(input.nonce()) , specVersion(input.spec_version()) - , version(input.extrinsic_version()) + , version(input.transaction_version()) , tip(load(input.tip())) { if (input.has_era()) { era = encodeEra(input.era().phase(), input.era().period()); diff --git a/src/Polkadot/ScaleCodec.h b/src/Polkadot/ScaleCodec.h index abe79f63cf1..c9fea779452 100644 --- a/src/Polkadot/ScaleCodec.h +++ b/src/Polkadot/ScaleCodec.h @@ -99,13 +99,13 @@ inline Data encodeVector(std::vector& vec) { } inline Data encodeAddress(const PublicKey& key) { - auto data = Data{0xff}; + auto data = Data{}; append(data, Data(key.bytes.begin(), key.bytes.end())); return data; } inline Data encodeAddress(const SS58Address& address) { - auto data = Data{0xff}; + auto data = Data{}; // first byte is network append(data, Data(address.bytes.begin() + 1, address.bytes.end())); return data; @@ -121,8 +121,8 @@ inline Data encodeAddresses(const std::vector& addresses) { 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/primitive/Extrinsic/ExtrinsicEra.ts#L74 - // See toU8a at https://github.com/polkadot-js/api/blob/master/packages/types/src/primitive/Extrinsic/ExtrinsicEra.ts#L141 + // 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; diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 7e70a9a0110..75e23e0f8b5 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -57,6 +57,8 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) case TWCurveED25519Blake2bNano: case TWCurveED25519Extended: case TWCurveCurve25519: + case TWCurveNone: + default: break; } @@ -89,7 +91,7 @@ PrivateKey::PrivateKey(const Data& data) { } PrivateKey::PrivateKey(const Data& data, const Data& ext, const Data& chainCode) { - if (!isValid(data) || !isValid(data) || !isValid(chainCode)) { + if (!isValid(data) || !isValid(ext) || !isValid(chainCode)) { throw std::invalid_argument("Invalid private key or extended key data"); } bytes = data; @@ -151,7 +153,7 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { Data PrivateKey::sign(const Data& digest, TWCurve curve) const { Data result; - bool success = true; + bool success = false; switch (curve) { case TWCurveSECP256k1: { result.resize(65); @@ -162,17 +164,20 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { 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); @@ -182,12 +187,16 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { 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(&nist256p1, bytes.data(), digest.data(), result.data(), result.data() + 64, nullptr) == 0; } break; + case TWCurveNone: + default: + break; } if (!success) { @@ -215,6 +224,9 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)( success = ecdsa_sign_digest(&nist256p1, bytes.data(), digest.data(), result.data() + 1, result.data(), canonicalChecker) == 0; } break; + case TWCurveNone: + default: + break; } if (!success) { @@ -254,10 +266,13 @@ Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { case TWCurveED25519: case TWCurveED25519Blake2bNano: case TWCurveED25519Extended: - case TWCurveCurve25519: { + case TWCurveCurve25519: + case TWCurveNone: + default: // not support - } break; + break; } + if (!success) { return {}; } diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 8628881e950..97a3faedf59 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -68,11 +68,8 @@ PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) : type(type) { break; case TWPublicKeyTypeED25519Blake2b: bytes.reserve(ed25519Size); - if (data.size() == ed25519Size + 1) { - std::copy(std::begin(data) + 1, std::end(data), std::back_inserter(bytes)); - } else { - std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); - } + 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); @@ -86,17 +83,19 @@ PublicKey PublicKey::compressed() const { } Data newBytes(secp256k1Size); + assert(bytes.size() >= 65); newBytes[0] = 0x02 | (bytes[64] & 0x01); + assert(type == TWPublicKeyTypeSECP256k1Extended || type == TWPublicKeyTypeNIST256p1Extended); switch (type) { case TWPublicKeyTypeSECP256k1Extended: std::copy(bytes.begin() + 1, bytes.begin() + secp256k1Size, newBytes.begin() + 1); return PublicKey(newBytes, TWPublicKeyTypeSECP256k1); + case TWPublicKeyTypeNIST256p1Extended: + default: std::copy(bytes.begin() + 1, bytes.begin() + secp256k1Size, newBytes.begin() + 1); return PublicKey(newBytes, TWPublicKeyTypeNIST256p1); - default: - return *this; } } @@ -180,4 +179,19 @@ 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) { + throw std::invalid_argument("signature too short"); + } + auto v = signature[64]; + if (v >= 27) { + v -= 27; + } + 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"); + } + return PublicKey(result, TWPublicKeyTypeSECP256k1Extended); +} + } // namespace TW diff --git a/src/PublicKey.h b/src/PublicKey.h index ed9d975fe7c..368d87b40a6 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -69,6 +69,9 @@ class PublicKey { /// 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; + + /// Recover public key from signature (SECP256k1Extended) + static PublicKey recover(const Data& signature, const Data& message); }; inline bool operator==(const PublicKey& lhs, const PublicKey& rhs) { diff --git a/src/Solana/Address.cpp b/src/Solana/Address.cpp index a45bcc3ba94..93460cdf9ea 100644 --- a/src/Solana/Address.cpp +++ b/src/Solana/Address.cpp @@ -41,8 +41,8 @@ Data Address::vector() const { return vec; } -Address addressFromValidatorSeed(Address& fromAddress, Address& validatorAddress, - Address& programId) { +Address 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()); diff --git a/src/Solana/Address.h b/src/Solana/Address.h index fda8c909415..8bdc71ec85e 100644 --- a/src/Solana/Address.h +++ b/src/Solana/Address.h @@ -40,6 +40,6 @@ class Address : public Base58Address<32> { } // namespace TW::Solana -TW::Solana::Address addressFromValidatorSeed(TW::Solana::Address& fromAddress, - TW::Solana::Address& validatorAddress, - TW::Solana::Address& programId); +TW::Solana::Address addressFromValidatorSeed(const TW::Solana::Address& fromAddress, + const TW::Solana::Address& validatorAddress, + const TW::Solana::Address& programId); diff --git a/src/Solana/Transaction.h b/src/Solana/Transaction.h index 12ffdbe9c6f..ce561b8c3e7 100644 --- a/src/Solana/Transaction.h +++ b/src/Solana/Transaction.h @@ -54,13 +54,13 @@ struct CompiledInstruction { // The program input data Data data; - CompiledInstruction(uint8_t programIdIndex, Data accounts, Data data) + CompiledInstruction(uint8_t programIdIndex, const Data& accounts, const Data& data) : programIdIndex(programIdIndex), accounts(accounts), data(data) {} // This constructor creates a default System Transfer instruction - CompiledInstruction(uint8_t programIdIndex, uint64_t value) : programIdIndex(programIdIndex) { - std::vector accounts = {0, 1}; - this->accounts = accounts; + CompiledInstruction(uint8_t programIdIndex, Data accountIndexes, uint64_t value) + : programIdIndex(programIdIndex) { + this->accounts = accountIndexes; SystemInstruction type = Transfer; auto data = Data(); encode32LE(static_cast(type), data); @@ -198,10 +198,21 @@ class Message { MessageHeader header = {1, 0, 1}; this->header = header; auto programId = Address("11111111111111111111111111111111"); - std::vector
accountKeys = {from, to, programId}; + std::vector
accountKeys; + Data accountIndexes; + uint8_t programIdIndex; + if (from.vector() != to.vector()) { + accountKeys = {from, to, programId}; + accountIndexes = {0, 1}; + programIdIndex = 2; + } else { + accountKeys = {from, programId}; + accountIndexes = {0, 0}; + programIdIndex = 1; + } this->accountKeys = accountKeys; std::vector instructions; - auto instruction = CompiledInstruction(2, value); + auto instruction = CompiledInstruction(programIdIndex, accountIndexes, value); instructions.push_back(instruction); this->instructions = instructions; } diff --git a/src/TON/Cell.cpp b/src/TON/Cell.cpp index af70dbfb3c2..be52dc56130 100644 --- a/src/TON/Cell.cpp +++ b/src/TON/Cell.cpp @@ -41,7 +41,7 @@ Slice Slice::createFromHex(std::string const& dataStr) { } Slice Slice::createFromBits(const Data& data, size_t sizeBits) { - if (sizeBits <= 0) { + if (sizeBits == 0) { throw std::runtime_error("empty data"); } Slice s; diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index 0c4aae8ba1f..b0732fd78c9 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -45,7 +45,7 @@ Data Signer::signOperationList(const PrivateKey& privateKey, const OperationList return signData(privateKey, forged); } -Data Signer::signData(const PrivateKey& privateKey, Data data) { +Data Signer::signData(const PrivateKey& privateKey, const Data& data) { Data watermarkedData = Data(); watermarkedData.push_back(0x03); append(watermarkedData, data); diff --git a/src/Tezos/Signer.h b/src/Tezos/Signer.h index d6b88fcc6b5..798d703ab21 100644 --- a/src/Tezos/Signer.h +++ b/src/Tezos/Signer.h @@ -25,7 +25,7 @@ class Signer { public: /// Signs the given transaction. Data signOperationList(const PrivateKey& privateKey, const OperationList& operationList); - Data signData(const PrivateKey& privateKey, Data data); + Data signData(const PrivateKey& privateKey, const Data& data); }; } // namespace TW::Tezos diff --git a/src/Theta/Transaction.h b/src/Theta/Transaction.h index fccc7e5439d..735c21ab1a8 100644 --- a/src/Theta/Transaction.h +++ b/src/Theta/Transaction.h @@ -24,7 +24,7 @@ class TxInput { TxInput(Ethereum::Address address, Coins coins, uint64_t sequence) : address(std::move(address)), coins(std::move(coins)), sequence(sequence) {} - TxInput(Ethereum::Address address, Coins coins, uint64_t sequence, Data signature) + TxInput(Ethereum::Address address, Coins coins, uint64_t sequence, const Data& signature) : address(std::move(address)), coins(std::move(coins)), sequence(sequence), signature(std::move(signature)) {} }; diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index 1838bf30fc4..3005227d6c7 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -157,8 +157,8 @@ protocol::TriggerSmartContract to_internal(const Proto::TriggerSmartContract& tr protocol::TriggerSmartContract to_internal(const Proto::TransferTRC20Contract& transferTrc20Contract) { auto toAddress = Base58::bitcoin.decodeCheck(transferTrc20Contract.to_address()); - Data amount; - encode64BE(transferTrc20Contract.amount(), amount); + // amount is 256 bits, big endian + Data amount = data(transferTrc20Contract.amount()); // Encode smart contract call parameters auto contract_params = parse_hex(TRANSFER_TOKEN_FUNCTION); diff --git a/src/VeChain/Clause.h b/src/VeChain/Clause.h index eb8d1e5dd06..9bbb1bc6435 100644 --- a/src/VeChain/Clause.h +++ b/src/VeChain/Clause.h @@ -19,7 +19,7 @@ class Clause { uint256_t value; Data data; - Clause(Ethereum::Address to, uint256_t value, Data data = {}) + Clause(Ethereum::Address to, uint256_t value, const Data& data = {}) : to(std::move(to)), value(std::move(value)), data(std::move(data)) {} /// Decodes from a proto representation. diff --git a/src/Waves/Address.cpp b/src/Waves/Address.cpp index 7b42ff51dc0..be51732e937 100644 --- a/src/Waves/Address.cpp +++ b/src/Waves/Address.cpp @@ -22,7 +22,7 @@ Data Address::secureHash(const T &data) { return Hash::keccak256(Hash::blake2b(data, 32)); } -bool Address::isValid(const Data &decoded) { +bool Address::isValid(const Data& decoded) { if (decoded.size() != Address::size) { return false; } @@ -44,12 +44,12 @@ bool Address::isValid(const Data &decoded) { return std::memcmp(data_checksum.data(), calculated_checksum.data(), 4) == 0; } -bool Address::isValid(const std::string &string) { +bool Address::isValid(const std::string& string) { const auto decoded = Base58::bitcoin.decode(string); return isValid(decoded); } -Address::Address(const std::string &string) { +Address::Address(const std::string& string) { const auto decoded = Base58::bitcoin.decode(string); if (!isValid(string)) { throw std::invalid_argument("Invalid address key data"); @@ -57,7 +57,7 @@ Address::Address(const std::string &string) { std::copy(decoded.begin(), decoded.end(), bytes.begin()); } -Address::Address(const Data &data) { +Address::Address(const Data& data) { if (!isValid(data)) { throw std::invalid_argument("Invalid address data"); } diff --git a/src/Waves/Address.h b/src/Waves/Address.h index 81950ee6b93..df518415334 100644 --- a/src/Waves/Address.h +++ b/src/Waves/Address.h @@ -17,9 +17,6 @@ namespace TW::Waves { class Address : public Base58Address<26> { public: - /// Number of bytes in an address. - static const size_t size = 26; - /// Address version. static const signed char v1 = 0x01; @@ -29,20 +26,16 @@ class Address : public Base58Address<26> { template static Data secureHash(const T &data); - /// Address data consisting of a version and network bytes followed by the public key - /// hash and the checksum. - std::array bytes; - /// Determines whether a string makes a valid address. - static bool isValid(const std::string &string); + static bool isValid(const std::string& string); - static bool isValid(const Data &data); + static bool isValid(const Data& data); /// Initializes a address with a string representation. - explicit Address(const std::string &string); + explicit Address(const std::string& string); /// Initializes a address with a collection of bytes. - explicit Address(const Data &data); + explicit Address(const Data& data); /// Initializes a address with a public key and a prefix. explicit Address(const PublicKey &publicKey); diff --git a/src/Waves/Transaction.cpp b/src/Waves/Transaction.cpp index 277c1540bc3..ee9d2a5af44 100644 --- a/src/Waves/Transaction.cpp +++ b/src/Waves/Transaction.cpp @@ -17,7 +17,7 @@ using json = nlohmann::json; const std::string Transaction::WAVES = "WAVES"; -Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::string fee_asset, Address to, Data attachment, int64_t timestamp, Data pub_key) { +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; @@ -50,7 +50,7 @@ Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::stri return data; } -Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, Data pub_key) { +Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, const Data& pub_key) { auto data = Data(); data.resize(2); data[0] = static_cast(TransactionType::lease); @@ -65,7 +65,7 @@ Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, return data; } -Data serializeCancelLease(Data leaseId, int64_t fee, int64_t timestamp, Data pub_key) { +Data serializeCancelLease(const Data& leaseId, int64_t fee, int64_t timestamp, const Data& pub_key) { auto data = Data(); data.resize(2); data[0] = static_cast(TransactionType::cancelLease); @@ -79,7 +79,7 @@ Data serializeCancelLease(Data leaseId, int64_t fee, int64_t timestamp, Data pub return data; } -json jsonTransfer(Data signature, int64_t amount, const std::string &asset, int64_t fee, const std::string &fee_asset, Address to, Data attachment, int64_t timestamp, Data pub_key) { +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; @@ -101,7 +101,7 @@ json jsonTransfer(Data signature, int64_t amount, const std::string &asset, int6 return jsonTx; } -json jsonLease(Data signature, int64_t amount, int64_t fee, Address to, int64_t timestamp, Data pub_key) { +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; @@ -116,7 +116,7 @@ json jsonLease(Data signature, int64_t amount, int64_t fee, Address to, int64_t return jsonTx; } -json jsonCancelLease(Data signature, Data leaseId, int64_t fee, int64_t timestamp, Data pub_key) { +json jsonCancelLease(const Data& signature, const Data& leaseId, int64_t fee, int64_t timestamp, const Data& pub_key) { json jsonTx; jsonTx["type"] = TransactionType::cancelLease; @@ -162,7 +162,7 @@ Data Transaction::serializeToSign() const { -json Transaction::buildJson(Data signature) const { +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()); diff --git a/src/Waves/Transaction.h b/src/Waves/Transaction.h index 130f44ba095..b22fd54a030 100644 --- a/src/Waves/Transaction.h +++ b/src/Waves/Transaction.h @@ -29,7 +29,7 @@ class Transaction { public: Data serializeToSign() const; - nlohmann::json buildJson(Data signature) const; + nlohmann::json buildJson(const Data& signature) const; }; } // namespace TW::Waves diff --git a/src/XXHash64.h b/src/XXHash64.h index 34122ebc85e..6147ff42759 100644 --- a/src/XXHash64.h +++ b/src/XXHash64.h @@ -33,6 +33,7 @@ class XXHash64 state[1] = seed + Prime2; state[2] = seed; state[3] = seed - Prime1; + buffer[0] = 0; bufferSize = 0; totalLength = 0; } diff --git a/src/Zcash/Entry.h b/src/Zcash/Entry.h index 6b430daac86..424a7b49fe1 100644 --- a/src/Zcash/Entry.h +++ b/src/Zcash/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual 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; - // normalizeAddress is not used 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; diff --git a/src/Zcash/Transaction.cpp b/src/Zcash/Transaction.cpp index 29e4b0b153d..f872270d2dc 100644 --- a/src/Zcash/Transaction.cpp +++ b/src/Zcash/Transaction.cpp @@ -6,6 +6,7 @@ #include "Transaction.h" +#include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" #include "../HexCoding.h" @@ -50,7 +51,7 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e // Input nSequence (none/all, depending on flags) if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && - !TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + !Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { auto hashSequence = getSequenceHash(); std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); } else { @@ -58,10 +59,10 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e } // Outputs (none/one/all, depending on flags) - if (!TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + if (!Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { auto hashOutputs = getOutputsHash(); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); - } else if (TWBitcoinSigHashTypeIsSingle(hashType) && index < outputs.size()) { + } else if (Bitcoin::hashTypeIsSingle(hashType) && index < outputs.size()) { auto outputData = Data{}; outputs[index].encode(outputData); auto hashOutputs = diff --git a/src/Zcash/Transaction.h b/src/Zcash/Transaction.h index ff138452200..1ec45215711 100644 --- a/src/Zcash/Transaction.h +++ b/src/Zcash/Transaction.h @@ -33,6 +33,9 @@ struct Transaction { std::vector outputs; std::array branchId; + /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) + int previousEstimatedVirtualSize = 0; + Transaction() = default; Transaction(uint32_t version, uint32_t versionGroupId, uint32_t lockTime, uint32_t expiryHeight, diff --git a/src/Zcash/TransactionBuilder.h b/src/Zcash/TransactionBuilder.h index 4b26d276a40..e903c85888e 100644 --- a/src/Zcash/TransactionBuilder.h +++ b/src/Zcash/TransactionBuilder.h @@ -9,7 +9,6 @@ #include "Transaction.h" #include "../Bitcoin/TransactionBuilder.h" #include "../Bitcoin/TransactionPlan.h" -#include "../Bitcoin/UnspentSelector.h" #include "../proto/Bitcoin.pb.h" #include "../HexCoding.h" #include diff --git a/src/Zilliqa/Address.h b/src/Zilliqa/Address.h index 9aa09804f59..be7a7ef234f 100644 --- a/src/Zilliqa/Address.h +++ b/src/Zilliqa/Address.h @@ -18,12 +18,12 @@ class Address : public Bech32Address { public: static const std::string hrp; // HRP_ZILLIQA - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) {} + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2, publicKey) {} diff --git a/src/interface/TWAES.cpp b/src/interface/TWAES.cpp index 64cef9f8ee7..04f86c8faed 100644 --- a/src/interface/TWAES.cpp +++ b/src/interface/TWAES.cpp @@ -10,25 +10,25 @@ using namespace TW; -TWData *_Nullable TWAESCBCEncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode) { try { - Data encrypted = Encrypt::AESCBCEncrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); + Data encrypted = Encrypt::AESCBCEncrypt(*((Data*)key), *((Data*)data), *((Data*)iv), mode); return TWDataCreateWithData(&encrypted); } catch (...) { return nullptr; } } -TWData *_Nullable TWAESCBCDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode) { try { - Data decrypted = Encrypt::AESCBCDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); + Data decrypted = Encrypt::AESCBCDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv), mode); return TWDataCreateWithData(&decrypted); } catch (...) { return nullptr; } } -TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { try { Data encrypted = Encrypt::AESCTREncrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); return TWDataCreateWithData(&encrypted); @@ -37,7 +37,7 @@ TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, T } } -TWData *_Nullable TWAESCTRDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { try { Data decrypted = Encrypt::AESCTRDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); return TWDataCreateWithData(&decrypted); diff --git a/src/interface/TWAccount.cpp b/src/interface/TWAccount.cpp index 302874725fd..cbe49c3f321 100644 --- a/src/interface/TWAccount.cpp +++ b/src/interface/TWAccount.cpp @@ -10,12 +10,12 @@ using namespace TW; -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey) { +struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey) { auto& addressString = *reinterpret_cast(address); auto& derivationPathString = *reinterpret_cast(derivationPath); auto& extendedPublicKeyString = *reinterpret_cast(extendedPublicKey); const auto dp = DerivationPath(derivationPathString); - return new TWAccount{ Keystore::Account(addressString, dp, extendedPublicKeyString) }; + return new TWAccount{ Keystore::Account(addressString, coin, dp, extendedPublicKeyString) }; } void TWAccountDelete(struct TWAccount *_Nonnull account) { @@ -35,5 +35,5 @@ TWString *_Nonnull TWAccountExtendedPublicKey(struct TWAccount *_Nonnull account } enum TWCoinType TWAccountCoin(struct TWAccount *_Nonnull account) { - return account->impl.coin(); + return account->impl.coin; } diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index fcd2587ddf2..92a7141c9f7 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -20,6 +20,8 @@ #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" @@ -36,13 +38,13 @@ bool TWAnyAddressEqual(struct TWAnyAddress* _Nonnull lhs, struct TWAnyAddress* _ } bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { - auto& address = *reinterpret_cast(string); + const auto& address = *reinterpret_cast(string); return TW::validateAddress(coin, address); } struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, enum TWCoinType coin) { - auto& address = *reinterpret_cast(string); + 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}; @@ -75,6 +77,7 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { case TWCoinTypeCosmos: case TWCoinTypeKava: case TWCoinTypeTerra: + case TWCoinTypeBandChain: case TWCoinTypeIoTeX: { Cosmos::Address addr; if (!Cosmos::Address::decode(string, addr)) { @@ -141,6 +144,8 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { case TWCoinTypeTheta: case TWCoinTypeWanchain: case TWCoinTypeAion: + case TWCoinTypeSmartChainLegacy: + case TWCoinTypeSmartChain: data = parse_hex(string); break; @@ -184,6 +189,23 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { data = Data(addr.bytes.begin(), addr.bytes.end()); break; } + + case TWCoinTypeElrond: { + Elrond::Address addr; + if (Elrond::Address::decode(string, addr)) { + data = addr.getKeyHash(); + } + + break; + } + + case TWCoinTypeNEAR: { + auto addr = NEAR::Address(string); + // remove last 4 bytes checksum + data = Data(addr.bytes.begin(), addr.bytes.end() - 4); + break; + } + default: break; } return TWDataCreateWithBytes(data.data(), data.size()); diff --git a/src/interface/TWAnySigner.cpp b/src/interface/TWAnySigner.cpp index 64f3dbcc382..cd0f38ef733 100644 --- a/src/interface/TWAnySigner.cpp +++ b/src/interface/TWAnySigner.cpp @@ -27,6 +27,20 @@ extern bool TWAnySignerSupportsJSON(enum TWCoinType coin) { return TW::supportsJSONSigning(coin); } +TWData* _Nonnull TWAnySignerEncode(TWData* _Nonnull data, enum TWCoinType coin) { + const Data& dataIn = *(reinterpret_cast(data)); + Data dataOut; + TW::anyCoinEncode(coin, dataIn, dataOut); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + +TWData* _Nonnull TWAnySignerDecode(TWData* _Nonnull data, enum TWCoinType coin) { + const Data& dataIn = *(reinterpret_cast(data)); + Data dataOut; + TW::anyCoinDecode(coin, dataIn, dataOut); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + TWData* _Nonnull TWAnySignerPlan(TWData* _Nonnull data, enum TWCoinType coin) { const Data& dataIn = *(reinterpret_cast(data)); Data dataOut; diff --git a/src/interface/TWBase58.cpp b/src/interface/TWBase58.cpp index ee38331260e..923b8a33bf0 100644 --- a/src/interface/TWBase58.cpp +++ b/src/interface/TWBase58.cpp @@ -13,7 +13,7 @@ using namespace TW; TWString *_Nonnull TWBase58Encode(TWData *_Nonnull data) { - auto& d = *reinterpret_cast(data); + const auto& d = *reinterpret_cast(data); const auto str = Base58::bitcoin.encodeCheck(d); return TWStringCreateWithUTF8Bytes(str.c_str()); } diff --git a/src/interface/TWBitcoin.cpp b/src/interface/TWBitcoin.cpp index 54359ae3b19..d6cf837ce7e 100644 --- a/src/interface/TWBitcoin.cpp +++ b/src/interface/TWBitcoin.cpp @@ -5,11 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include +#include "../Bitcoin/SigHashType.h" bool TWBitcoinSigHashTypeIsSingle(enum TWBitcoinSigHashType type) { - return (type & 0x1f) == TWBitcoinSigHashTypeSingle; + return TW::Bitcoin::hashTypeIsSingle(type); } bool TWBitcoinSigHashTypeIsNone(enum TWBitcoinSigHashType type) { - return (type & 0x1f) == TWBitcoinSigHashTypeNone; + return TW::Bitcoin::hashTypeIsNone(type); } diff --git a/src/interface/TWBitcoinAddress.cpp b/src/interface/TWBitcoinAddress.cpp index b18e840364d..8df8de2a37f 100644 --- a/src/interface/TWBitcoinAddress.cpp +++ b/src/interface/TWBitcoinAddress.cpp @@ -38,7 +38,7 @@ struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_N } struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnull data) { - auto& d = *reinterpret_cast(data); + const auto& d = *reinterpret_cast(data); try { return new TWBitcoinAddress{ Address(d) }; } catch (...) { diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 1fa6575c560..6aff55689f2 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -7,6 +7,7 @@ #include #include "../Bitcoin/Script.h" +#include "../Bitcoin/SigHashType.h" using namespace TW::Bitcoin; @@ -59,6 +60,10 @@ bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *scrip return script->impl.isPayToWitnessScriptHash(); } +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *script) { + return script->impl.isPayToWitnessPublicKeyHash(); +} + bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *script) { return script->impl.isWitnessProgram(); } @@ -69,7 +74,7 @@ bool TWBitcoinScriptEqual(const struct TWBitcoinScript *_Nonnull lhs, const stru TWData *TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *script) { std::vector data; - if (script->impl.matchPayToPubkey(data)) { + if (script->impl.matchPayToPublicKey(data)) { return TWDataCreateWithBytes(data.data(), data.size()); } return nullptr; @@ -77,7 +82,7 @@ TWData *TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *script) { TWData *TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript *script) { std::vector data; - if (script->impl.matchPayToPubkeyHash(data)) { + if (script->impl.matchPayToPublicKeyHash(data)) { return TWDataCreateWithBytes(data.data(), data.size()); } return nullptr; @@ -127,7 +132,7 @@ struct TWBitcoinScript *TWBitcoinScriptBuildPayToScriptHash(TWData *scriptHash) struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *hash) { auto v = reinterpret_cast*>(hash); - auto script = Script::buildPayToWitnessPubkeyHash(*v); + auto script = Script::buildPayToWitnessPublicKeyHash(*v); return new TWBitcoinScript{ .impl = script }; } @@ -137,8 +142,12 @@ struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *scrip return new TWBitcoinScript{ .impl = script }; } -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildForAddress(TWString *_Nonnull address, enum TWCoinType coin) { +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin) { auto s = reinterpret_cast(address); - auto script = Script::buildForAddress(*s, coin); + auto script = Script::lockScriptForAddress(*s, coin); return new TWBitcoinScript{ .impl = script }; } + +uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { + return TW::Bitcoin::hashTypeForCoin(coinType); +} diff --git a/src/interface/TWCoinType.cpp b/src/interface/TWCoinType.cpp index c5035c8b6df..a99ecee7c75 100644 --- a/src/interface/TWCoinType.cpp +++ b/src/interface/TWCoinType.cpp @@ -64,3 +64,7 @@ uint8_t TWCoinTypeP2shPrefix(enum TWCoinType coin) { uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin) { return TW::staticPrefix(coin); } + +uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin) { + return TW::slip44Id(coin); +} diff --git a/src/interface/TWData+Hex.cpp b/src/interface/TWData+Hex.cpp deleted file mode 100644 index cdc40659f33..00000000000 --- a/src/interface/TWData+Hex.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 -#include - -static inline uint8_t value(uint8_t c) { - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - - // Invalid digit - return 0; - } - -TWData *TWDataCreateWithHexString(const TWString *hex) { - size_t stringIndex = 0; - if (TWStringSize(hex) >= 2 && TWStringGet(hex, 0) == '0' && TWStringGet(hex, 1) == 'x') { - stringIndex += 2; - } - - const size_t count = (TWStringSize(hex) - stringIndex + 1) / 2; - TWData *data = TWDataCreateWithSize(count); - - size_t dataIndex = 0; - while (stringIndex < TWStringSize(hex)) { - uint8_t high = value(TWStringGet(hex, stringIndex)); - stringIndex += 1; - if (stringIndex >= TWStringSize(hex)) { - TWDataSet(data, dataIndex, high); - break; - } - - uint8_t low = value(TWStringGet(hex, stringIndex)); - stringIndex += 1; - - TWDataSet(data, dataIndex, static_cast((high << 4) | low)); - dataIndex += 1; - } - - return data; -} diff --git a/src/interface/TWData.cpp b/src/interface/TWData.cpp index 5a0ae87d6b2..cb3afcd52ca 100644 --- a/src/interface/TWData.cpp +++ b/src/interface/TWData.cpp @@ -5,9 +5,14 @@ // file LICENSE at the root of the source code distribution tree. #include +#include +#include "Data.h" +#include "HexCoding.h" #include #include +using namespace TW; + TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size) { auto data = new std::vector(); data->reserve(size); @@ -26,6 +31,14 @@ TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data) { return copy; } +TWData* TWDataCreateWithHexString(const TWString* hex) { + if (hex == nullptr) { + return nullptr; + } + Data data = parse_hex(std::string(TWStringUTF8Bytes(hex))); + return TWDataCreateWithBytes(data.data(), data.size()); +} + size_t TWDataSize(TWData *_Nonnull data) { auto v = reinterpret_cast*>(data); return v->size(); @@ -70,8 +83,7 @@ void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte) { void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append) { auto v = const_cast*>(reinterpret_cast*>(data)); auto av = reinterpret_cast*>(append); - for (auto& b : *av) - v->push_back(b); + std::copy(av->begin(), av->end(), std::back_inserter(*v)); } void TWDataReverse(TWData *_Nonnull data) { diff --git a/src/interface/TWEthereumAbiEncoder.cpp b/src/interface/TWEthereumAbi.cpp similarity index 51% rename from src/interface/TWEthereumAbiEncoder.cpp rename to src/interface/TWEthereumAbi.cpp index 976dfe46075..a6208fe4193 100644 --- a/src/interface/TWEthereumAbiEncoder.cpp +++ b/src/interface/TWEthereumAbi.cpp @@ -4,40 +4,29 @@ // 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 "Ethereum/ABI.h" +#include "Ethereum/ContractCall.h" #include "HexCoding.h" -#include "../uint256.h" +#include "uint256.h" +#include #include #include -#include using namespace TW; using namespace TW::Ethereum; using namespace TW::Ethereum::ABI; /// Wrapper for C interface, empty as all methods are static -struct TWEthereumAbiEncoder { - //TW::Ethereum::ABI::Encoder impl; +struct TWEthereumAbi { + // TW::Ethereum::ABI::Encoder impl; }; -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiEncoderBuildFunction(TWString *_Nonnull name) { - auto func = Function(TWStringUTF8Bytes(name)); - return new TWEthereumAbiFunction{ func }; -} - -void TWEthereumAbiEncoderDeleteFunction(struct TWEthereumAbiFunction *_Nonnull func_in) { - assert(func_in != nullptr); - delete func_in; -} - -///// Encode & decode - -TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnull func_in) { +TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull func_in) { assert(func_in != nullptr); Function& function = func_in->impl; @@ -46,7 +35,8 @@ TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnul return TWDataCreateWithData(&encoded); } -bool TWEthereumAbiEncoderDecodeOutput(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull encoded) { +bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull func_in, + TWData* _Nonnull encoded) { assert(func_in != nullptr); Function& function = func_in->impl; assert(encoded != nullptr); @@ -55,3 +45,19 @@ bool TWEthereumAbiEncoderDecodeOutput(struct TWEthereumAbiFunction *_Nonnull fun size_t offset = 0; return function.decodeOutput(encData, offset); } + +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); + if (!string.has_value()) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(string->c_str()); + } + catch(...) { + return nullptr; + } +} diff --git a/src/interface/TWEthereumAbiFunction.cpp b/src/interface/TWEthereumAbiFunction.cpp index e185e8ae9e7..93b451c2a51 100644 --- a/src/interface/TWEthereumAbiFunction.cpp +++ b/src/interface/TWEthereumAbiFunction.cpp @@ -19,7 +19,7 @@ using namespace TW; using namespace TW::Ethereum; using namespace TW::Ethereum::ABI; -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name) { +struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name) { auto func = Function(TWStringUTF8Bytes(name)); return new TWEthereumAbiFunction{ func }; } diff --git a/src/interface/TWEthereumAbiValueEncoder.cpp b/src/interface/TWEthereumAbiValue.cpp similarity index 64% rename from src/interface/TWEthereumAbiValueEncoder.cpp rename to src/interface/TWEthereumAbiValue.cpp index 473826cdab6..e9732657c14 100644 --- a/src/interface/TWEthereumAbiValueEncoder.cpp +++ b/src/interface/TWEthereumAbiValue.cpp @@ -4,9 +4,10 @@ // 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 @@ -15,58 +16,64 @@ using namespace TW::Ethereum; using namespace TW; -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBool(bool value) { +TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value) { Data data; ABI::ValueEncoder::encodeBool(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt32(int32_t value) { +TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value) { Data data; ABI::ValueEncoder::encodeInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt32(uint32_t value) { +TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value) { Data data; ABI::ValueEncoder::encodeUInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt256(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value) { Data data; int256_t value256 = static_cast(TW::load(*reinterpret_cast(value))); ABI::ValueEncoder::encodeInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt256(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value) { Data data; uint256_t value256 = TW::load(*reinterpret_cast(value)); ABI::ValueEncoder::encodeUInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeAddress(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value) { Data data; ABI::ValueEncoder::encodeAddress(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeString(TWString* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value) { Data data; ABI::ValueEncoder::encodeString(TWStringUTF8Bytes(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytes(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value) { Data data; ABI::ValueEncoder::encodeBytes(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytesDyn(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value) { Data data; ABI::ValueEncoder::encodeBytesDyn(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } + +TWString* _Nonnull TWEthereumAbiValueDecodeUInt256(TWData* _Nonnull input) { + auto data = TW::data(TWDataBytes(input), TWDataSize(input)); + auto decoded = Ethereum::ABI::ValueDecoder::decodeUInt256(data); + return TWStringCreateWithUTF8Bytes(TW::toString(decoded).c_str()); +} diff --git a/src/interface/TWFIOAccount.cpp b/src/interface/TWFIOAccount.cpp new file mode 100644 index 00000000000..b600d82b493 --- /dev/null +++ b/src/interface/TWFIOAccount.cpp @@ -0,0 +1,39 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "FIO/Actor.h" +#include "FIO/Address.h" + +#include + +using namespace TW; +using namespace TW::FIO; + +struct TWFIOAccount { + std::string description; +}; + +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 (Actor::validate(account)) { + return new TWFIOAccount{account}; + } + return nullptr; +} + +void TWFIOAccountDelete(struct TWFIOAccount *_Nonnull account) { + delete account; +} + +TWString *_Nonnull TWFIOAccountDescription(struct TWFIOAccount *_Nonnull account) { + return TWStringCreateWithUTF8Bytes(account->description.c_str()); +} diff --git a/src/interface/TWHDVersion.cpp b/src/interface/TWHDVersion.cpp index 7573820320f..e093336537a 100644 --- a/src/interface/TWHDVersion.cpp +++ b/src/interface/TWHDVersion.cpp @@ -20,6 +20,14 @@ bool TWHDVersionIsPublic(enum TWHDVersion version) { case TWHDVersionDGUB: return true; + case TWHDVersionNone: + default: + return false; + } +} + +bool TWHDVersionIsPrivate(enum TWHDVersion version) { + switch (version) { case TWHDVersionXPRV: case TWHDVersionYPRV: case TWHDVersionZPRV: @@ -27,7 +35,7 @@ bool TWHDVersionIsPublic(enum TWHDVersion version) { case TWHDVersionMTPV: case TWHDVersionDPRV: case TWHDVersionDGPV: - return false; + return true; case TWHDVersionNone: default: @@ -35,9 +43,4 @@ bool TWHDVersionIsPublic(enum TWHDVersion version) { } } -bool TWHDVersionIsPrivate(enum TWHDVersion version) { - if (version == TWHDVersionNone) return false; - return !TWHDVersionIsPublic(version); -} - #pragma clang diagnostic pop diff --git a/src/interface/TWHDWallet.cpp b/src/interface/TWHDWallet.cpp index eff92fa74a8..b8a3846a055 100644 --- a/src/interface/TWHDWallet.cpp +++ b/src/interface/TWHDWallet.cpp @@ -46,24 +46,25 @@ 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(derivationPath) }; + return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; } TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *wallet, TWCoinType coin) { auto derivationPath = TW::derivationPath(coin); - PrivateKey privateKey = wallet->impl.getKey(derivationPath); + PrivateKey privateKey = wallet->impl.getKey(coin, derivationPath); std::string address = deriveAddress(coin, privateKey); return TWStringCreateWithUTF8Bytes(address.c_str()); } -struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, TWString *_Nonnull derivationPath) { +struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, TWString *_Nonnull derivationPath) { auto& s = *reinterpret_cast(derivationPath); - return new TWPrivateKey{ wallet->impl.getKey( TW::DerivationPath(s)) }; + const auto path = DerivationPath(s); + 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) { - const auto derivationPath = DerivationPath(TW::purpose(coin), coin, account, change, address); - return new TWPrivateKey{ wallet->impl.getKey(derivationPath) }; + const auto derivationPath = DerivationPath(TW::purpose(coin), TW::slip44Id(coin), account, change, address); + return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; } TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWHDVersion version) { @@ -74,9 +75,9 @@ TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *wallet, TWP return new std::string(wallet->impl.getExtendedPublicKey(purpose, coin, version)); } -TWPublicKey *TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, TWString *_Nonnull derivationPath) { +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), derivationPathObject); + auto publicKey = HDWallet::getPublicKeyFromExtended(*reinterpret_cast(extended), coin, derivationPathObject); if (!publicKey) { return nullptr; } diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index 9ae603f8ffd..ab6b2b27ccb 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -90,7 +90,7 @@ struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivate } TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { - auto& d = *reinterpret_cast(digest); + const auto& d = *reinterpret_cast(digest); auto result = pk->impl.sign(d, curve); if (result.empty()) { return nullptr; @@ -110,7 +110,7 @@ TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull } TWData *TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message, enum TWCurve curve) { - auto& msg = *reinterpret_cast(message); + const auto& msg = *reinterpret_cast(message); auto result = pk->impl.signSchnorr(msg, curve); if (result.empty()) { diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 9ada3d666d0..10d68e60a7f 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -49,14 +49,14 @@ struct TWPublicKey *_Nonnull TWPublicKeyUncompressed(struct TWPublicKey *_Nonnul } bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *signature, TWData *message) { - auto& s = *reinterpret_cast(signature); - auto& m = *reinterpret_cast(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) { - auto& s = *reinterpret_cast(signature); - auto& m = *reinterpret_cast(message); + const auto& s = *reinterpret_cast(signature); + const auto& m = *reinterpret_cast(message); return pk->impl.verifySchnorr(s, m); } @@ -70,14 +70,10 @@ TWString *_Nonnull TWPublicKeyDescription(struct TWPublicKey *_Nonnull publicKey } struct TWPublicKey *_Nullable TWPublicKeyRecover(TWData *_Nonnull signature, TWData *_Nonnull message) { - auto signatureBytes = TWDataBytes(signature); - auto v = signatureBytes[64]; - if (v >= 27) { - v -= 27; - } - TW::Data result(65); - if (ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signatureBytes, TWDataBytes(message), v) != 0) { + try { + const PublicKey publicKey = PublicKey::recover(*((TW::Data*)signature), *((TW::Data*)message)); + return new TWPublicKey{ publicKey }; + } catch (...) { return nullptr; } - return new TWPublicKey{ PublicKey(result, TWPublicKeyTypeSECP256k1Extended) }; } diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index 795cfa38ce9..3d2ed4c7284 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -25,29 +25,29 @@ struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path) { } } -struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWString* _Nonnull password) { +struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { const auto& nameString = *reinterpret_cast(name); - const auto& passwordString = *reinterpret_cast(password); - return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordString) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordData) }; } -struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWString* _Nonnull password, enum TWCoinType coin) { +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { try { const auto& privateKeyData = *reinterpret_cast(privateKey); const auto& nameString = *reinterpret_cast(name); - const auto& passwordString = *reinterpret_cast(password); - return new TWStoredKey{ StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordString, coin, privateKeyData) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData) }; } catch (...) { return nullptr; } } -struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWString* _Nonnull password, enum TWCoinType coin) { +struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { try { const auto& mnemonicString = *reinterpret_cast(mnemonic); const auto& nameString = *reinterpret_cast(name); - const auto& passwordString = *reinterpret_cast(password); - return new TWStoredKey{ StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordString, mnemonicString, coin) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin) }; } catch (...) { return nullptr; } @@ -107,11 +107,11 @@ void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCo key->impl.removeAccount(coin); } -void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey) { +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey) { const auto& addressString = *reinterpret_cast(address); const auto& extetndedPublicKeyString = *reinterpret_cast(extetndedPublicKey); const auto dp = TW::DerivationPath(*reinterpret_cast(derivationPath)); - key->impl.addAccount(addressString, dp, extetndedPublicKeyString); + key->impl.addAccount(addressString, coin, dp, extetndedPublicKeyString); } bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) { @@ -124,20 +124,20 @@ bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) } } -TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - const auto data = key->impl.payload.decrypt(passwordString); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + const auto data = key->impl.payload.decrypt(passwordData); return TWDataCreateWithBytes(data.data(), data.size()); } catch (...) { return nullptr; } } -TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - const auto data = key->impl.payload.decrypt(passwordString); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + const auto data = key->impl.payload.decrypt(passwordData); const auto string = std::string(data.begin(), data.end()); return TWStringCreateWithUTF8Bytes(string.c_str()); } catch (...) { @@ -145,19 +145,19 @@ TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, } } -struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWString* _Nonnull password) { +struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - return new TWPrivateKey{ key->impl.privateKey(coin, passwordString) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWPrivateKey{ key->impl.privateKey(coin, passwordData) }; } catch (...) { return nullptr; } } -struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - return new TWHDWallet{ key->impl.wallet(passwordString) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWHDWallet{ key->impl.wallet(passwordData) }; } catch (...) { return nullptr; } @@ -168,10 +168,10 @@ TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key) { return TWDataCreateWithBytes(reinterpret_cast(json.data()), json.size()); } -bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - key->impl.fixAddresses(passwordString); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + key->impl.fixAddresses(passwordData); return true; } catch (...) { return false; diff --git a/src/proto/Aion.proto b/src/proto/Aion.proto index 630d1709d30..771e6774171 100644 --- a/src/proto/Aion.proto +++ b/src/proto/Aion.proto @@ -25,6 +25,9 @@ message SigningInput { // Private key. bytes private_key = 7; + + // Timestamp + uint64 timestamp = 8; } // Transaction signing output. diff --git a/src/proto/Binance.proto b/src/proto/Binance.proto index 37d2467175f..595892b034d 100644 --- a/src/proto/Binance.proto +++ b/src/proto/Binance.proto @@ -132,6 +132,35 @@ message RefundHTLTOrder { bytes swap_id = 2; } +message TransferOut { + bytes from = 1; + bytes to = 2; + SendOrder.Token amount = 3; + int64 expire_time = 4; +} + +message SideChainDelegate { + bytes delegator_addr = 1; + bytes validator_addr = 2; + SendOrder.Token delegation = 3; + string chain_id = 4; +} + +message SideChainRedelegate { + bytes delegator_addr = 1; + bytes validator_src_addr = 2; + bytes validator_dst_addr = 3; + SendOrder.Token amount = 4; + string chain_id = 5; +} + +message SideChainUndelegate { + bytes delegator_addr = 1; + bytes validator_addr = 2; + SendOrder.Token amount = 3; + string chain_id = 4; +} + // Input data necessary to create a signed order. message SigningInput { string chain_id = 1; @@ -154,6 +183,10 @@ message SigningInput { TokenIssueOrder issue_order = 17; TokenMintOrder mint_order = 18; TokenBurnOrder burn_order = 19; + TransferOut transfer_out_order = 20; + SideChainDelegate side_delegate_order = 21; + SideChainRedelegate side_redelegate_order = 22; + SideChainUndelegate side_undelegate_order = 23; } } diff --git a/src/proto/Elrond.proto b/src/proto/Elrond.proto new file mode 100644 index 00000000000..36cb5aaf2c2 --- /dev/null +++ b/src/proto/Elrond.proto @@ -0,0 +1,38 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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/IoTeX.proto b/src/proto/IoTeX.proto index 669b049ecb3..1df337cf627 100644 --- a/src/proto/IoTeX.proto +++ b/src/proto/IoTeX.proto @@ -10,43 +10,73 @@ message Transfer { } message Staking { - message Stake { - string candidate = 1; - uint64 duration = 2; - bool nonDecay= 3; - bytes data = 4; - } - - message Unstake { - uint64 piggy_index = 1; - bytes data = 2; - } - - message Withdraw { - uint64 piggy_index = 1; - bytes data = 2; - } - - message AddStake { - uint64 piggy_index = 1; - bytes data = 2; - } - - message MoveStake { - uint64 piggy_index = 1; - string candidate = 2; - bytes data = 3; - } + // create stake + message Create { + string candidateName = 1; + string stakedAmount = 2; + uint32 stakedDuration = 3; + bool autoStake = 4; + bytes payload = 5; + } - string amount = 1; - string contract = 2; + // 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; + } - oneof message { - Stake stake = 3; - Unstake unstake = 4; - Withdraw withdraw = 5; - AddStake addStake = 6; - MoveStake moveStake = 7; + 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; } } @@ -65,8 +95,17 @@ message SigningInput { bytes privateKey = 5; oneof action { Transfer transfer = 10; - Staking staking = 11; ContractCall call = 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; } } @@ -78,3 +117,30 @@ message SigningOutput { // Signed Action hash bytes hash = 2; } + +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; + } +} + +message Action { + ActionCore core = 1; + bytes senderPubKey = 2; + bytes signature = 3; +} \ No newline at end of file diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index 0c51868b490..1710c32068c 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -31,7 +31,7 @@ message Balance { message Staking { message Bond { - string validator = 1; + string controller = 1; bytes value = 2; RewardDestination reward_destination = 3; } @@ -44,7 +44,9 @@ message Staking { bytes value = 1; } - message WithdrawUnbonded {} + message WithdrawUnbonded { + int32 slashing_spans = 1; + } message Nominate { repeated string nominators = 1; @@ -68,11 +70,11 @@ message SigningInput { bytes genesis_hash = 2; uint64 nonce = 3; uint32 spec_version = 4; - bytes tip = 5; // big integer - Era era = 6; // empty means Immortal - bytes private_key = 7; - Network network = 8; - uint32 extrinsic_version = 9; + uint32 transaction_version = 5; + bytes tip = 6; // big integer + Era era = 7; // empty means Immortal + bytes private_key = 8; + Network network = 9; oneof message_oneof { Balance balance_call = 10; diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index f2921e368b8..3a6b635b169 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -38,8 +38,8 @@ message TransferTRC20Contract { // Recipient address. string to_address = 3; - // Amount to send. - int64 amount = 4; + // Amount to send, uint256, big-endian. + bytes amount = 4; } message FreezeBalanceContract { diff --git a/swift/Playground.playground/Contents.swift b/swift/Playground.playground/Contents.swift new file mode 100644 index 00000000000..832b3e213a6 --- /dev/null +++ b/swift/Playground.playground/Contents.swift @@ -0,0 +1,22 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 UIKit +import TrustWalletCore +import SwiftProtobuf + +enum PlaygroundError: Error { + case invalidHexString +} + +func reverseHex(string: String) throws -> String { + guard let data = Data(hexString: string) else { + throw PlaygroundError.invalidHexString + } + return Data(data.reversed()).hexString +} + +try reverseHex(string: "0xdeadbeef") diff --git a/swift/Playground.playground/contents.xcplayground b/swift/Playground.playground/contents.xcplayground new file mode 100644 index 00000000000..89da2d47069 --- /dev/null +++ b/swift/Playground.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/swift/Podfile b/swift/Podfile index 556109f432e..5d60e21b200 100644 --- a/swift/Podfile +++ b/swift/Podfile @@ -9,3 +9,16 @@ target 'TrustWalletCore' do target 'TrustWalletCoreTests' end + +post_install do |installer| + # Set default deployment target + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + end + end + + installer.pods_project.build_configurations.each do |config| + config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' + end +end diff --git a/swift/Podfile.lock b/swift/Podfile.lock index 9250a5e40a0..c4c6353731c 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -1,6 +1,6 @@ PODS: - - SwiftLint (0.32.0) - - SwiftProtobuf (1.7.0) + - SwiftLint (0.39.2) + - SwiftProtobuf (1.10.2) DEPENDENCIES: - SwiftLint @@ -12,9 +12,9 @@ SPEC REPOS: - SwiftProtobuf SPEC CHECKSUMS: - SwiftLint: 009a898ef2a1c851f45e1b59349bf6ff2ddc990d - SwiftProtobuf: 4fd9645e69b72cbae6ec8da5be0cdd20ca6565dd + SwiftLint: 22ccbbe3b8008684be5955693bab135e0ed6a447 + SwiftProtobuf: bec1ae7d686ff73dd09d79866154f4970b891410 -PODFILE CHECKSUM: 9af33e2495c8b16bfcea0f7779a0fcfe29e3ab60 +PODFILE CHECKSUM: 3a51502ee71f1c313b60afcec49ad1dc8ed279b7 -COCOAPODS: 1.9.0 +COCOAPODS: 1.9.3 diff --git a/swift/Sources/AnySigner.swift b/swift/Sources/AnySigner.swift index 9a6f5862cd6..79dfe0b7a97 100644 --- a/swift/Sources/AnySigner.swift +++ b/swift/Sources/AnySigner.swift @@ -41,6 +41,30 @@ public final class AnySigner { return TWStringNSString(TWAnySignerSignJSON(jsonString, keyData, TWCoinType(rawValue: coin.rawValue))) } + public static func encode(input: SigningInput, coin: CoinType) -> Data { + do { + return nativeEncode(data: try input.serializedData(), coin: coin) + } catch let error { + fatalError(error.localizedDescription) + } + } + + public static func nativeEncode(data: Data, coin: CoinType) -> Data { + let inputData = TWDataCreateWithNSData(data) + defer { + TWDataDelete(inputData) + } + return TWDataNSData(TWAnySignerEncode(inputData, TWCoinType(rawValue: coin.rawValue))) + } + + public static func decode(data: Data, coin: CoinType) -> Data { + let inputData = TWDataCreateWithNSData(data) + defer { + TWDataDelete(inputData) + } + return TWDataNSData(TWAnySignerDecode(inputData, TWCoinType(rawValue: coin.rawValue))) + } + public static func plan(input: SigningInput, coin: CoinType) -> TransactionPlan { do { let outputData = nativePlan(data: try input.serializedData(), coin: coin) diff --git a/swift/Sources/DerivationPath.swift b/swift/Sources/DerivationPath.swift index 16da7631ee6..c9f694bb6b4 100644 --- a/swift/Sources/DerivationPath.swift +++ b/swift/Sources/DerivationPath.swift @@ -8,7 +8,7 @@ import Foundation /// Represents a hierarchical determinisic derivation path. public struct DerivationPath: Codable, Hashable, CustomStringConvertible { - let indexCount = 5 + var indexCount = 5 /// List of indices in the derivation path. public private(set) var indices = [Index]() @@ -24,12 +24,12 @@ public struct DerivationPath: Codable, Hashable, CustomStringConvertible { } /// Coin type distinguishes between main net, test net, and forks. - public var coinType: CoinType { + public var coinType: UInt32 { get { - return CoinType(rawValue: indices[1].value)! + return indices[1].value } set { - indices[1] = Index(newValue.rawValue, hardened: true) + indices[1] = Index(newValue, hardened: true) } } @@ -69,10 +69,10 @@ public struct DerivationPath: Codable, Hashable, CustomStringConvertible { } /// Creates a `DerivationPath` by components. - public init(purpose: Purpose, coinType: CoinType, account: UInt32 = 0, change: UInt32 = 0, address: UInt32 = 0) { + 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 = coinType + self.coinType = coin self.account = account self.change = change self.address = address diff --git a/swift/Sources/Extensions/AddressProtocol.swift b/swift/Sources/Extensions/AddressProtocol.swift index 1c993e2df09..273e71cea0f 100644 --- a/swift/Sources/Extensions/AddressProtocol.swift +++ b/swift/Sources/Extensions/AddressProtocol.swift @@ -7,3 +7,5 @@ import Foundation public protocol Address: CustomStringConvertible {} + +extension AnyAddress: Equatable {} diff --git a/swift/Sources/Extensions/Data+Hex.swift b/swift/Sources/Extensions/Data+Hex.swift index 817a2a9605e..3c4642eaa76 100644 --- a/swift/Sources/Extensions/Data+Hex.swift +++ b/swift/Sources/Extensions/Data+Hex.swift @@ -16,6 +16,11 @@ extension Data { string = hexString } + // Check odd length hex string + if string.count % 2 != 0 { + return nil + } + // Convert the string to bytes for better performance guard let stringData = string.data(using: .ascii, allowLossyConversion: true) else { return nil diff --git a/swift/Sources/Extensions/HDWallet+Extension.swift b/swift/Sources/Extensions/HDWallet+Extension.swift deleted file mode 100644 index b57b93c1f11..00000000000 --- a/swift/Sources/Extensions/HDWallet+Extension.swift +++ /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. - -/// A hierarchical deterministic wallet. -public extension HDWallet { - static func derive(from extended: String, at path: DerivationPath) -> PublicKey? { - return HDWallet.getPublicKeyFromExtended(extended: extended, derivationPath: path.description) - } - - func getKey(at path: DerivationPath) -> PrivateKey { - return getKey(derivationPath: path.description) - } -} diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 35e46d9ab0d..d86ce0014c2 100755 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -75,7 +75,7 @@ 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: password) + let key = StoredKey(name: name, password: Data(password.utf8)) return try saveCreatedWallet(for: key, password: password, coins: coins) } @@ -101,7 +101,7 @@ public final class KeyStore { /// Remove accounts from a wallet. public func removeAccounts(wallet: Wallet, coins: [CoinType], password: String) throws -> Wallet { - guard wallet.key.decryptPrivateKey(password: password) != nil else { + guard wallet.key.decryptPrivateKey(password: Data(password.utf8)) != nil else { throw Error.invalidPassword } @@ -130,7 +130,7 @@ public final class KeyStore { guard let key = StoredKey.importJSON(json: json) else { throw Error.invalidKey } - guard let data = key.decryptPrivateKey(password: password) else { + guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } @@ -159,7 +159,7 @@ public final class KeyStore { /// - 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: password, coin: coin) else { + guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin) else { throw Error.invalidKey } let url = makeAccountURL() @@ -180,7 +180,7 @@ public final class KeyStore { /// - 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: encryptPassword, coin: coins.first ?? .ethereum) else { + guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coins.first ?? .ethereum) else { throw Error.invalidMnemonic } let url = makeAccountURL() @@ -211,12 +211,12 @@ public final class KeyStore { throw Error.accountNotFound } - if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWallet(mnemonic: mnemonic, name: "", password: newPassword, coin: coin) { + if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWallet(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } return json - } else if let newKey = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "", password: newPassword, coin: coin) { + } else if let newKey = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } @@ -233,7 +233,7 @@ public final class KeyStore { /// - password: account password /// - Returns: private key data for encrypted keys or menmonic phrase for HD wallets public func exportPrivateKey(wallet: Wallet, password: String) throws -> Data { - guard let key = wallet.key.decryptPrivateKey(password: password) else { + guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } return key @@ -247,7 +247,7 @@ public final class KeyStore { /// - Returns: mnemonic phrase /// - Throws: `EncryptError.invalidMnemonic` if the account is not an HD wallet. public func exportMnemonic(wallet: Wallet, password: String) throws -> String { - guard let mnemonic = wallet.key.decryptMnemonic(password: password) else { + guard let mnemonic = wallet.key.decryptMnemonic(password: Data(password.utf8)) else { throw Error.invalidPassword } return mnemonic @@ -278,7 +278,7 @@ public final class KeyStore { fatalError("Missing wallet") } - guard var privateKeyData = wallet.key.decryptPrivateKey(password: password) else { + guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } defer { @@ -291,10 +291,10 @@ public final class KeyStore { } if let mnemonic = checkMnemonic(privateKeyData), - let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: newPassword, coin: coins[0]) { + let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { wallets[index].key = key } else if let key = StoredKey.importPrivateKey( - privateKey: privateKeyData, name: newName, password: newPassword, coin: coins[0]) { + privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { wallets[index].key = key } else { throw Error.invalidKey @@ -310,7 +310,7 @@ public final class KeyStore { fatalError("Missing wallet") } - guard var privateKey = wallet.key.decryptPrivateKey(password: password) else { + guard var privateKey = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidKey } defer { diff --git a/swift/Sources/SecRandom.m b/swift/Sources/SecRandom.m index 1f441d48895..52518c110a1 100644 --- a/swift/Sources/SecRandom.m +++ b/swift/Sources/SecRandom.m @@ -3,12 +3,16 @@ uint32_t random32(void) { uint32_t value; - int status = SecRandomCopyBytes(kSecRandomDefault, sizeof(value), &value); - NSCAssert(status == errSecSuccess, @"Failed to generate random number"); + if (SecRandomCopyBytes(kSecRandomDefault, sizeof(value), &value) != errSecSuccess) { + // failed to generate random number + abort(); + } return value; } void random_buffer(uint8_t *buf, size_t len) { - int status = SecRandomCopyBytes(kSecRandomDefault, len, buf); - NSCAssert(status == errSecSuccess, @"Failed to generate random number"); + if (SecRandomCopyBytes(kSecRandomDefault, len, buf) != errSecSuccess) { + // failed to generate random number + abort(); + } } diff --git a/swift/Sources/Wallet.swift b/swift/Sources/Wallet.swift index e702ea105cf..036d2a22d7e 100755 --- a/swift/Sources/Wallet.swift +++ b/swift/Sources/Wallet.swift @@ -36,7 +36,7 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the account /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func getAccount(password: String, coin: CoinType) throws -> Account { - let wallet = key.wallet(password: password) + let wallet = key.wallet(password: Data(password.utf8)) guard let account = key.accountForCoin(coin: coin, wallet: wallet) else { throw KeyStore.Error.invalidPassword } @@ -51,7 +51,7 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the added accounts /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func getAccounts(password: String, coins: [CoinType]) throws -> [Account] { - guard let wallet = key.wallet(password: password) else { + guard let wallet = key.wallet(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } return coins.compactMap({ key.accountForCoin(coin: $0, wallet: wallet) }) @@ -65,7 +65,7 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the private key /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func privateKey(password: String, coin: CoinType) throws -> PrivateKey { - guard let pk = key.privateKey(coin: coin, password: password) else { + guard let pk = key.privateKey(coin: coin, password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } return pk diff --git a/swift/Tests/AESTests.swift b/swift/Tests/AESTests.swift new file mode 100644 index 00000000000..6e3fb4d5f37 --- /dev/null +++ b/swift/Tests/AESTests.swift @@ -0,0 +1,35 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 XCTest +import TrustWalletCore + +class AESTests: XCTestCase { + func testDecrypt() throws { + let key = Data(hexString: "5caa3a74154cee16bd1b570a1330be46e086474ac2f4720530662ef1a469662c")! + let iv = Data(hexString: "89ef1d6728bac2f1dcde2ef9330d2bb8")! + let cipher = Data(hexString: "1b3db3674de082d65455eba0ae61cfe7e681c8ef1132e60c8dbd8e52daf18f4fea42cc76366c83351dab6dca52682ff81f828753f89a21e1cc46587ca51ccd353914ffdd3b0394acfee392be6c22b3db9237d3f717a3777e3577dd70408c089a4c9c85130a68c43b0a8aadb00f1b8a8558798104e67aa4ff027b35d4b989e7fd3988d5dcdd563105767670be735b21c4")! + let expected = """ + {"id":1554098597199736,"jsonrpc":"2.0","method":"wc_sessionUpdate","params":[{"approved":false,"chainId":null,"accounts":null}]} + """ + + let decrypted = AES.decryptCBC(key: key, data: cipher, iv: iv, mode: .pkcs7)! + + XCTAssertEqual(String(data: decrypted, encoding: .utf8)!, expected) + } + + func testEncrypt() throws { + let key = Data(hexString: "bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96")! + let iv = Data(hexString: "5b3a1a561e395d7ad7fe9c92abdacd17")! + let plain = """ + {"jsonrpc":"2.0","id":1554343834752446,"error":{"code":-32000,"message":"Session Rejected"}} + """ + + let encrypted = AES.encryptCBC(key: key, data: Data(plain.utf8), iv: iv, mode: .pkcs7)! + + XCTAssertEqual(encrypted.hexString, "6a93788fcd6d266d06ccff35d1ed328d634605a7f2734f1519256b9950d86d6ca34fe4a13ff0ed513025b49427e6fe15268c84d6dfad6c0c8a21abc9306a5308f545b08d946a2a24b7cd18526bcefd6d9740db9b8e21f4511df148d9b9b03ad9") + } +} diff --git a/swift/Tests/Blockchains/AionTests.swift b/swift/Tests/Blockchains/AionTests.swift index 7b7adef39a9..529395697cb 100644 --- a/swift/Tests/Blockchains/AionTests.swift +++ b/swift/Tests/Blockchains/AionTests.swift @@ -23,6 +23,7 @@ class AionTests: XCTestCase { $0.gasLimit = Data(hexString: "5208")! $0.toAddress = "0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e" $0.amount = Data(hexString: "2710")! + $0.timestamp = 155157377101 $0.privateKey = Data(hexString: "db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")! } diff --git a/swift/Tests/Blockchains/BandChainTests.swift b/swift/Tests/Blockchains/BandChainTests.swift new file mode 100644 index 00000000000..536d6f2e5fc --- /dev/null +++ b/swift/Tests/Blockchains/BandChainTests.swift @@ -0,0 +1,405 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 TrustWalletCore +import XCTest + +class BandChainTests: XCTestCase { + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + + func testAddress() { + let address = CoinType.bandChain.deriveAddress(privateKey: privateKey) + + XCTAssertEqual(address, "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3") + XCTAssertTrue(CoinType.bandChain.validate(address: "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh")) + XCTAssertTrue(CoinType.bandChain.validate(address: "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3")) + XCTAssertFalse(CoinType.bandChain.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) + } + + func testSigningTransaction() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .bandChain).description + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress + $0.toAddress = "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh" + $0.amounts = [CosmosAmount.with { + $0.amount = 1000000 + $0.denom = "uband" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON: String = +""" +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "1000000", + "denom": "uband" + } + ], + "from_address": "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", + "to_address": "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "C6X5J08I1kkebqxa9LiFRRSJsp8U9E/IulruGTtOvpcpn/kMwWAxbFTDzvrDV5SnTWDSlimTkeZq8OuwL7j9nQ==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testStaking() { + // https://scan-wenchang-testnet2.bandchain.org/tx/ca86ad13b7d3add04a926f9f6184f1134b964f4f67f39c3a7169540553119915 + let stakeMessage = CosmosMessage.Delegate.with { + $0.delegatorAddress = "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz" + $0.validatorAddress = "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + $0.amount = CosmosAmount.with { + $0.amount = 1000000 + $0.denom = "uband" + } + } + + let message = CosmosMessage.with { + $0.stakeMessage = stakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 1 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uband" + }, + "delegator_address": "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz", + "validator_address": "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "My+OzXqBOImtuDOoqoO9YBdlAhl6weWZvtJfkm4KDZ8I/wnQHNFBa41ql1e2LYSk3jnR/14LZ6E3pY8YW3WU9w==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testWithdraw() { + // https://scan-wenchang-testnet2.bandchain.org/tx/0df1a6742647224646ed0213abfc012fca54ca42311414c158a3b8799fb61d56 + let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz" + $0.validatorAddress = "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + } + + let message = CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdrawMessage + } + + let fee = CosmosFee.with { + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + $0.gas = 200000 + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz", + "validator_address": "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "yV2ORYqs66hq9wECEqOgLoBx7OghdRCnN8MqZk5cY/40PAm1EGjFKkdNVLNTXuBrskcT0pP/AT0XfMyywvhkWg==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testUndelegate() { + // https://scan-wenchang-testnet2.bandchain.org/tx/cbf84d227f5a8d36e7b529d94b0f93e7107d3706148c79d8c539c09ce4698447 + let unstakeMessage = CosmosMessage.Undelegate.with { + $0.delegatorAddress = "band13tug898kgtwprg7fevzzqgh45draa3cyffw3kp" + $0.validatorAddress = "bandvaloper1jp633fleakzv4uxxvl707j9u2jj6j5x2rg7glv" + $0.amount = CosmosAmount.with { + $0.amount = 500000 + $0.denom = "uband" + } + } + + let message = CosmosMessage.with { + $0.unstakeMessage = unstakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 3 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgUndelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uband" + }, + "delegator_address": "band13tug898kgtwprg7fevzzqgh45draa3cyffw3kp", + "validator_address": "bandvaloper1jp633fleakzv4uxxvl707j9u2jj6j5x2rg7glv" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "mdWnaIfRASZoCs0HKEk0dCL3S3ky1fbh1wp76M7Cov0D8fiByoxOfNknGgDwZecmwhZ4Gf66E+25B5hBCJpY/A==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testRedlegate() { + // https://scan-wenchang-testnet2.bandchain.org/tx/92b351e9aa2f7faae6cd556db04737a1fff778ea0306e2dfb064ccec76a41b13 + let restakeMessage = CosmosMessage.BeginRedelegate.with { + $0.delegatorAddress = "band1hln9scsl9yqup8nxyum06rmggql5m5zqzslg52" + $0.validatorSrcAddress = "bandvaloper1hln9scsl9yqup8nxyum06rmggql5m5zqwxmt3p" + $0.validatorDstAddress = "bandvaloper1hydxm5h8v6tty2x623az65x3r39tl3paxyxtr0" + $0.amount = CosmosAmount.with { + $0.amount = 500000 + $0.denom = "uband" + } + } + + let message = CosmosMessage.with { + $0.restakeMessage = restakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 4 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgBeginRedelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uband" + }, + "delegator_address": "band1hln9scsl9yqup8nxyum06rmggql5m5zqzslg52", + "validator_dst_address": "bandvaloper1hydxm5h8v6tty2x623az65x3r39tl3paxyxtr0", + "validator_src_address": "bandvaloper1hln9scsl9yqup8nxyum06rmggql5m5zqwxmt3p" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "Y+nCZxjvrXPs++VDLiJxDQmp/59Mdwv7OEhgaH4oObtZ7N9+ZVraAiAcxJO26bbcW3cptFf88jxGpWdp6XG9Tg==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } +} \ No newline at end of file diff --git a/swift/Tests/Blockchains/BinanceChainTests.swift b/swift/Tests/Blockchains/BinanceChainTests.swift index 4965311d499..339ed8c43bd 100644 --- a/swift/Tests/Blockchains/BinanceChainTests.swift +++ b/swift/Tests/Blockchains/BinanceChainTests.swift @@ -25,7 +25,7 @@ class BinanceChainTests: XCTestCase { XCTAssertEqual("bnb1devga6q804tx9fqrnx0vtu5r36kxgp9tmk4xkm", address.description) } - func testSigning() { + func testSignSendOrder() { let privateKey = PrivateKey(data: Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")!)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) @@ -63,6 +63,56 @@ class BinanceChainTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679") } + func testSignTransferOutOrder() throws { + let key = PrivateKey(data: Data(hexString: "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let input = BinanceSigningInput.with { + $0.chainID = "test-chain" + $0.accountNumber = 15 + $0.sequence = 1 + $0.privateKey = key.data + $0.transferOutOrder = BinanceTransferOut.with { + $0.from = AnyAddress(publicKey: pubkey, coin: .binance).data + $0.to = AnyAddress(string: "0x35552c16704d214347f29Fa77f77DA6d75d7C752", coin: .ethereum)!.data + $0.amount = BinanceSendOrder.Token.with { + $0.denom = "BNB" + $0.amount = 100000000 + } + $0.expireTime = 12345678 + } + } + + let output: BinanceSigningOutput = AnySigner.sign(input: input, coin: .binance) + + // swiftlint:disable:next line_length + XCTAssertEqual(output.encoded.hexString, "b701f0625dee0a41800819c00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121435552c16704d214347f29fa77f77da6d75d7c7521a0a0a03424e421080c2d72f20cec2f105126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712407eda148e1167b1be1271a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95ef3983cad85a29cd14262c22e0180f2001") + } + + func testSignStakeOrder() throws { + let key = PrivateKey(data: Data(hexString: "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let input = BinanceSigningInput.with { + $0.chainID = "test-chain" + $0.accountNumber = 15 + $0.sequence = 1 + $0.privateKey = key.data + $0.sideDelegateOrder = BinanceSideChainDelegate.with { + $0.delegatorAddr = AnyAddress(publicKey: pubkey, coin: .binance).data + $0.validatorAddr = AnyAddress(string: "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", coin: .binance)!.data + $0.delegation = BinanceSendOrder.Token.with { + $0.denom = "BNB" + $0.amount = 200000000 + } + $0.chainID = "chapel" + } + } + + let output: BinanceSigningOutput = AnySigner.sign(input: input, coin: .binance) + + // swiftlint:disable:next line_length + XCTAssertEqual(output.encoded.hexString, "ba01f0625dee0a44e3a07fd20a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112147cc24a1de5245f14a95e457f903bcc8461ac869c1a0a0a03424e42108084af5f220663686170656c126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7124039302c9975fb2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d9d8f46aeb3627a7d7aa901fe186af34c180f2001") + } + func testSignJSON() { let json = """ { diff --git a/swift/Tests/Blockchains/BinanceSmartChainTests.swift b/swift/Tests/Blockchains/BinanceSmartChainTests.swift new file mode 100644 index 00000000000..a9eed0df2b8 --- /dev/null +++ b/swift/Tests/Blockchains/BinanceSmartChainTests.swift @@ -0,0 +1,20 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 TrustWalletCore +import XCTest + +class BinanceSmartChainTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .smartChain) + let expected = AnyAddress(string: "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coin: .smartChain)! + + XCTAssertEqual(address.description, expected.description) + } +} diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift new file mode 100644 index 00000000000..6ba7dceb8d8 --- /dev/null +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -0,0 +1,130 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 XCTest +import TrustWalletCore + +class BitcoinTransactionSignerTests: XCTestCase { + override func setUp() { + continueAfterFailure = false + } + + func testSignP2WSH() throws { + // set up input + var input = BitcoinSigningInput.with { + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) + $0.amount = 1000 + $0.byteFee = 1 + $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" + } + + input.scripts["593128f9f90e38b706c18623151e37d2da05c229"] = Data(hexString: "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")! + + let p2sh = BitcoinScript.buildPayToWitnessScriptHash(scriptHash: Data(hexString: "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")!) + let utxo0 = BitcoinUnspentTransaction.with { + $0.script = p2sh.data + $0.amount = 1226 + $0.outPoint.hash = Data(hexString: "0001000000000000000000000000000000000000000000000000000000000000")! + $0.outPoint.index = 0 + $0.outPoint.sequence = UInt32.max + } + input.utxo.append(utxo0) + + // Plan + let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .bitcoin) + + XCTAssertEqual(plan.amount, 1000) + XCTAssertEqual(plan.fee, 147) + XCTAssertEqual(plan.change, 79) + + // Extend input with private key + input.privateKey.append(Data(hexString: "ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")!) + input.privateKey.append(Data(hexString: "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")!) + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) + XCTAssertTrue(output.error.isEmpty) + + let signedTx = output.transaction + XCTAssertEqual(signedTx.version, 1) + + let txId = output.transactionID + XCTAssertEqual(txId, "dc60991ff61a6061f55854ce6fb3203b7c8291ed7b2ce799040114c608391583") + + XCTAssertEqual(signedTx.inputs.count, 1) // Only one UTXO available + XCTAssertEqual(signedTx.inputs[0].script.hexString, "") + + XCTAssertEqual(signedTx.outputs.count, 2) // Exact amount + XCTAssertEqual(signedTx.outputs[0].value, 1000) + XCTAssertEqual(signedTx.outputs[1].value, 79) + + let encoded = output.encoded + let witnessHash = Data(Hash.sha256SHA256(data: encoded).reversed()) + XCTAssertEqual(witnessHash.hexString, "ec57de0d46eb45e8019b82e388e458e72fb834dba971e3a45ff8fa7bb7bdb799") + XCTAssertEqual(encoded.hexString, + "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 + ) + } + + func testSignP2SH_P2WPKH() { + let address = "3LGoLac9mtCwDy2q8PYyvwL8kMyrCWCYQW" + let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoin) + let key = PrivateKey(data: Data(hexString: "e240ef3419d038577e48426c8c37c3c13bec1a0ed3f5270b82e7377bc48699dd")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(Data(hexString: "8b5f4861c6d4a4ea361aa4066d720067f73854d9a1b1d01e2b0e3c9e150bc5a3")!.reversed()) + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = lockScript.data + $0.amount = 54700 + } + ] + + let plan = BitcoinTransactionPlan.with { + $0.amount = 43980 + $0.fee = 10720 + $0.change = 0 + $0.utxos = utxos + } + + // redeem p2wpkh nested in p2sh + let scriptHash = lockScript.matchPayToScriptHash()! + let input = BitcoinSigningInput.with { + $0.toAddress = "3NqULUrjZ7NL36YtBGsSVzqr5q1x9CJWwu" + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) + $0.coinType = CoinType.bitcoin.rawValue + $0.scripts = [ + scriptHash.hexString: BitcoinScript.buildPayToWitnessPubkeyHash(hash: pubkey.bitcoinKeyHash).data + ] + $0.privateKey = [key.data] + $0.plan = plan + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) + + // https://blockchair.com/bitcoin/transaction/da2a9ce5d71ff7490bc9025e2888ca109b68ec0bd0e7d26195e1783305c00117 + XCTAssertEqual(output.encoded.hexString, "01000000000101a3c50b159e3c0e2b1ed0b1a1d95438f76700726d06a41a36eaa4d4c661485f8b00000000171600140a3cca78017f46ac23e463148adb7231aef81956ffffffff01ccab00000000000017a914e7f40472c54fc93078c5129568cf95c27be3b2c287024830450221008dc29a5430facd4078ad93e72517d87b298d7a73b55d2828acab040ccf713ed5022063a13e348655fa7cdcfff084380611629babf165607b529bcc35bf6ddfab1f8101210370386469db8302c3092955724f56bcca9a36f31df82655aa79be46b08744cd1200000000") + } + + func testHashTypeForCoin() { + XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoin), TWBitcoinSigHashTypeAll.rawValue) + XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoinCash), 0x41) + XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoinGold), 0x4f41) + } +} diff --git a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift deleted file mode 100644 index 851bf5d5d1b..00000000000 --- a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift +++ /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. - -import XCTest -import TrustWalletCore - -class BitcoinTransactionSignerTests: XCTestCase { - override func setUp() { - continueAfterFailure = false - } - - func testSignP2WSH() throws { - var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue - $0.amount = 1000 - $0.byteFee = 1 - $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" - $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" - } - - input.privateKey.append(Data(hexString: "ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")!) - input.privateKey.append(Data(hexString: "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")!) - - input.scripts["593128f9f90e38b706c18623151e37d2da05c229"] = Data(hexString: "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")! - - let p2sh = BitcoinScript.buildPayToWitnessScriptHash(scriptHash: Data(hexString: "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")!) - let utxo0 = BitcoinUnspentTransaction.with { - $0.script = p2sh.data - $0.amount = 1226 - $0.outPoint.hash = Data(hexString: "0001000000000000000000000000000000000000000000000000000000000000")! - $0.outPoint.index = 0 - $0.outPoint.sequence = UInt32.max - } - input.utxo.append(utxo0) - - let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .bitcoin) - - XCTAssertEqual(plan.amount, 1000) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 0) - - let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) - XCTAssertTrue(output.error.isEmpty) - - let signedTx = output.transaction - XCTAssertEqual(signedTx.version, 1) - - let txId = output.transactionID - XCTAssertEqual(txId, "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd") - - XCTAssertEqual(signedTx.inputs.count, 1) // Only one UTXO available - XCTAssertEqual(signedTx.inputs[0].script.hexString, "") - - XCTAssertEqual(signedTx.outputs.count, 1) // Exact amount - XCTAssertEqual(signedTx.outputs[0].value, 1000) - - let encoded = output.encoded - let witnessHash = Data(Hash.sha256SHA256(data: encoded).reversed()) - XCTAssertEqual(witnessHash.hexString, "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a") - XCTAssertEqual(encoded.hexString, "01000000" + - "0001" + - "01" + - "0001000000000000000000000000000000000000000000000000000000000000" + "00000000" + "00" + "ffffffff" + - "01" + - "e803000000000000" + "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + - "02" + - "4730440220252e92b8757f1e5577c54ce5deb8072914c1f03333128777dee96ebceeb6a99b02202b7298789316779d0aa7595abeedc03054405c42ab9859e67d9253d2c9a0cdfa01232103596d3451025c" + - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + - "00000000" - ) - } -} diff --git a/swift/Tests/Blockchains/BitconCashTests.swift b/swift/Tests/Blockchains/BitconCashTests.swift index b157880c6c9..76a2b0828ed 100644 --- a/swift/Tests/Blockchains/BitconCashTests.swift +++ b/swift/Tests/Blockchains/BitconCashTests.swift @@ -23,9 +23,17 @@ class BitcoinCashTests: XCTestCase { let xpub = "xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw" let coin = CoinType.bitcoinCash - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 0, address: 2))! - - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 0, address: 9))! + 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), "bitcoincash:qq4cm0hcc4trsj98v425f4ackdq7h92rsy6zzstrgy") XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr9), "bitcoincash:qqyqupaugd7mycyr87j899u02exc6t2tcg9frrqnve") @@ -44,11 +52,11 @@ class BitcoinCashTests: XCTestCase { func testLockScript() { let address = AnyAddress(string: "pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l", coin: .bitcoinCash)! - let script = BitcoinScript.buildForAddress(address: address.description, coin: .bitcoinCash) + let script = BitcoinScript.lockScriptForAddress(address: address.description, coin: .bitcoinCash) XCTAssertEqual(script.data.hexString, "a914b9604b7820876bc510009b8247316c4b801aff8a87") let address2 = AnyAddress(string: "qphr8l8ns8wd99a8653ctfe5qcrxaumz5qpmqlk2ex", coin: .bitcoinCash)! - let script2 = BitcoinScript.buildForAddress(address: address2.description, coin: .bitcoinCash) + let script2 = BitcoinScript.lockScriptForAddress(address: address2.description, coin: .bitcoinCash) XCTAssertEqual(script2.data.hexString, "76a9146e33fcf381dcd297a7d52385a73406066ef362a088ac") } @@ -61,11 +69,11 @@ class BitcoinCashTests: XCTestCase { $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.buildForAddress(address: address, coin: .bitcoinCash).data // Build lock script from address or public key hash + $0.script = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoinCash).data // Build lock script from address or public key hash } let input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue | BitcoinSigHashType.fork.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoinCash) $0.amount = 600 $0.byteFee = 1 $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" diff --git a/swift/Tests/Blockchains/DashTests.swift b/swift/Tests/Blockchains/DashTests.swift index 211d491cc93..2af2b70a34c 100644 --- a/swift/Tests/Blockchains/DashTests.swift +++ b/swift/Tests/Blockchains/DashTests.swift @@ -29,8 +29,16 @@ class DashAddressTests: XCTestCase { let xpub = "xpub6DRhaBX1cn2rRtbpphQTSLYcR3ABXzQeEYoT44MjbwTanhw1ePtNcYTZNeHyrJMsMGTbig4iFMSvht7RviohzFxkpjURgHDThygLqbZ1tib" let coin = CoinType.dash - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 1, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 1, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: coin.purpose, coin: coin.slip44Id, account: 0, change: 1, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: coin.purpose, coin: coin.slip44Id, account: 0, change: 1, address: 9).description + )! XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr2), "Xh4D3Mv6ikL5iR45bEsCtaR8Ub4jkRLpU2") XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey:xpubAddr9), "XvwNJsXVBpvAU92xPwU8phT6wKjJVaBMkk") diff --git a/swift/Tests/Blockchains/Data b/swift/Tests/Blockchains/Data new file mode 120000 index 00000000000..f50c5d874d3 --- /dev/null +++ b/swift/Tests/Blockchains/Data @@ -0,0 +1 @@ +../../../tests/Ethereum/Data \ No newline at end of file diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index 6025f543327..77d8bc7d393 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -22,7 +22,11 @@ class DecredTests: XCTestCase { func testDeriveFromDpub() { let dpub = "dpubZFUmm9oh5zmQkR2Tr2AXS4tCkTWg4B27SpCPFkapZrrAqgU1EwgEFgrmi6EnLGXhak86yDHhXPxFAnGU58W5S4e8NCKG1ASUVaxwRqqNdfP" - let pubkey0 = HDWallet.derive(from: dpub, at: DerivationPath(purpose: .bip44, coinType: .decred, account: 0, change: 0, address: 0))! + let pubkey0 = HDWallet.getPublicKeyFromExtended( + extended: dpub, + coin: .decred, + derivationPath: DerivationPath(purpose: .bip44, coin: CoinType.decred.slip44Id, account: 0, change: 0, address: 0).description + )! XCTAssertEqual(AnyAddress(publicKey: pubkey0, coin: .decred).description, "DsksmLD2wDoA8g8QfFvm99ASg8KsZL8eJFd") } @@ -33,8 +37,8 @@ class DecredTests: XCTestCase { let txHash = Data(Data(hexString: "5015d14dcfd78998cfa13e0325798a74d95bbe75f167a49467303f70dde9bffd")!.reversed()) let utxoAddress = CoinType.decred.deriveAddress(privateKey: key) - let script = BitcoinScript.buildForAddress(address: utxoAddress, coin: .decred) - print(txHash.hexString) + let script = BitcoinScript.lockScriptForAddress(address: utxoAddress, coin: .decred) + let amount = Int64(10_000_000) let utxo = BitcoinUnspentTransaction.with { @@ -45,7 +49,7 @@ class DecredTests: XCTestCase { } let input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .decred) $0.amount = amount $0.byteFee = 1 $0.toAddress = "Dsesp1V6DZDEtcq2behmBVKdYqKMdkh96hL" diff --git a/swift/Tests/Blockchains/DogeTests.swift b/swift/Tests/Blockchains/DogeTests.swift index 5094206a703..c0a1553ca32 100644 --- a/swift/Tests/Blockchains/DogeTests.swift +++ b/swift/Tests/Blockchains/DogeTests.swift @@ -39,7 +39,11 @@ class DogeTests: XCTestCase { func testDeriveFromDpub() { let dgub = "dgub8rjvUmFc6cqR6NRBEj2FBZCHUDUrykPyv24Vea6bCsPex5PzNFrRtr4KN37XgwuVzzC2MikJRW2Ddcp99Ehsqp2iaU4eerNCJVruKxz6Gci" - let pubkey8 = HDWallet.derive(from: dgub, at: DerivationPath(purpose: .bip44, coinType: coin, account: 0, change: 0, address: 8))! + let pubkey8 = HDWallet.getPublicKeyFromExtended( + extended: dgub, + coin: .dogecoin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 8).description + )! let address = BitcoinAddress(publicKey: pubkey8, prefix: coin.p2pkhPrefix)! XCTAssertEqual(address.description, "DLrjRgrVqbbpGrSQUtSYgsiWWMvRz5skQE") diff --git a/swift/Tests/Blockchains/EOSTests.swift b/swift/Tests/Blockchains/EOSTests.swift index 259c07e0e7b..2eca9f62e4e 100644 --- a/swift/Tests/Blockchains/EOSTests.swift +++ b/swift/Tests/Blockchains/EOSTests.swift @@ -55,8 +55,6 @@ class EOSTests: XCTestCase { func testSigning() throws { let ouptut: EOSSigningOutput = AnySigner.sign(input: signingInput, coin: .eos) - print(signingInput.privateKey.hexString) - print(try! signingInput.jsonString()) let expectedJSON = """ { diff --git a/swift/Tests/Blockchains/ElrondTests.swift b/swift/Tests/Blockchains/ElrondTests.swift new file mode 100644 index 00000000000..e82ad7f0aeb --- /dev/null +++ b/swift/Tests/Blockchains/ElrondTests.swift @@ -0,0 +1,53 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 TrustWalletCore +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 b2e2a618e76..71ff407a414 100644 --- a/swift/Tests/Blockchains/EthereumAbiTests.swift +++ b/swift/Tests/Blockchains/EthereumAbiTests.swift @@ -9,7 +9,7 @@ import TrustWalletCore class EthereumAbiTests: XCTestCase { func testAbiEncoder() { - let function = EthereumAbiEncoder.buildFunction(name: "sam")! + let function = EthereumAbiFunction(name: "sam") // add params XCTAssertEqual(0, function.addParamBytes(val: Data(hexString: "0x64617665")!, isOutput: false)) XCTAssertEqual(1, function.addParamBool(val: true, isOutput: false)) @@ -24,20 +24,55 @@ class EthereumAbiTests: XCTestCase { XCTAssertEqual(function.getType(), "sam(bytes,bool,uint256[])") // encode into binrary - XCTAssertEqual(EthereumAbiEncoder.encode(func_in: function).hexString, "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + XCTAssertEqual(EthereumAbi.encode(fn: function).hexString, "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") // original output value XCTAssertEqual(0, function.getParamUInt64(idx: 0, isOutput: true)) // decode output from binary let encodedOutput = Data(hexString: "0x0000000000000000000000000000000000000000000000000000000000000045")! - let decodeRes = EthereumAbiEncoder.decodeOutput(func_in: function, encoded: encodedOutput) + let decodeRes = EthereumAbi.decodeOutput(fn: function, encoded: encodedOutput) XCTAssertEqual(true, decodeRes) // new output value XCTAssertEqual(0x45, function.getParamUInt64(idx: 0, isOutput: true)) } func testValueEncoder() { - let data2 = EthereumAbiValueEncoder.encodeInt32(value: 69) + let data2 = EthereumAbiValue.encodeInt32(value: 69) XCTAssertEqual(data2.hexString, "0000000000000000000000000000000000000000000000000000000000000045") } + + func testValueDecoder() { + let expected = "1234567890987654321" + let inputs = [ + "112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb10000000000000000000000000000000000000000000000000000000000000000" + ] + for input in inputs { + let data = Data(hexString: input)! + XCTAssertEqual(expected, EthereumAbiValue.decodeUInt256(input: data)) + } + } + + func testDecodeApprove() throws { + let data = Data(hexString: "095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed0000000000000000000000000000000000000000000000000000000000000001")! + let url = Bundle(for: EthereumAbiTests.self).url(forResource: "erc20", withExtension: "json")! + let abi = try String(contentsOf: url) + let decoded = EthereumAbi.decodeCall(data: data, abi: abi)! + let expected = """ + { + "function": "approve(address,uint256)", + "inputs": [{ + "name": "_spender", + "type": "address", + "value": "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + }, { + "name": "_value", + "type": "uint256", + "value": "1" + }] + } + """ + XCTAssertJSONEqual(decoded, expected) + } } diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index 391315b8b6c..e0f6837ebb3 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -14,6 +14,11 @@ class EthereumTests: XCTestCase { XCTAssertEqual(anyAddress?.description, "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1") XCTAssertEqual(anyAddress?.coin, .ethereum) + + let invalid = "0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4" + XCTAssertNil(Data(hexString: invalid)) + XCTAssertNil(AnyAddress(string: invalid, coin: .ethereum)) + XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .ethereum)) } func testSigner() { @@ -28,10 +33,13 @@ class EthereumTests: XCTestCase { } let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + let encoded = AnySigner.encode(input: input, coin: .ethereum) + XCTAssertEqual(encoded, output.encoded) XCTAssertEqual(output.v.hexString, "25") XCTAssertEqual(output.r.hexString, "28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276") XCTAssertEqual(output.s.hexString, "67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + XCTAssertEqual(output.encoded.hexString, "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") } func testSignJSON() { @@ -49,4 +57,24 @@ class EthereumTests: XCTestCase { XCTAssertEqual(result, "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10") } + + func testDecode() { + let rawTx = Data(hexString: "0xf8a86484b2d05e008277fb9400000000000c2e074ec69a0dfb2997ba6c7d2e1e80b8441896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba4125a0b55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4dfa077b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d")! + let decoded = AnySigner.decode(data: rawTx, coin: .ethereum) + + let expected = """ + { + "gas": "0x77fb", + "gasPrice": "0xb2d05e00", + "input": "0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41", + "nonce": "0x64", + "to": "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e", + "value": "0x", + "v": "0x25", + "r": "0xb55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4df", + "s": "0x77b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d" + } + """ + XCTAssertJSONEqual(String(data: decoded, encoding: .utf8)!, expected) + } } diff --git a/swift/Tests/Blockchains/FIOTests.swift b/swift/Tests/Blockchains/FIOTests.swift index 06444088bdb..eb38aebef65 100644 --- a/swift/Tests/Blockchains/FIOTests.swift +++ b/swift/Tests/Blockchains/FIOTests.swift @@ -139,4 +139,20 @@ class FIOTests: XCTestCase { XCTAssertEqual(out.json, expectedJson) } + + func testAccountNames() { + XCTAssertEqual( + FIOAccount(string: "FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE")!.description, "hhq2g4qgycfb" + ) + XCTAssertEqual( + FIOAccount(string: "hhq2g4qgycfb")!.description, "hhq2g4qgycfb" + ) + + XCTAssertEqual( + FIOAccount(string: "rewards@wallet")!.description, "rewards@wallet" + ) + + XCTAssertNil(FIOAccount(string: "asdf19s")) + XCTAssertNil(FIOAccount(string: "0x320196ef1b137786be51aa391e78e0a2c756f46b")) + } } diff --git a/swift/Tests/Blockchains/FilecoinTests.swift b/swift/Tests/Blockchains/FilecoinTests.swift index 2fdad85dac3..023099a50e1 100644 --- a/swift/Tests/Blockchains/FilecoinTests.swift +++ b/swift/Tests/Blockchains/FilecoinTests.swift @@ -29,7 +29,7 @@ class FilecoinTests: XCTestCase { let output: FilecoinSigningOutput = AnySigner.sign(input: input, coin: .filecoin) - XCTAssertEqual(output.encoded.hexString, "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac351052600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60befa485903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01") + XCTAssertEqual(output.encoded.hexString, "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922176cc900") } } diff --git a/swift/Tests/Blockchains/GroestlcoinTests.swift b/swift/Tests/Blockchains/GroestlcoinTests.swift index c06ebf05021..4711baf9127 100644 --- a/swift/Tests/Blockchains/GroestlcoinTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTests.swift @@ -58,8 +58,16 @@ class GroestlcoinTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6qXFnWiY6FdT5BQptrzEhHfm1WpaBTFc6MHzR4KwscXGdt6xCqUtrAEjrHdeEsjaYEwVMgjtTvENQ83yo2fmkYYGjTpJoH7vFWKQJp1bg1X" let groestlcoin = CoinType.groestlcoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: groestlcoin.purpose, coinType: groestlcoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: groestlcoin.purpose, coinType: groestlcoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: groestlcoin, + derivationPath: DerivationPath(purpose: groestlcoin.purpose, coin: groestlcoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: groestlcoin, + derivationPath: DerivationPath(purpose: groestlcoin.purpose, coin: groestlcoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .groestlcoin, publicKey: zpubAddr4).description, "grs1quwq6ml2r8rc25tue5ltfa6uc4pdzhtzul3c0rk") XCTAssertEqual(SegwitAddress(hrp: .groestlcoin, publicKey: zpubAddr11).description, "grs1ql0a7czm8wrj253h78dm2h5j2k89zwpy2qjq0q9") diff --git a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift index 542434189be..0eb8606931c 100644 --- a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift @@ -14,7 +14,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2WPKH() throws { var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 2500 $0.byteFee = 1 $0.toAddress = "31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P" @@ -34,8 +34,13 @@ class GroestlcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .groestlcoin) XCTAssertEqual(plan.amount, 2500) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 2048) + XCTAssertEqual(plan.fee, 145) + XCTAssertEqual(plan.change, 2129) + + // Supply plan for signing, to match fee of previously-created real TX + input.plan = plan + input.plan.fee = 226 + input.plan.change = 2048 // https://blockbook.groestlcoin.org/tx/40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64 let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .groestlcoin) @@ -62,7 +67,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2PKH() throws { var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 2500 $0.byteFee = 1 $0.toAddress = "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne" @@ -82,8 +87,13 @@ class GroestlcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .groestlcoin) XCTAssertEqual(plan.amount, 2500) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 2274) + XCTAssertEqual(plan.fee, 221) + XCTAssertEqual(plan.change, 2279) + + // Supply plan for signing, to match fee of previously-created real TX + input.plan = plan + input.plan.fee = 226 + input.plan.change = 2274 // https://blockbook.groestlcoin.org/tx/74a0dd12bc178cfcc1e0982a2a5b2c01a50e41abbb63beb031bcd21b3e28eac0 let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .groestlcoin) @@ -108,7 +118,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2SH_P2WPKH() throws { var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 5000 $0.byteFee = 1 $0.toAddress = "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM" @@ -130,8 +140,13 @@ class GroestlcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .groestlcoin) XCTAssertEqual(plan.amount, 5000) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 4774) + XCTAssertEqual(plan.fee, 167) + XCTAssertEqual(plan.change, 4833) + + // Supply plan for signing, to match fee of previously-created real TX + input.plan = plan + input.plan.fee = 226 + input.plan.change = 4774 // https://blockbook.groestlcoin.org/tx/8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895 let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .groestlcoin) diff --git a/swift/Tests/Blockchains/HarmonyTests.swift b/swift/Tests/Blockchains/HarmonyTests.swift index 172641f323b..c5ac36ebd11 100644 --- a/swift/Tests/Blockchains/HarmonyTests.swift +++ b/swift/Tests/Blockchains/HarmonyTests.swift @@ -12,12 +12,12 @@ class HarmonyTests: XCTestCase { func testSigner() { let transaction = HarmonyTransactionMessage.with { - $0.nonce = Data(hexString: "0x9")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x09")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x5208")! - $0.fromShardID = Data(hexString: "0x1")! - $0.toShardID = Data(hexString: "0x0")! - $0.payload = Data(hexString: "0x")! + $0.fromShardID = Data(hexString: "0x01")! + $0.toShardID = Data() + $0.payload = Data() $0.toAddress = "one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc" $0.amount = Data(hexString: "0x4c53ecdc18a60000")! } @@ -49,16 +49,16 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let rate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let maxRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x9")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x09")! + $0.precision = Data(hexString: "0x01")! } let maxChangeRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x5")! - $0.precision = Data(hexString: "0x2")! + $0.value = Data(hexString: "0x05")! + $0.precision = Data(hexString: "0x02")! } let commission = HarmonyCommissionRate.with { $0.rate = rate @@ -71,7 +71,7 @@ class HarmonyTests: XCTestCase { $0.validatorAddress = oneAddress $0.description_p = description $0.commissionRates = commission - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotPubKeys = [pubKey] $0.slotKeySigs = [blsSig] @@ -79,8 +79,8 @@ class HarmonyTests: XCTestCase { } let staking = HarmonyStakingMessage.with { $0.createValidatorMessage = createValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x0")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data(hexString: "0x00")! $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -107,24 +107,24 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let commissionRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let editValidator = HarmonyDirectiveEditValidator.with { $0.validatorAddress = oneAddress $0.description_p = desc $0.commissionRate = commissionRate - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotKeyToRemove = pubKeyData $0.slotKeyToAdd = pubKeyData $0.slotKeyToAddSig = blsSigData - $0.active = Data(hexString: "0x1")! + $0.active = Data(hexString: "0x01")! } let staking = HarmonyStakingMessage.with { $0.editValidatorMessage = editValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -151,24 +151,24 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let commissionRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let editValidator = HarmonyDirectiveEditValidator.with { $0.validatorAddress = oneAddress $0.description_p = desc $0.commissionRate = commissionRate - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotKeyToRemove = pubKeyData $0.slotKeyToAdd = pubKeyData $0.slotKeyToAddSig = blsSigData - $0.active = Data(hexString: "0x2")! + $0.active = Data(hexString: "0x02")! } let staking = HarmonyStakingMessage.with { $0.editValidatorMessage = editValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -194,24 +194,24 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let commissionRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let editValidator = HarmonyDirectiveEditValidator.with { $0.validatorAddress = oneAddress $0.description_p = desc $0.commissionRate = commissionRate - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotKeyToRemove = pubKeyData $0.slotKeyToAdd = pubKeyData $0.slotKeyToAddSig = blsSigData - $0.active = Data(hexString: "0x0")! + $0.active = Data() } let staking = HarmonyStakingMessage.with { $0.editValidatorMessage = editValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -233,12 +233,12 @@ class HarmonyTests: XCTestCase { let delegate = HarmonyDirectiveDelegate.with { $0.delegatorAddress = oneAddress $0.validatorAddress = oneAddress - $0.amount = Data(hexString: "0xa")! + $0.amount = Data(hexString: "0x0a")! } let staking = HarmonyStakingMessage.with { $0.delegateMessage = delegate - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -262,12 +262,12 @@ class HarmonyTests: XCTestCase { let undelegate = HarmonyDirectiveUndelegate.with { $0.delegatorAddress = oneAddress $0.validatorAddress = oneAddress - $0.amount = Data(hexString: "0xa")! + $0.amount = Data(hexString: "0x0a")! } let staking = HarmonyStakingMessage.with { $0.undelegateMessage = undelegate - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -293,8 +293,8 @@ class HarmonyTests: XCTestCase { } let staking = HarmonyStakingMessage.with { $0.collectRewards = collectRewards - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { diff --git a/swift/Tests/Blockchains/IoTeXTests.swift b/swift/Tests/Blockchains/IoTeXTests.swift index 1209db8d4e1..0e68e5ab83f 100644 --- a/swift/Tests/Blockchains/IoTeXTests.swift +++ b/swift/Tests/Blockchains/IoTeXTests.swift @@ -8,7 +8,6 @@ import XCTest import TrustWalletCore class IoTeXTests: XCTestCase { - func testSign() { let privateKey = PrivateKey(data: Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")!)! @@ -29,81 +28,182 @@ class IoTeXTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "0a39080110011801220131522e0a01311229696f3165326e7173797437666b707a733578377a6632756b306a6a3732746575356e36616b75337472124104fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5cf762f20459c9100eb9d4d7597f5817bf21e10b53a0120b9ec1ba5cddfdcb669b1a41ec9757ae6c9009315830faaab250b6db0e9535b00843277f596ae0b2b9efc0bd4e14138c056fc4cdfa285d13dd618052b3d1cb7a3f554722005a2941bfede96601") } - func stakingInput() -> IoTeXSigningInput { - let input = IoTeXSigningInput.with { + func testSignStakingCreate() { + var input = IoTeXSigningInput.with { $0.version = 1 - $0.nonce = 123 - $0.gasLimit = 888 - $0.gasPrice = "999" - $0.staking = IoTeXStaking.with { - $0.amount = "456" - $0.contract = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" - } - $0.privateKey = Data(hexString: "0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")! + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! } - return input + input.stakeCreate = IoTeXStaking.Create.with { + $0.candidateName = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + $0.stakedAmount = "100" + $0.stakedDuration = 10000 + $0.autoStake = true + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) + + XCTAssertEqual(output.encoded.hexString, "0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5aed8e2e026d46e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801") + XCTAssertEqual(output.hash.hexString, "f1785e47b4200c752bb6518bd18097a41e075438b8c18c9cb00e1ae2f38ce767") } - func testStake() { - // candidate name is a string - let candidate = "\u{01}\u{02}\u{03}\u{04}\u{05}\u{06}\u{07}\u{08}\t\n\u{0B}\u{0C}" - var input = stakingInput() - input.staking.stake = IoTeXStaking.Stake.with { - $0.candidate = candidate - $0.duration = 1001 - $0.nonDecay = true - $0.data = "this is a test".data(using: .utf8)! + func testSignStakingAddDeposit() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeAddDeposit = IoTeXStaking.AddDeposit.with { + $0.bucketIndex = 10 + $0.amount = "10" + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0a86020801107b18f806220339393962f7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611ac40107c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74686973206973206120746573740000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41a558bc9a4bfba920242ccd4d5c5da363ec534d4dd5eb67f88e9db7aaad5c50ad62dfe298c0e54e311ebba045f48cea1136e42a123a8e6b03d3e6ed82d4ec2b9401") - XCTAssertEqual(output.hash.hexString, "41b1f8be5f6b884c06556fba2611716e8e514b507f5a653fc02ac50ba13fbd6c") + XCTAssertEqual(output.encoded.hexString, "0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb55e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01") + XCTAssertEqual(output.hash.hexString, "ca8937d6f224a4e4bf93cb5605581de2d26fb0481e1dfc1eef384ee7ccf94b73") } + + func testSignStakingUnstake() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeUnstake = IoTeXStaking.Reclaim.with { + $0.bucketIndex = 10 + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - func testUnstake() { - var input = stakingInput() - input.staking.unstake = IoTeXStaking.Unstake.with { - $0.piggyIndex = 1001 + XCTAssertEqual(output.encoded.hexString, "0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601") + XCTAssertEqual(output.hash.hexString, "bed58b64a6c4e959eca60a86f0b2149ce0e1dd527ac5fd26aef725ebf7c22a7d") + } + + func testSignStakingWithdraw() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeWithdraw = IoTeXStaking.Reclaim.with { + $0.bucketIndex = 10 + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0aa5010801107b18f80622033939396296010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a64c8fd6ed000000000000000000000000000000000000000000000000000000000000003e9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41eeb7cb3fa7ec22a61156753d569b3f4da3c74c3c7e2f148b1a43e11d220cac5d164663ff6c785439679b088de9d7f2437545f007ca9cda4b2f5327d2c6eda5aa01") - XCTAssertEqual(output.hash.hexString, "b93a2874a72ce4eb8a41a20c209cf3fd188671ed8be8239a57960cbed887e962") + XCTAssertEqual(output.encoded.hexString, "0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00") + XCTAssertEqual(output.hash.hexString, "28049348cf34f1aa927caa250e7a1b08778c44efaf73b565b6fa9abe843871b4") } - func testWithdraw() { - var input = stakingInput() - input.staking.withdraw = IoTeXStaking.Withdraw.with { - $0.piggyIndex = 1001 + func testSignStakingRestake() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeRestake = IoTeXStaking.Restake.with { + $0.bucketIndex = 10 + $0.stakedDuration = 1000 + $0.autoStake = true + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0aa5010801107b18f80622033939396296010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a64030ba25d00000000000000000000000000000000000000000000000000000000000003e9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41903c79d042f6b1c05446ececb3d760ca154c539b5787e66135cd3db77638294d18dbbbbcb0de8b9a393cc7c0448cf246898e4343a2a51666e21e738ee6d8a6f700") - XCTAssertEqual(output.hash.hexString, "2b2657247a72cb262de214b4e793c7a01fa2139fd5d12a46d43c24f87f9e2396") + XCTAssertEqual(output.encoded.hexString, "0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418eedbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101") + XCTAssertEqual(output.hash.hexString, "8816e8f784a1fce40b54d1cd172bb6976fd9552f1570c73d1d9fcdc5635424a9") } - func testAddStake() { - var input = stakingInput() - input.staking.addStake = IoTeXStaking.AddStake.with { - $0.piggyIndex = 1001 + func testSignStakingChangeCandidate() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeChangeCandidate = IoTeXStaking.ChangeCandidate.with { + $0.bucketIndex = 10 + $0.candidateName = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) + + XCTAssertEqual(output.encoded.hexString, "0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f81895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801") + XCTAssertEqual(output.hash.hexString, "186526b5b9fe74e25beb52c83c41780a69108160bef2ddaf3bffb9f1f1e5e73a") + } + + func testSignStakingTransfer() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeTransferOwnership = IoTeXStaking.TransferOwnership.with { + $0.bucketIndex = 10 + $0.voterAddress = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) + + XCTAssertEqual(output.encoded.hexString, "0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01") + XCTAssertEqual(output.hash.hexString, "74b2e1d6a09ba5d1298fa422d5850991ae516865077282196295a38f93c78b85") + } + + func testSignCandidateRegister() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "1000" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.candidateRegister = IoTeXStaking.CandidateRegister.with { + $0.candidate = IoTeXStaking.CandidateBasicInfo.with { + $0.name = "test" + $0.operatorAddress = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he" + $0.rewardAddress = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv" + } + $0.stakedAmount = "100" + $0.stakedDuration = 10000 + $0.autoStake = false + $0.ownerAddress = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0aa5010801107b18f80622033939396296010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a646e7b301700000000000000000000000000000000000000000000000000000000000003e9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a410f0832bb7a48c9e468c0bcb2c4a35a202f9519a63c4d6474b48087ab9dc33aea18127940d5cda43710cd874cbdf7a7b26efc9c04236e14dfb4d9b6f7095b0b6c01") - XCTAssertEqual(output.hash.hexString, "c71058812a5febe5cdcdaf9499ba0b2c895f88d1acd3203e5097b307c2a5f1d1") + XCTAssertEqual(output.encoded.hexString, "0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a32077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a9179b141ac68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400") + XCTAssertEqual(output.hash.hexString, "35f53a536e014b32b85df50483ef04849b80ad60635b3b1979c5ba1096b65237") } - func testMoveStake() { - // candidate name is a string - let candidate = "\u{01}\u{02}\u{03}\u{04}\u{05}\u{06}\u{07}\u{08}\t\n\u{0B}\u{0C}" - var input = stakingInput() - input.staking.moveStake = IoTeXStaking.MoveStake.with { - $0.piggyIndex = 1001 - $0.candidate = candidate + func testSignCandidateUpdate() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.candidateUpdate = IoTeXStaking.CandidateBasicInfo.with { + $0.name = "test" + $0.operatorAddress = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng" + $0.rewardAddress = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd" } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0ac6010801107b18f806220339393962b7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a8401d3e41fd200000000000000000000000000000000000000000000000000000000000003e90102030405060708090a0b0c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a4118b71944993d1cd5362379ff64e892024a75ce697b2755cc9bbfcac24482a19b1ec2951bec7461ef9eb0723f803987cb87e3c3afb340006cd5b413c1fac10d7c01") - XCTAssertEqual(output.hash.hexString, "33290ded342efaebf795855be73d34cbac149a2415ff9558de10303e6126f30d") + XCTAssertEqual(output.encoded.hexString, "0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c32657635646661393838716d677a673278346866617a6d7039766e326736366e671a29696f316a757678356730363365753474733833326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701") + XCTAssertEqual(output.hash.hexString, "ca1a28f0e9a58ffc67037cc75066dbdd8e024aa2b2e416e4d6ce16c3d86282e5") } -} +} \ No newline at end of file diff --git a/swift/Tests/Blockchains/KusamaTests.swift b/swift/Tests/Blockchains/KusamaTests.swift index c089c409980..a47d097e772 100644 --- a/swift/Tests/Blockchains/KusamaTests.swift +++ b/swift/Tests/Blockchains/KusamaTests.swift @@ -32,59 +32,18 @@ class KusamaTests: XCTestCase { XCTAssertEqual(address.data.hexString, pubkey.data.hexString) } - func testStorageKey() { - func generateStorageKey(module: String, function: String, publicKey: Data) -> Data { - var data = Data() - data.append(Hash.twoXXHash64Concat(data: module.data(using: .utf8)!)) - data.append(Hash.twoXXHash64Concat(data: function.data(using: .utf8)!)) - data.append(Hash.blake2b(data: publicKey, size: 32)) - return data - } - - let address = AnyAddress(string: "HKtMPUSoTC8Hts2uqcQVzPAuPRpecBt4XJ5Q1AT1GM3tp2r", coin: .kusama)! - let key = generateStorageKey(module: "Balances", function: "FreeBalance", publicKey: address.data) - XCTAssertEqual(key.hexString, "c2261276cc9d1f8598ea4b6a74b15c2f6482b9ade7bc6657aaca787ba1add3b4c801483fa04e8fd48dc5c5675891cfaab709696db6de3184d95d26a1c894f1f8") - } - func testSigningTransfer() { - let key = PrivateKey(data: Data(hexString: "0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")!)! - let address = CoinType.kusama.deriveAddress(privateKey: key) - - XCTAssertEqual(address.description, "FfmSiZNJP72xtSaXiP2iUhBwWeMEvmjPrxY2ViVkWaeChDC") - - let genesisHash = Data(hexString: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe")! - let input = PolkadotSigningInput.with { - $0.genesisHash = genesisHash - $0.blockHash = genesisHash - $0.nonce = 0 - $0.specVersion = 1031 - $0.balanceCall = PolkadotBalance.with { - $0.transfer = PolkadotBalance.Transfer.with { - $0.toAddress = "FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP" - $0.value = Data(hexString: "3039")! // 12345 - } - } - $0.network = .kusama - $0.extrinsicVersion = 4 - $0.privateKey = key.data - } - let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .kusama) - - XCTAssertEqual(output.encoded.hexString, "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") - } - - func testSigningTransfer2() { - // https://kusama.subscan.io/extrinsic/0x20cfbba19817e4b7a61e718d269de47e7067a24860fa978c2a8ead4c96a827c4 - // 1p test wallet + // https://kusama.subscan.io/extrinsic/0x9211b8f6500c78f4771d18289c6187ec59c2b1fb28e8324ee32a1f9a3303be7e + // real key in 1p test let wallet = HDWallet.test - let key = wallet.getKeyForCoin(coin: .kusama) + let key = wallet.getKey(coin: .kusama, derivationPath: "m/44'/434'/0'") let genesisHash = Data(hexString: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe")! let input = PolkadotSigningInput.with { $0.genesisHash = genesisHash $0.blockHash = genesisHash - $0.nonce = 0 - $0.specVersion = 1031 + $0.nonce = 1 + $0.specVersion = 2019 $0.balanceCall = PolkadotBalance.with { $0.transfer = PolkadotBalance.Transfer.with { $0.toAddress = "CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY" @@ -93,11 +52,11 @@ class KusamaTests: XCTestCase { } } $0.network = .kusama - $0.extrinsicVersion = 4 + $0.transactionVersion = 2 $0.privateKey = key.data } let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .kusama) - XCTAssertEqual(output.encoded.hexString, "3d0284fff41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae70043e0fe7497f1d11ca6635b7860ef9551d395172b18af22c16e375326326f524cd32ffafb3e1e73112f016a8028c50eebb608df29523751a11147e36a49f2d40a0000000400ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402") + XCTAssertEqual(output.encoded.hexString, "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402") } } diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index 51e951bd321..aa2007ece7b 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -62,8 +62,16 @@ class LitecoinTests: XCTestCase { func testDeriveFromLtub() { let xpub = "Ltub2Ye6FtTv7U4zzHDL6iMfcE3cj5BHJjkBXQj1deZEAgSBrHB5oM191hYTF8BC34r7vRDGng59yfP6FH4m3nttc3TLDg944G8QK7d5NnygCRu" let litecoin = CoinType.litecoin - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: litecoin, account: 0, change: 0, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: litecoin, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip44, coin: litecoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip44, coin: litecoin.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.litecoin.p2pkhPrefix)!.description, "LdJvSS8gcRSN1WbSEj6srV8dKzGcybHGKt") XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.litecoin.p2pkhPrefix)!.description, "Laj4byUKgW3wuou4G3XCAPWqzVc3SdEpQk") @@ -73,8 +81,16 @@ class LitecoinTests: XCTestCase { let ypub = "Mtub2sZjeBCxVccvybLHSD1i3Aw38QvCTDadaPyXbSkRRX1RQm3mxtfsbQU5M3PdCSP4xAFHCceEQ3FmQF69Du2wbcmebt3CaWAGALBSe8c4Gvw" let litecoin = CoinType.litecoin - let ypubAddr3 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: litecoin, account: 0, change: 0, address: 3))! - let ypubAddr10 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: litecoin, account: 0, change: 0, address: 10))! + let ypubAddr3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypubAddr10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 10).description + )! XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr3, prefix: CoinType.litecoin.p2shPrefix).description, "MVr2vvjyaTzmfX3LFZcg5KZ7Cc36pgAWcy") XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr10, prefix: CoinType.litecoin.p2shPrefix).description, "MTgkF6T5h92QDmpFsBk4fJeYt3dx5ERQtD") @@ -83,10 +99,83 @@ class LitecoinTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs" let litecoin = CoinType.litecoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: litecoin.purpose, coinType: litecoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: litecoin.purpose, coinType: litecoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .litecoin, publicKey: zpubAddr4).description, "ltc1qcgnevr9rp7aazy62m4gen0tfzlssa52axwytt6") XCTAssertEqual(SegwitAddress(hrp: .litecoin, publicKey: zpubAddr11).description, "ltc1qy072y8968nzp6mz3j292h8lp72d678fcmms6vl") } + + func testPlanAndSign_8435() throws { + let address = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum" + let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .litecoin) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(Data(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407")!.reversed()) + $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 = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00" + $0.changeAddress = address + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .litecoin) + $0.amount = 1200000 + $0.coinType = CoinType.litecoin.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: .litecoin) + + XCTAssertEqual(plan.amount, 1200000) + XCTAssertEqual(plan.fee, 141) + XCTAssertEqual(plan.change, 2699633) + + // Extend input with private key + input.privateKey = [Data(hexString: "690b34763f34e0226ad2a4d47098269322e0402f847c97166e8f39959fcaff5a")!] + input.plan = plan + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .litecoin) + XCTAssertTrue(output.error.isEmpty) + + // https://blockchair.com/litecoin/transaction/8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8 + let txId = output.transactionID + XCTAssertEqual(txId, "8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8") + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "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 + ) + } } diff --git a/swift/Tests/Blockchains/MonacoinTests.swift b/swift/Tests/Blockchains/MonacoinTests.swift index fa5a18c59bf..9b02e80f2b2 100644 --- a/swift/Tests/Blockchains/MonacoinTests.swift +++ b/swift/Tests/Blockchains/MonacoinTests.swift @@ -62,8 +62,16 @@ class MonacoinTests: XCTestCase { func testDeriveFromXpub() { let xpub = "xpub6CYWFE1BgTCW2vtbDm1RRT81i3hBkQrXCfGs5hYp211fpgLZV5xCEwXMWPAL3LgaBA9koXpLZSUo7rTyJ8q1JwqKhvzVpdzBKRGyyGb31KF" let monacoin = CoinType.monacoin - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: monacoin, account: 0, change: 0, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: monacoin, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip44, coin: monacoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip44, coin: monacoin.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.monacoin.p2pkhPrefix)!.description, "MCoYzbqdsMYTBbjr7rd2zJsSF32QMgZCSj") XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.monacoin.p2pkhPrefix)!.description, "MAtduu1Fvtv1Frx6vbg5tZDZwirCA3y8qq") @@ -73,8 +81,16 @@ class MonacoinTests: XCTestCase { let ypub = "ypub6YKchgn8hmHJ9a1c2wy1ydge6ez5AcWBVSwURTnC93yj6MT1tCUN3qvuZZPsA1CwZVh5qEGhMWhDZEK43jQqWtHBzME91ws9KD6WU9n8Nau" let monacoin = CoinType.monacoin - let ypubAddr3 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: monacoin, account: 0, change: 0, address: 3))! - let ypubAddr10 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: monacoin, account: 0, change: 0, address: 10))! + let ypubAddr3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip49, coin: monacoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypubAddr10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip49, coin: monacoin.slip44Id, account: 0, change: 0, address: 10).description + )! XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr3, prefix: CoinType.monacoin.p2shPrefix).description, "PRAnwctxh9UWFdjCcrQy2Ym1SxMgcjTpRx") XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr10, prefix: CoinType.monacoin.p2shPrefix).description, "PNA4qYzxsVfFXQ3bBSfMhVqumZHAJVZAaQ") @@ -83,8 +99,16 @@ class MonacoinTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6rPmNCEpXnLtvBTZyCWnJDr6QVyBaELfVX6kQeAXtZEAFLRCzWEBc2V35UHUQKJh1SpSNCtAtCx8KhRg5AWFnKrMCsxX4J2Zee21FQ5YS4n" let monacoin = CoinType.monacoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: monacoin.purpose, coinType: monacoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: monacoin.purpose, coinType: monacoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip84, coin: monacoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip84, coin: monacoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .monacoin, publicKey: zpubAddr4).description, "mona1qkrylcw85ulyrar9wt35huvpu6hlqwfg2gxf523") XCTAssertEqual(SegwitAddress(hrp: .monacoin, publicKey: zpubAddr11).description, "mona1qulanqvye6gmsf03m0cahr8dwtmj8gy53y8rc6n") diff --git a/swift/Tests/Blockchains/NEARTests.swift b/swift/Tests/Blockchains/NEARTests.swift index 696b4c31a94..699b8a5e8b8 100644 --- a/swift/Tests/Blockchains/NEARTests.swift +++ b/swift/Tests/Blockchains/NEARTests.swift @@ -7,7 +7,20 @@ import XCTest import TrustWalletCore -class NEARAddressTests: XCTestCase { +class NEARTests: XCTestCase { + + func testAddress() { + let hex = "3b83b07cab54824a59c3d3f2e203a7cd913b7fcdc4439595983e2402c2cf791d" + let string = "NEARTDDWrUMdoC2rA1eU6gNrSU2zyGKdR71TNucTvsQHyfAXjKcJb" + let pubKey = PublicKey(data: Data(hexString: hex)!, type: .ed25519)! + let address = AnyAddress(publicKey: pubKey, coin: .near) + let expected = AnyAddress(string: string, coin: .near)! + + XCTAssertEqual(address, expected) + XCTAssertEqual(address.description, string) + XCTAssertEqual(expected.data.hexString, hex) + } + func testAddressValidation() { let near = CoinType.near for address in [ @@ -18,9 +31,6 @@ class NEARAddressTests: XCTestCase { XCTAssertEqual(near.address(string: address)?.description, address) } } -} - -class NEARSignerTests: XCTestCase { func testSigningTransaction() { // swiftlint:disable:next line_length diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index 6068414f23e..f64ef1595dd 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -31,4 +31,32 @@ class PolkadotTests: XCTestCase { XCTAssertEqual(address.description, addressFromString.description) XCTAssertEqual(address.data, pubkey.data) } + + func testSigningBond() { + // https://polkadot.subscan.io/extrinsic/0x5ec2ec6633b4b6993d9cf889ef42c457a99676244dc361a9ae17935d331dc39a + // real key in 1p test + let wallet = HDWallet.test + let key = wallet.getKey(coin: .polkadot, derivationPath: "m/44'/354'/0'") + let address = CoinType.polkadot.deriveAddress(privateKey: key) + + let genesisHash = Data(hexString: "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")! + let input = PolkadotSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = genesisHash + $0.nonce = 0 + $0.specVersion = 17 + $0.network = .polkadot + $0.transactionVersion = 3 + $0.privateKey = key.data + $0.stakingCall.bond = PolkadotStaking.Bond.with { + $0.controller = address + $0.rewardDestination = .staked + // 0.01 + $0.value = Data(hexString: "0x02540be400")! + } + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + XCTAssertEqual(output.encoded.hexString, "3902848d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa00a559867d1304cc95bac7cfe5d1b2fd49aed9f43c25c7d29b9b01c1238fa1f6ffef34b9650e42325de41e20fd502af7b074c67a9ec858bd9a1ba6d4212e3e0d0f00000007008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0700e40b540200") + } } diff --git a/swift/Tests/Blockchains/QtumTests.swift b/swift/Tests/Blockchains/QtumTests.swift index 230de9d1efd..090a7963f16 100644 --- a/swift/Tests/Blockchains/QtumTests.swift +++ b/swift/Tests/Blockchains/QtumTests.swift @@ -55,8 +55,17 @@ class QtumTests: XCTestCase { func testDeriveFromXpub() { let xpub = "xpub6CAkJZPecMDxRXEXZpDwyxcQ6CGie8GdovuJhsGwc2gFbLxdGr1PyqBXmsL7aYds1wfY2rB3YMVZiEE3CB3Lkj6KGoq1rEJ1wuaGkMDBf1m" let qtum = CoinType.qtum - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: qtum.purpose, coinType: qtum, account: 0, change: 0, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: qtum.purpose, coinType: qtum, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: qtum, + derivationPath: DerivationPath(purpose: qtum.purpose, coin: qtum.slip44Id, account: 0, change: 0, address: 2).description + )! + + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: qtum, + derivationPath: DerivationPath(purpose: qtum.purpose, coin: qtum.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.qtum.p2pkhPrefix)!.description, "QStYeAAfiYKxsABzY9yugHDpm5bsynYPqc") XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.qtum.p2pkhPrefix)!.description, "QfbKFChfhx1s4VXS9BzaVJgyKw5a1hnFg4") @@ -65,8 +74,16 @@ class QtumTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6rJJqJZcpaC7DrdsYiprLfUfvtaf11ZZWmrmYeWMkdZTx6tgfQLiBZuisraogskwBRLMGWfXoCyWRrXSypwPdNV2UWJXm5bDVQvBXvrzz9d" let qtum = CoinType.qtum - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: .bip84, coinType: qtum, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: .bip84, coinType: qtum, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: qtum, + derivationPath: DerivationPath(purpose: .bip84, coin: qtum.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: qtum, + derivationPath: DerivationPath(purpose: .bip84, coin: qtum.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .qtum, publicKey: zpubAddr4).description, "qc1q3cvjmc2cgjkz9y58waj3r9ccchmrmrdzq03783") XCTAssertEqual(SegwitAddress(hrp: .qtum, publicKey: zpubAddr11).description, "qc1qrlk0ajg6khu2unsdppggs3pgpxxvdeymky58af") diff --git a/swift/Tests/Blockchains/ZcashTests.swift b/swift/Tests/Blockchains/ZcashTests.swift index 4ceb069c4c2..dca6f59f969 100644 --- a/swift/Tests/Blockchains/ZcashTests.swift +++ b/swift/Tests/Blockchains/ZcashTests.swift @@ -34,7 +34,11 @@ class ZcashTests: XCTestCase { func testDeriveFromXpub() { let xpub = "xpub6C7HhMqpir3KBA6ammv5B58RT3XFTJqoZFoj3J56dz9XwehZ2puSH38ERtnz7HaXGxaZP8AHT4M2bSRHpBXUZrbsJ2xg3xs53DGKYCqj8mr" - let pubkey = HDWallet.derive(from: xpub, at: DerivationPath(purpose: zcash.purpose, coinType: zcash))! + let pubkey = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: .zcash, + derivationPath: DerivationPath(purpose: zcash.purpose, coin: zcash.slip44Id).description + )! let address = AnyAddress(publicKey: pubkey, coin: .zcash) XCTAssertEqual(address.description, "t1TKCtCETHPrAdA6eY1fdhhnTkTmb371oPt") @@ -51,7 +55,7 @@ class ZcashTests: XCTestCase { } ] let input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .zcash) $0.amount = 488000 $0.toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS" $0.coinType = CoinType.zcash.rawValue diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 81b55b548ed..6f60eceb4d1 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -21,175 +21,187 @@ class CoinAddressDerivationTests: XCTestCase { switch coin { case .aion: let expectedResult = "0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .binance: let expectedResult = "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cosmos: let expectedResult = "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoin: let expectedResult = "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoinCash: let expectedResult = "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bitcoinGold: + let expectedResult = "btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .callisto: let expectedResult = "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .dash: let expectedResult = "XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .dogecoin: let expectedResult = "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .digiByte: let expectedResult = "dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) - case .ethereum: + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ethereum, .smartChain: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ethereumClassic: let expectedResult = "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .goChain: let expectedResult = "0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .icon: let expectedResult = "hx18b380b53c23dc4ee9f6666bc20d1be02f3fe106" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .litecoin: let expectedResult = "ltc1qhd8fxxp2dx3vsmpac43z6ev0kllm4n53t5sk0u" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ontology: let expectedResult = "AHKTnybvnWo3TeY8uvNXekvYxMrXogUjeT" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .poanetwork: let expectedResult = "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .xrp: let expectedResult = "rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tezos: let expectedResult = "tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .thunderToken: let expectedResult = "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tomoChain: let expectedResult = "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tron: let expectedResult = "TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .veChain: let expectedResult = "0x1a553275dF34195eAf23942CB7328AcF9d48c160" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .wanchain: let expectedResult = "0xd5CA90B928279fe5d06144136A25dEd90127Ac15" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zcash: let expectedResult = "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zcoin: let expectedResult = "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nimiq: let expectedResult = "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .stellar: let expectedResult = "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nano: let expectedResult = "nano_39gsbcishxn3n7wd17ono4otq5wazwzusqgqigztx73wbrh5jwbdbshfnumc" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .near: let expectedResult = "NEAR6Y66fCzeKqWiwxoPox5oGeDN9VhNCu7CEQ9M86iniqoN9vg2X" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nebulas: let expectedResult = "n1ZVgEidtdseYv9ogmGz69Cz4mbqmHYSNqJ" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .kin: let expectedResult = "GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .decred: let expectedResult = "DsidJiDGceqHTyqiejABy1ZQ3FX4SiWZkYG" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .theta: let expectedResult = "0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .groestlcoin: let expectedResult = "grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .viacoin: let expectedResult = "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .qtum: let expectedResult = "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .eos: let expectedResult = "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ioTeX: let expectedResult = "io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zilliqa: let expectedResult = "zil1mk6pqphhkmaguhalq6n3cq0h38ltcehg0rfmv6" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zelcash: let expectedResult = "t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ravencoin: let expectedResult = "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .waves: let expectedResult = "3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .aeternity: let expectedResult = "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .terra: let expectedResult = "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .monacoin: let expectedResult = "M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .fio: let expectedResult = "FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .harmony: let expectedResult = "one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .solana: let expectedResult = "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ton: let expectedResult = "EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .algorand: let expectedResult = "JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nuls: let expectedResult = "NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .kusama: - let expectedResult = "DE2jNrgosggXWJXfYDmRgy1q8XKkbtzSxj2uWAy5fbBfZwT" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + let expectedResult = "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .polkadot: - let expectedResult = "1b97X8xTpFKMDzJpxiVhdYMNvekBDSfvGFf4DutxFkUjqfR" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .kava: let expectedResult = "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bandChain: + let expectedResult = "band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cardano: let expectedResult = "addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .neo: let expectedResult = "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .filecoin: let expectedResult = "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .elrond: + let expectedResult = "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .smartChainLegacy: + let expectedResult = "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } @@ -197,7 +209,7 @@ class CoinAddressDerivationTests: XCTestCase { } } - private func AssetCoinDerivation(_ coin: CoinType, _ expected: String, _ derivedAddress: String, _ address: AnyAddress?) { + private func assertCoinDerivation(_ coin: CoinType, _ expected: String, _ derivedAddress: String, _ address: AnyAddress?) { XCTAssertNotNil(address, "\(coin) is not implemented CoinType.address(string: String)") XCTAssertEqual(expected, derivedAddress, "\(coin) failed to match address") XCTAssertEqual(expected, address?.description, "\(coin) is not implemented CoinType.address(string: String)") diff --git a/swift/Tests/SlipTests.swift b/swift/Tests/CoinTypeTests.swift similarity index 97% rename from swift/Tests/SlipTests.swift rename to swift/Tests/CoinTypeTests.swift index da38e2248d3..9ca1d5c39b4 100644 --- a/swift/Tests/SlipTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -7,7 +7,7 @@ import XCTest import TrustWalletCore -class SlipTests: XCTestCase { +class CoinTypeTests: XCTestCase { func testCoinType() { XCTAssertEqual(CoinType.bitcoin.rawValue, 0) diff --git a/swift/Tests/DataTests.swift b/swift/Tests/DataTests.swift index 09566f45a4a..c3c1a198c3e 100644 --- a/swift/Tests/DataTests.swift +++ b/swift/Tests/DataTests.swift @@ -15,4 +15,10 @@ class DataTests: XCTestCase { XCTAssertEqual(Array(data), bytes) } + + func testOddLength() { + XCTAssertNil(Data(hexString: "0x0")) + XCTAssertNil(Data(hexString: "0x28fa6ae00")) + XCTAssertNil(Data(hexString: "28fa6ae00")) + } } diff --git a/swift/Tests/DerivationPathTests.swift b/swift/Tests/DerivationPathTests.swift index 417b617b9af..693a4174a49 100644 --- a/swift/Tests/DerivationPathTests.swift +++ b/swift/Tests/DerivationPathTests.swift @@ -9,7 +9,7 @@ import XCTest class DerivationPathTests: XCTestCase { func testInitWithIndices() { - let path = DerivationPath(purpose: .bip44, coinType: .ethereum, account: 0, change: 0, address: 0) + 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)) @@ -28,7 +28,7 @@ class DerivationPathTests: XCTestCase { XCTAssertEqual(path?.indices[4], DerivationPath.Index(0, hardened: false)) XCTAssertEqual(path?.purpose, Purpose(rawValue: 44)!) - XCTAssertEqual(path?.coinType, CoinType(rawValue: 60)!) + XCTAssertEqual(path?.coinType, 60) XCTAssertEqual(path?.account, 0) XCTAssertEqual(path?.change, 0) XCTAssertEqual(path?.address, 0) diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 46248d088d2..c0f9df78586 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -290,8 +290,16 @@ class HDWalletTests: XCTestCase { let xpub = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj" let bitcoin = CoinType.bitcoinCash - let xpub2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: bitcoin, account: 0, change: 0, address: 2))! - let xpub9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: bitcoin, account: 0, change: 0, address: 9))! + let xpub2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip44, coin: bitcoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpub9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip44, coin: bitcoin.slip44Id, account: 0, change: 0, address: 9).description + )! let xpubAddr2 = BitcoinAddress(publicKey: xpub2, prefix: CoinType.bitcoin.p2pkhPrefix)! let xpubAddr9 = BitcoinAddress(publicKey: xpub9, prefix: CoinType.bitcoin.p2pkhPrefix)! @@ -304,8 +312,16 @@ class HDWalletTests: XCTestCase { let ypub = "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP" let bitcoin = CoinType.bitcoin - let ypub3 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: bitcoin, account: 0, change: 0, address: 3))! - let ypub10 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: bitcoin, account: 0, change: 0, address: 10))! + let ypub3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip49, coin: bitcoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypub10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip49, coin: bitcoin.slip44Id, account: 0, change: 0, address: 10).description + )! let ypubAddr3 = BitcoinAddress.compatibleAddress(publicKey: ypub3, prefix: CoinType.bitcoin.p2shPrefix) let ypubAddr10 = BitcoinAddress.compatibleAddress(publicKey: ypub10, prefix: CoinType.bitcoin.p2shPrefix) @@ -316,8 +332,16 @@ class HDWalletTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs" let bitcoin = CoinType.bitcoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: bitcoin.purpose, coinType: bitcoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: bitcoin.purpose, coinType: bitcoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip84, coin: bitcoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip84, coin: bitcoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(bitcoin.deriveAddressFromPublicKey(publicKey: zpubAddr4).description, "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n") XCTAssertEqual(bitcoin.deriveAddressFromPublicKey(publicKey: zpubAddr11).description, "bc1qxr4fjkvnxjqphuyaw5a08za9g6qqh65t8qwgum") @@ -327,8 +351,8 @@ class HDWalletTests: XCTestCase { let zpub = "zpub6qeA5j9oSq8tZaYEBTp1X61ZSjeen6HbiUBSG4KLPD8d65Pi7eSMPNuxCqgbLdtnim2hgnJEzmE6jhFoJXtJdRxRKRdNFQBJ6iidx9BHGyk" let bitcoin = CoinType.bitcoin - let path = DerivationPath(purpose: bitcoin.purpose, coinType: bitcoin, account: 0, change: 0, address: 0) - let pubkey = HDWallet.derive(from: zpub, at: path)! + let path = DerivationPath(purpose: bitcoin.purpose, coin: bitcoin.slip44Id, account: 0, change: 0, address: 0) + let pubkey = HDWallet.getPublicKeyFromExtended(extended: zpub, coin: bitcoin, derivationPath: path.description)! let address = bitcoin.deriveAddressFromPublicKey(publicKey: pubkey) XCTAssertEqual(pubkey.data.hexString, "039fdd3652495d01b6a363f8db8b3adce09f83ea5c43ff872ad0a39192340256b0") @@ -388,4 +412,12 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "kava1zrst72upua78pylhku9csxd5zmhsyrk7xhrdlf") } + + func testDeriveBandChain() { + let coin = CoinType.bandChain + let key = HDWallet.test.getKeyForCoin(coin: coin) + let address = coin.deriveAddress(privateKey: key) + + XCTAssertEqual(address, "band1pe8xm2r46rmctsukuqu7gl900vzprfsp4sguc3") + } } diff --git a/swift/Tests/Keystore/AccountTests.swift b/swift/Tests/Keystore/AccountTests.swift index 7d1d184173f..8fa934e9e11 100755 --- a/swift/Tests/Keystore/AccountTests.swift +++ b/swift/Tests/Keystore/AccountTests.swift @@ -9,7 +9,7 @@ import XCTest class AccountTests: XCTestCase { let words = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal" - let password = "password" + let password = Data("password".utf8) func testSignHash() throws { let privateKeyData = Data(hexString: "D30519BCAE8D180DBFCC94FE0B8383DC310185B0BE97B4365083EBCECCD75759")! @@ -42,9 +42,10 @@ class AccountTests: XCTestCase { func testExtendedPubkey() throws { let key = StoredKey.importHDWallet(mnemonic: words, name: "name", password: password, coin: .ethereum)! let wallet = Wallet(keyURL: URL(fileURLWithPath: "/"), key: key) - _ = try wallet.getAccount(password: password, coin: .bitcoin) - _ = try wallet.getAccount(password: password, coin: .bitcoinCash) - _ = try wallet.getAccount(password: password, coin: .ethereumClassic) + let stringPass = String(data: password, encoding: .utf8)! + _ = try wallet.getAccount(password: stringPass, coin: .bitcoin) + _ = try wallet.getAccount(password: stringPass, coin: .bitcoinCash) + _ = try wallet.getAccount(password: stringPass, coin: .ethereumClassic) XCTAssertEqual(wallet.accounts[0].extendedPublicKey, "") XCTAssertEqual(wallet.accounts[1].extendedPublicKey, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9") diff --git a/swift/Tests/Keystore/Data/bnb_wallet.json b/swift/Tests/Keystore/Data/bnb_wallet.json new file mode 100644 index 00000000000..b38f95886be --- /dev/null +++ b/swift/Tests/Keystore/Data/bnb_wallet.json @@ -0,0 +1,33 @@ +{ + "activeAccounts": [{ + "address": "bc1q4zehq85jqx9zzgzvzn9t64yjy66nunn3vehuv6", + "derivationPath": "m/84'/0'/0'/0/0", + "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q" + }, { + "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", + "derivationPath": "m/44'/60'/0'/0/0" + }, { + "address": "bnb1njuczq3hgvupu2vnczrjz7rc8x4uxlmhjyq95z", + "derivationPath": "m/44'/714'/0'/0/0" + }], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "cfeacebdf0d0c57cbbe6260094cdf3a9" + }, + "ciphertext": "60358be4204c0d9c723775159bcadd63a51f0c06fce4024294d8ed4c19acb85cba3ca769dc3521fb572a06f8986d8bbc5736d6900e3e215f9bc112acffa470b178a621922041300bd7", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "14198d7e5f2afbfde2b00539d0c9abaec99e708dd4a2242448c57248e3e07c77" + }, + "mac": "90b65f299a9ac59f50d24c6f80f4cdcffe6500c86687df716a15d79461992085" + }, + "id": "3c937d42-443d-4acf-9311-2d9dfa857e1c", + "name": "", + "type": "mnemonic", + "version": 3 +} diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 928f5194345..8f65151f9ab 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -21,6 +21,10 @@ extension KeyStore { var bitcoinWallet: Wallet { return wallets.first(where: { $0.identifier == "btc_missing_address.json"})! } + + var bnbWallet: Wallet { + return wallets.first(where: { $0.identifier == "bnb_wallet.json"})! + } } class KeyStoreTests: XCTestCase { @@ -62,22 +66,30 @@ class KeyStoreTests: XCTestCase { try? fileManager.removeItem(at: watchesDestination) try? fileManager.copyItem(at: watchesURL, to: watchesDestination) + + let bnbWalletURL = Bundle(for: type(of: self)).url(forResource: "bnb_wallet", withExtension: "json")! + let bnbWalletDestination = keyDirectory.appendingPathComponent("bnb_wallet.json") + + try? fileManager.removeItem(at: bnbWalletDestination) + try? fileManager.copyItem(at: bnbWalletURL, to: bnbWalletDestination) } func testLoadKeyStore() { let keyStore = try! KeyStore(keyDirectory: keyDirectory) - XCTAssertEqual(keyStore.wallets.count, 3) + XCTAssertEqual(keyStore.wallets.count, 4) XCTAssertEqual(keyStore.watches.count, 1) } func testCreateHDWallet() throws { - let coins = [CoinType.ethereum] + let coins = [CoinType.ethereum, .binance, .smartChain] let keyStore = try KeyStore(keyDirectory: keyDirectory) let newWallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) - XCTAssertEqual(newWallet.accounts.count, 1) - XCTAssertEqual(keyStore.wallets.count, 4) + XCTAssertEqual(newWallet.accounts.count, 3) + XCTAssertEqual(keyStore.wallets.count, 5) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .ethereum)) + XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binance)) + XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .smartChain)) } func testUpdateKey() throws { @@ -90,7 +102,7 @@ class KeyStoreTests: XCTestCase { let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) let savedWallet = savedKeyStore.wallets.first(where: { $0 == wallet })! - let data = savedWallet.key.decryptPrivateKey(password: "testpassword") + let data = savedWallet.key.decryptPrivateKey(password: Data("testpassword".utf8)) let mnemonic = String(data: data!, encoding: .ascii) XCTAssertEqual(savedWallet.accounts.count, coins.count) @@ -110,7 +122,7 @@ class KeyStoreTests: XCTestCase { let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) let savedWallet = savedKeyStore.wallets.first(where: { $0 == wallet })! - let data = savedWallet.key.decryptPrivateKey(password: "password") + let data = savedWallet.key.decryptPrivateKey(password: Data("password".utf8)) let mnemonic = String(data: data!, encoding: .ascii) XCTAssertEqual(savedWallet.accounts.count, coins.count) @@ -160,11 +172,11 @@ class KeyStoreTests: XCTestCase { func testImportKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let privateKeyData = Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")! - let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: "password", coin: .ethereum)! + let key = StoredKey.importPrivateKey(privateKey: privateKeyData, 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 storedData = wallet.key.decryptPrivateKey(password: "newPassword") + let storedData = wallet.key.decryptPrivateKey(password: Data("newPassword".utf8)) XCTAssertNotNil(keyStore.keyWallet) XCTAssertNotNil(storedData) @@ -176,7 +188,7 @@ class KeyStoreTests: XCTestCase { let privateKey = PrivateKey(data: Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")!)! let wallet = try keyStore.import(privateKey: privateKey, name: "name", password: "password", coin: .ethereum) - let storedData = wallet.key.decryptPrivateKey(password: "password") + let storedData = wallet.key.decryptPrivateKey(password: Data("password".utf8)) XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) @@ -189,13 +201,82 @@ class KeyStoreTests: XCTestCase { func testImportWallet() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum]) - let storedData = wallet.key.decryptMnemonic(password: "newPassword") + 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" + }, { + "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", + "coin": 60, + "derivationPath": "m/44'/60'/0'/0/0" + }, { + "address": "bnb1njuczq3hgvupu2vnczrjz7rc8x4uxlmhjyq95z", + "coin": 714, + "derivationPath": "m/44'/714'/0'/0/0" + }, { + "address": "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC", + "coin": 10000714, + "derivationPath": "m/44'/714'/0'/0/0" + }, { + "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", + "coin": 20000714, + "derivationPath": "m/44'/60'/0'/0/0" + }], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "cfeacebdf0d0c57cbbe6260094cdf3a9" + }, + "ciphertext": "60358be4204c0d9c723775159bcadd63a51f0c06fce4024294d8ed4c19acb85cba3ca769dc3521fb572a06f8986d8bbc5736d6900e3e215f9bc112acffa470b178a621922041300bd7", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "14198d7e5f2afbfde2b00539d0c9abaec99e708dd4a2242448c57248e3e07c77" + }, + "mac": "90b65f299a9ac59f50d24c6f80f4cdcffe6500c86687df716a15d79461992085" + }, + "id": "3c937d42-443d-4acf-9311-2d9dfa857e1c", + "name": "", + "type": "mnemonic", + "version": 3 + } + """ + + let password = "e28ddf66cec05c1fc09939a00628b230459202b2493fccac288038ef37815723" + let keyStore = try KeyStore(keyDirectory: keyDirectory) + _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.smartChainLegacy, .smartChain], password: password) + + XCTAssertEqual(keyStore.bnbWallet.accounts.count, 5) + + let accountLegacy = keyStore.bnbWallet.accounts[3] + + XCTAssertEqual(accountLegacy.coin, CoinType.smartChainLegacy) + XCTAssertEqual(accountLegacy.address, "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC") + + let account = keyStore.bnbWallet.accounts[4] + + XCTAssertEqual(account.coin, CoinType.smartChain) + XCTAssertEqual(account.address, "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea") + + let saved = try String(contentsOf: keyStore.bnbWallet.keyURL) + XCTAssertJSONEqual(saved, expected) + } + func testExportMnemonic() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum]) diff --git a/swift/Tests/Keystore/KeystoreKeyTests.swift b/swift/Tests/Keystore/KeystoreKeyTests.swift index 580a1f8dae1..cad779e3105 100755 --- a/swift/Tests/Keystore/KeystoreKeyTests.swift +++ b/swift/Tests/Keystore/KeystoreKeyTests.swift @@ -25,21 +25,21 @@ class KeystoreKeyTests: XCTestCase { let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! let key = StoredKey.load(path: url.path)! - XCTAssertNil(key.decryptPrivateKey(password: "password")) + XCTAssertNil(key.decryptPrivateKey(password: Data("password".utf8))) } func testDecrypt() { let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! let key = StoredKey.load(path: url.path)! - let privateKey = key.decryptPrivateKey(password: "testpassword")! + let privateKey = key.decryptPrivateKey(password: Data("testpassword".utf8))! XCTAssertEqual(privateKey.hexString, "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d") } func testCreateWallet() { let privateKeyData = Data(hexString: "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266")! - let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: "password", coin: .ethereum)! - let decrypted = key.decryptPrivateKey(password: "password")! + let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: Data("password".utf8), coin: .ethereum)! + let decrypted = key.decryptPrivateKey(password: Data("password".utf8))! XCTAssertEqual(decrypted.hexString, privateKeyData.hexString) } @@ -59,4 +59,64 @@ class KeystoreKeyTests: XCTestCase { XCTAssertEqual(account.address.description, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y") } + + func testLongHexPassword() { + let json = """ + { + "address": "34bae2218c254ed190c0f5b1dd4323aee8e7da09", + "id": "86066d8c-8dba-4d81-afd4-934e2a2b72a2", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "a4976ad73057007ad788d1f792db851d" + }, + "ciphertext": "5e4458d69964172c492616b751d6589b4ad7da4217dcfccecc3f4e515a934bb8", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "24c72d92bf88a4f7c7b3f5e3cb3620714d71fceabbb0bc6099f50c6d5d898e7c" + }, + "mac": "c15e3035ddcaca766dfc56648978d33e94d3c57d4a5e13fcf8b5f8dbb0902900" + } + } + """.data(using: .utf8)! + let keystore = StoredKey.importJSON(json: json)! + let password = Data(hexString: "2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd")! + let data = keystore.decryptPrivateKey(password: password) + XCTAssertEqual(data?.hexString, "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b") + } + + func testLongPassword() { + let json = """ + { + "address": "b92bcd5b14d2d70651d483c8f03bae79223b88ec", + "id": "480fc670-e5e6-4b39-ba33-61f54ce792f9", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "5d88369e4306bcc98e1fe70e7710f3a0" + }, + "ciphertext": "de9bac5d1ca94cdd067a51275a56f0766ed3631de108609ee240c42a0994f97e", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "3849479dd4880793cfd92ebfeb30c60dccf04f3f76a2778fe89bead4237ddad4" + }, + "mac": "361df97ff456009849ab1ffd4e71b61d7e66c9d2071c5a7563d1bbbdb0b2653b" + } + } + """.data(using: .utf8)! + let keystore = StoredKey.importJSON(json: json)! + let password = "2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd".data(using: .utf8)! + let data = keystore.decryptPrivateKey(password: password) + XCTAssertEqual(data?.hexString, "4357b2f9a6150ba969bc52f01c98cce5313595fe49f2d08303759c73e5c7a46c") + } } diff --git a/swift/Tests/Keystore/WalletTests.swift b/swift/Tests/Keystore/WalletTests.swift index 983797344cc..6b31424870e 100755 --- a/swift/Tests/Keystore/WalletTests.swift +++ b/swift/Tests/Keystore/WalletTests.swift @@ -21,7 +21,7 @@ class WalletTests: XCTestCase { func testIdentifier() throws { let url = URL(string: "UTC--2018-07-23T15-42-07.380692005-42000--6E199F01-FA96-4ADF-9A4B-36EE4B1E08C7")! - let key = StoredKey(name: "name", password: "password") + let key = StoredKey(name: "name", password: Data("password".utf8)) let wallet = Wallet(keyURL: url, key: key) XCTAssertEqual(wallet.identifier, "UTC--2018-07-23T15-42-07.380692005-42000--6E199F01-FA96-4ADF-9A4B-36EE4B1E08C7") } diff --git a/swift/cpp.xcconfig.in b/swift/cpp.xcconfig.in index 9d351112b5b..9e2fd5ef152 100644 --- a/swift/cpp.xcconfig.in +++ b/swift/cpp.xcconfig.in @@ -5,4 +5,4 @@ // file LICENSE at the root of the source code distribution tree. HEADER_SEARCH_PATHS = $(SRCROOT)/../src @Boost_INCLUDE_DIRS@ -SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/protobuf/staging/protobuf-3.9.0/src +SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.9.0/src diff --git a/swift/project.yml b/swift/project.yml index 0d0db778479..3a47df9d91e 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -6,7 +6,7 @@ settings: base: HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/src SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include - CLANG_CXX_LANGUAGE_STANDARD: gnu++17 + CLANG_CXX_LANGUAGE_STANDARD: c++17 SWIFT_VERSION: 5.1 configs: release: @@ -47,6 +47,8 @@ targets: BUILD_LIBRARY_FOR_DISTRIBUTION: true INFOPLIST_FILE: 'Info.plist' CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: $(inherited) true + OTHER_CFLAGS: $(inherited) -Wno-comma postCompileScripts: - script: ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml name: SwiftLint @@ -205,4 +207,4 @@ targets: - protobuf/google/protobuf/wrappers.pb.cc settings: GCC_WARN_64_TO_32_BIT_CONVERSION: NO - OTHER_CFLAGS: -DHAVE_PTHREAD=1 + OTHER_CFLAGS: $(inherited) -DHAVE_PTHREAD=1 -Wno-comma -Wno-unreachable-code diff --git a/swift/protobuf b/swift/protobuf index 42dacc8e080..b0c567bc5a1 120000 --- a/swift/protobuf +++ b/swift/protobuf @@ -1 +1 @@ -../build/protobuf/staging/protobuf-3.9.0/src \ No newline at end of file +../build/local/src/protobuf/protobuf-3.9.0/src \ No newline at end of file diff --git a/tests/Aeternity/TWAeternityAddressTests.cpp b/tests/Aeternity/TWAeternityAddressTests.cpp index e5047e44ed5..f936454d907 100644 --- a/tests/Aeternity/TWAeternityAddressTests.cpp +++ b/tests/Aeternity/TWAeternityAddressTests.cpp @@ -12,18 +12,17 @@ #include TEST(TWAeternityAddress, HDWallet) { - auto mnemonic = "shoot island position soft burden budget tooth cruel issue economy destroy above"; + 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 wallet = WRAP( + TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeDerivationPath(TWCoinTypeAeternity)); + auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeAeternity, TWCoinTypeDerivationPath(TWCoinTypeAeternity)); auto publicKey = TWPrivateKeyGetPublicKeyEd25519(privateKey); - auto address = TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeAeternity); - auto addressStr = WRAPS(TWAnyAddressDescription(address)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeAeternity)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); assertStringsEqual(addressStr, "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN"); } diff --git a/tests/Aion/SignerTests.cpp b/tests/Aion/SignerTests.cpp index 04f38e8e39b..b8a7970f248 100644 --- a/tests/Aion/SignerTests.cpp +++ b/tests/Aion/SignerTests.cpp @@ -15,7 +15,7 @@ using namespace TW::Aion; TEST(AionSigner, Sign) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, {}); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); Signer::sign(privateKey, transaction); @@ -28,7 +28,7 @@ TEST(AionSigner, Sign) { TEST(AionSigner, SignWithData) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, parse_hex("41494f4e0000")); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, parse_hex("41494f4e0000")); auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); Signer::sign(privateKey, transaction); diff --git a/tests/Aion/TWAnySignerTests.cpp b/tests/Aion/TWAnySignerTests.cpp index 9ba8cc58fac..1f30f761d03 100644 --- a/tests/Aion/TWAnySignerTests.cpp +++ b/tests/Aion/TWAnySignerTests.cpp @@ -25,9 +25,10 @@ TEST(TWAnySignerAion, Sign) { 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());; + 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; diff --git a/tests/Aion/TransactionTests.cpp b/tests/Aion/TransactionTests.cpp index f35815c06d8..ad6b8546c2b 100644 --- a/tests/Aion/TransactionTests.cpp +++ b/tests/Aion/TransactionTests.cpp @@ -15,13 +15,13 @@ using namespace TW::Aion; TEST(AionTransaction, Encode) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, {}); + 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, {}); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); transaction.signature = parse_hex("a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); ASSERT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); } diff --git a/tests/BandChain/TWCoinTypeTests.cpp b/tests/BandChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..695573b6f5c --- /dev/null +++ b/tests/BandChain/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 = TWStringCreateWithUTF8Bytes("473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBandChain, txId)); + auto accId = TWStringCreateWithUTF8Bytes("band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBandChain, accId)); + 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/Bech32AddressTests.cpp b/tests/Bech32AddressTests.cpp index ff425cb7c4f..4b6024ecd5e 100644 --- a/tests/Bech32AddressTests.cpp +++ b/tests/Bech32AddressTests.cpp @@ -27,6 +27,10 @@ TEST(Bech32Address, Valid) { ASSERT_TRUE(Bech32Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", "io")); ASSERT_TRUE(Bech32Address::isValid("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7", "zil")); + + ASSERT_TRUE(Bech32Address::isValid("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", "erd")); + ASSERT_TRUE(Bech32Address::isValid("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", "erd")); + ASSERT_TRUE(Bech32Address::isValid("erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", "erd")); } TEST(Bech32Address, Invalid) { @@ -48,6 +52,10 @@ TEST(Bech32Address, Invalid) { ASSERT_FALSE(Bech32Address::isValid("")); ASSERT_FALSE(Bech32Address::isValid("0x")); ASSERT_FALSE(Bech32Address::isValid("91cddcebe846ce4d47712287eee53cf17c2cfb7")); + + ASSERT_FALSE(Bech32Address::isValid("", "erd")); + ASSERT_FALSE(Bech32Address::isValid("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35!", "erd")); + ASSERT_FALSE(Bech32Address::isValid("xerd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", "erd")); } TEST(Bech32Address, InvalidWrongPrefix) { diff --git a/tests/Binance/SignerTests.cpp b/tests/Binance/SignerTests.cpp index 1094f714d5e..35c0d8d8979 100644 --- a/tests/Binance/SignerTests.cpp +++ b/tests/Binance/SignerTests.cpp @@ -4,13 +4,14 @@ // 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 "Binance/Address.h" -#include "Binance/Signer.h" -#include "proto/Binance.pb.h" #include @@ -43,7 +44,9 @@ TEST(BinanceSigner, Sign) { auto signer = Binance::Signer(std::move(input)); auto signature = signer.sign(); - ASSERT_EQ(hex(signature.begin(), signature.end()), "9123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d6997f5f939ef834ea61d596a314237c48e560da9e17b5a"); + ASSERT_EQ(hex(signature.begin(), signature.end()), + "9123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d69" + "97f5f939ef834ea61d596a314237c48e560da9e17b5a"); } TEST(BinanceSigner, Build) { @@ -125,7 +128,9 @@ TEST(BinanceSigner, BuildSend) { auto signer = Binance::Signer(std::move(signingInput)); auto signature = signer.sign(); - ASSERT_EQ(hex(signature.begin(), signature.end()), "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aadc4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f"); + ASSERT_EQ(hex(signature.begin(), signature.end()), + "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aad" + "c4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f"); auto result = signer.build(); @@ -151,11 +156,11 @@ 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(derivationPath); + 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(derivationPath); + const auto toPrivateKey = toWallet.getKey(TWCoinTypeBinance, derivationPath); const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto signingInput = Proto::SigningInput(); @@ -164,21 +169,21 @@ TEST(BinanceSigner, BuildSend2) { signingInput.set_sequence(1); signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - auto token = Proto::SendOrder::Token(); + auto token = Proto::SendOrder::Token(); token.set_denom("BNB"); token.set_amount(100000000000000); - auto input = Proto::SendOrder::Input(); + 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 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(); + auto sendOrder = Proto::SendOrder(); *sendOrder.add_inputs() = input; *sendOrder.add_outputs() = output; @@ -202,11 +207,13 @@ TEST(BinanceSigner, BuildSend2) { } TEST(BinanceSigner, BuildHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + 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 toPrivateKey = + PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto toAddr = Binance::Address(toPublicKey).getKeyHash(); @@ -221,9 +228,9 @@ TEST(BinanceSigner, BuildHTLT) { token.set_amount(100000000); auto randomNumberHash = - parse_hex("e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"); + parse_hex("e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"); - auto &htltOrder = *signingInput.mutable_htlt_order(); + 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(""); @@ -246,7 +253,8 @@ TEST(BinanceSigner, BuildHTLT) { } TEST(BinanceSigner, BuildDepositHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); + const auto fromPrivateKey = + PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -262,7 +270,7 @@ TEST(BinanceSigner, BuildDepositHTLT) { auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - auto &depositHTLTOrder = *signingInput.mutable_deposithtlt_order(); + 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; @@ -277,7 +285,8 @@ TEST(BinanceSigner, BuildDepositHTLT) { } TEST(BinanceSigner, BuildClaimHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -288,26 +297,27 @@ TEST(BinanceSigner, BuildClaimHTLT) { signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); auto randomNumber = - parse_hex("bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"); + parse_hex("bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"); auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - auto &claimHTLTOrder = *signingInput.mutable_claimhtlt_order(); + 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"); + hex(data.begin(), data.end()), + "d401f0625dee0a5ec16653000a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741844d35" + "eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e51a20bda6933c7757d0ca428aa01fb9d0935a231f87bf" + "2deeb9b409cea3f2d580a2cc126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561a" + "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7" + "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001"); } TEST(BinanceSigner, BuildRefundHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -319,7 +329,7 @@ TEST(BinanceSigner, BuildRefundHTLT) { auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - auto &refundHTLTOrder = *signingInput.mutable_refundhtlt_order(); + auto& refundHTLTOrder = *signingInput.mutable_refundhtlt_order(); refundHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); refundHTLTOrder.set_swap_id(swapID.data(), swapID.size()); @@ -333,7 +343,8 @@ TEST(BinanceSigner, BuildRefundHTLT) { } TEST(BinanceSigner, BuildIssueOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -352,29 +363,20 @@ TEST(BinanceSigner, BuildIssueOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "b601f0625dee0a40" - "17efab80" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f" - "4e657742696e616e6365546f6b656e" - "1a0b" - "4e4e422d3333385f424e42" - "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001" - ); - - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("b601f0625dee0a4017efab800a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f4e657742696e616e6365546f6b656e1a0b4e4e422d3333385f424e42208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "b601f0625dee0a40" + "17efab80" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f" + "4e657742696e616e6365546f6b656e" + "1a0b" + "4e4e422d3333385f424e42" + "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac" + "993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c9" + "5aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); } TEST(BinanceSigner, BuildMintOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -391,27 +393,18 @@ TEST(BinanceSigner, BuildMintOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "467e0829" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001" - ); - - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2b467e08290a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "a101f0625dee0a2b" + "467e0829" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" + "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); } TEST(BinanceSigner, BuildBurnOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -428,47 +421,18 @@ TEST(BinanceSigner, BuildBurnOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "7ed2d2a0" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001" - ); - - /* - Matching binance chain sdk code: - - // encode - priv := "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d" - keyManager, err := NewPrivateKeyManager(priv) - assert.NoError(t, err) - fromAddr, err := ctypes.AccAddressFromBech32("bnb1prrujx8kkukrcrppklggadhuvegfnx8phwey70") - assert.NoError(t, err) - - burnMsg := msg.NewTokenBurnMsg(fromAddr, "NNB-338_BNB", 1000000) - signMsg := tx.StdSignMsg{ - ChainID: "test-chain", - AccountNumber: 15, - Sequence: 1, - Memo: "", - Msgs: []msg.Msg{burnMsg}, - Source: 0, - } - rawSignResult, err := keyManager.Sign(signMsg) - fmt.Printf("%x\n", rawSignResult) - - // decode back - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2b7ed2d2a00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "a101f0625dee0a2b" + "7ed2d2a0" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" + "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); } TEST(BinanceSigner, BuildFreezeOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -485,27 +449,18 @@ TEST(BinanceSigner, BuildFreezeOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "e774b32d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" - ); - - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2be774b32d0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "a101f0625dee0a2b" + "e774b32d" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" + "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); } TEST(BinanceSigner, BuildUnfreezeOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -522,23 +477,147 @@ TEST(BinanceSigner, BuildUnfreezeOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "6515ff0d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" + "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" ); +} - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2b6515ff0d0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ +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" + ); } } // namespace TW::Binance diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp new file mode 100644 index 00000000000..97e19fc6685 --- /dev/null +++ b/tests/BinanceSmartChain/SignerTests.cpp @@ -0,0 +1,91 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 + /* payload: */ {} + ); + + // 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_payload(payloadFunction.data(), payloadFunction.size()); + input.set_amount(dummyAmount.data(), dummyAmount.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.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 new file mode 100644 index 00000000000..4940e520fab --- /dev/null +++ b/tests/BinanceSmartChain/TWAnyAddressTests.cpp @@ -0,0 +1,29 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 = TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false); + auto string = "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"; + + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, 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 new file mode 100644 index 00000000000..8ce05e436d6 --- /dev/null +++ b/tests/BinanceSmartChain/TWCoinTypeTests.cpp @@ -0,0 +1,44 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 = TWStringCreateWithUTF8Bytes("0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartChain, txId)); + auto accId = TWStringCreateWithUTF8Bytes("0x35552c16704d214347f29Fa77f77DA6d75d7C752"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartChain, accId)); + 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/Bitcoin/BitcoinScriptTests.cpp b/tests/Bitcoin/BitcoinScriptTests.cpp new file mode 100644 index 00000000000..4942cc948ff --- /dev/null +++ b/tests/Bitcoin/BitcoinScriptTests.cpp @@ -0,0 +1,332 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..a5f65a9048b --- /dev/null +++ b/tests/Bitcoin/FeeCalculatorTests.cpp @@ -0,0 +1,63 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 192); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 78); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 158); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2260); +} + +TEST(BitcoinDefaultFeeCalculator, calculate) { + 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(BitcoinDefaultFeeCalculator, calculateSingleInput) { + DefaultFeeCalculator defaultFeeCalculator; + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 148); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(2), 296); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(10), 1480); +} + +TEST(ZCashFeeCalculator, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeZcash); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 10000); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10000); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 10000); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + +TEST(GroestlcoinFeeCalculator, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeGroestlcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 20000); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 20000); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 20000); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + +TEST(DecredFeeCalculator, calculate) { + 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/TWBitcoinAddressTests.cpp b/tests/Bitcoin/TWBitcoinAddressTests.cpp new file mode 100644 index 00000000000..79cacb6445a --- /dev/null +++ b/tests/Bitcoin/TWBitcoinAddressTests.cpp @@ -0,0 +1,82 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 + +const auto addr1Valid = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; +const auto addr1Data = "00769bdff96a02f9135a1d19b749db6a78fe07dc90"; + +TEST(TWBitcoinAddress, Create) { + { + TWBitcoinAddress* addr = TWBitcoinAddressCreateWithString(STRING(addr1Valid).get()); + EXPECT_TRUE(addr != nullptr); + TWBitcoinAddressDelete(addr); + } + { + TWBitcoinAddress* addr = TWBitcoinAddressCreateWithData(DATA(addr1Data).get()); + EXPECT_TRUE(addr != nullptr); + TWBitcoinAddressDelete(addr); + } +} + +TEST(TWBitcoinAddress, IsValid) { + EXPECT_TRUE(TWBitcoinAddressIsValidString(STRING(addr1Valid).get())); + EXPECT_TRUE(TWBitcoinAddressIsValid(DATA(addr1Data).get())); +} + +TEST(TWBitcoinAddress, CreateWithPublicKey) { + const auto pubKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0239de350077b204f8fa1b63542b33580b8f125c4b9b827d5fc65cbe47fc1d9a52").get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(pubKey != nullptr); + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey.get(), TWCoinTypeP2shPrefix(TWCoinTypeBitcoin))); + EXPECT_TRUE(addr.get() != nullptr); +} + +TEST(TWBitcoinAddress, Description) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING(addr1Valid).get())); + EXPECT_TRUE(addr.get() != nullptr); + EXPECT_EQ(std::string(TWStringUTF8Bytes(TWBitcoinAddressDescription(addr.get()))), addr1Valid); +} + +TEST(TWBitcoinAddress, PrefixAndHash) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING(addr1Valid).get())); + EXPECT_TRUE(addr.get() != nullptr); + EXPECT_EQ(TWBitcoinAddressPrefix(addr.get()), 0x00); + const auto keyhash = WRAPD(TWBitcoinAddressKeyhash(addr.get())); + EXPECT_EQ(TW::hex(TW::data(TWDataBytes(keyhash.get()), TWDataSize(keyhash.get()))), addr1Data + 2); +} + +TEST(TWBitcoinAddress, Equal) { + const auto addr1 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING(addr1Valid).get())); + EXPECT_TRUE(addr1.get() != nullptr); + const auto addr2 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(DATA(addr1Data).get())); + EXPECT_TRUE(addr2.get() != nullptr); + EXPECT_TRUE(TWBitcoinAddressEqual(addr1.get(), addr2.get())); +} + +TEST(TWBitcoinAddress, CreateWithStringInvalid) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING("__INVALID__").get())); + EXPECT_TRUE(addr.get() == nullptr); +} + +TEST(TWBitcoinAddress, CreateWithDataInvalid) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(DATA("deadbeef").get())); + EXPECT_TRUE(addr.get() == nullptr); +} + +TEST(TWBitcoinAddress, CreateWithPublicKeyInvalid) { + const auto pubKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3").get(), + TWPublicKeyTypeED25519)); + EXPECT_TRUE(pubKey != nullptr); + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey.get(), TWCoinTypeP2shPrefix(TWCoinTypeBitcoin))); + EXPECT_TRUE(addr.get() == nullptr); +} diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/Bitcoin/TWBitcoinScriptTests.cpp index 6e5c716fcc5..6c6f44f4a45 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/Bitcoin/TWBitcoinScriptTests.cpp @@ -11,7 +11,124 @@ #include -TEST(BitcoinScript, ScriptHash) { +const auto PayToScriptHash = TWBitcoinScriptCreateWithData(DATA("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87").get()); +const auto PayToWitnessScriptHash = TWBitcoinScriptCreateWithData(DATA("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db").get()); +const auto PayToWitnessPublicKeyHash = TWBitcoinScriptCreateWithData(DATA("0014" "79091972186c449eb1ded22b78e40d009bdf0089").get()); +const auto PayToPublicKeySecp256k1 = TWBitcoinScriptCreateWithData(DATA("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac").get()); +const auto PayToPublicKeyHash = TWBitcoinScriptCreateWithData(DATA("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac").get()); + +TEST(TWBitcoinScript, Create) { + auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); + { + auto script = TWBitcoinScriptCreateWithData(data.get()); + ASSERT_TRUE(script != nullptr); + ASSERT_EQ(TWBitcoinScriptSize(script), 23); + } + { + auto script = TWBitcoinScriptCreateWithBytes(TWDataBytes(data.get()), TWDataSize(data.get())); + ASSERT_TRUE(script != nullptr); + ASSERT_EQ(TWBitcoinScriptSize(script), 23); + + auto scriptCopy = TWBitcoinScriptCreateCopy(script); + ASSERT_TRUE(scriptCopy != nullptr); + ASSERT_EQ(TWBitcoinScriptSize(scriptCopy), 23); + } +} + +TEST(TWBitcoinScript, Equals) { + auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); + auto script = TWBitcoinScriptCreateWithBytes(TWDataBytes(data.get()), TWDataSize(data.get())); + auto scriptCopy = TWBitcoinScriptCreateCopy(script); + ASSERT_TRUE(TWBitcoinScriptEqual(script, scriptCopy)); +} + +TEST(TWBitcoinScript, IsPayToScriptHash) { + ASSERT_TRUE(TWBitcoinScriptIsPayToScriptHash(PayToScriptHash)); +} + +TEST(TWBitcoinScript, IsPayToWitnessScriptHash) { + ASSERT_TRUE(TWBitcoinScriptIsPayToWitnessScriptHash(PayToWitnessScriptHash)); +} + +TEST(TWBitcoinScript, IsPayToWitnessPublicKeyHash) { + ASSERT_TRUE(TWBitcoinScriptIsPayToWitnessPublicKeyHash(PayToWitnessPublicKeyHash)); +} + +TEST(TWBitcoinScript, IsWitnessProgram) { + ASSERT_TRUE(TWBitcoinScriptIsWitnessProgram(PayToWitnessScriptHash)); + ASSERT_TRUE(TWBitcoinScriptIsWitnessProgram(PayToWitnessPublicKeyHash)); + ASSERT_FALSE(TWBitcoinScriptIsWitnessProgram(PayToScriptHash)); +} + +TEST(TWBitcoinScript, MatchPayToPubkey) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToPubkey(PayToPublicKeySecp256k1)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToPubkey(PayToScriptHash), nullptr); +} + +TEST(TWBitcoinScript, TWBitcoinScriptMatchPayToPubkeyHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToPubkeyHash(PayToPublicKeyHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "79091972186c449eb1ded22b78e40d009bdf0089"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToPubkeyHash(PayToScriptHash), nullptr); +} + +TEST(TWBitcoinScript, MatchPayToScriptHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToScriptHash(PayToScriptHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToScriptHash(PayToPublicKeySecp256k1), nullptr); +} + +TEST(TWBitcoinScript, MatchPayToWitnessPublicKeyHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToWitnessPublicKeyHash(PayToWitnessPublicKeyHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "79091972186c449eb1ded22b78e40d009bdf0089"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToWitnessPublicKeyHash(PayToPublicKeySecp256k1), nullptr); +} + +TEST(TWBitcoinScript, MatchPayToWitnessScriptHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToWitnessScriptHash(PayToWitnessScriptHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToWitnessScriptHash(PayToPublicKeySecp256k1), nullptr); +} + +TEST(TWBitcoinScript, Encode) { + const auto res = WRAPD(TWBitcoinScriptEncode(PayToScriptHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "17a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); +} + +TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { + const auto hash = DATA("79091972186c449eb1ded22b78e40d009bdf0089"); + const auto script = TWBitcoinScriptBuildPayToWitnessPubkeyHash(hash.get()); + ASSERT_TRUE(script != nullptr); + const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script)).get())); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0014" "79091972186c449eb1ded22b78e40d009bdf0089"); +} + +TEST(TWBitcoinScript, BuildPayToWitnessScriptHash) { + const auto hash = DATA("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + const auto script = TWBitcoinScriptBuildPayToWitnessScriptHash(hash.get()); + ASSERT_TRUE(script != nullptr); + const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script)).get())); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); +} + +TEST(TWBitcoinScript, ScriptHash) { auto pkData = DATA("cf5007e19af3641199f21f3fa54dff2fa2627471"); auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(pkData.get())); @@ -26,7 +143,7 @@ TEST(BitcoinScript, ScriptHash) { ASSERT_STREQ(TWStringUTF8Bytes(hexData.get()), "c470d22e69a2a967f2cec0cd5a5aebb955cdd395"); } -TEST(BitcoinScript, RedeemScript) { +TEST(TWBitcoinScript, RedeemScript) { auto pkData = DATA("cf5007e19af3641199f21f3fa54dff2fa2627471"); auto embeddedScript = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(pkData.get())); @@ -40,48 +157,70 @@ TEST(BitcoinScript, RedeemScript) { ASSERT_STREQ(TWStringUTF8Bytes(hexData.get()), "a914c470d22e69a2a967f2cec0cd5a5aebb955cdd39587"); } -TEST(BitcoinScript, LockScriptForP2PKHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H").get(), TWCoinTypeBitcoin)); +TEST(TWBitcoinScript, LockScriptForP2PKHAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac"); - auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV").get(), TWCoinTypeBitcoin)); + auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV").get(), TWCoinTypeBitcoin)); auto scriptPub2Data = WRAPD(TWBitcoinScriptData(scriptPub2.get())); assertHexEqual(scriptPub2Data, "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac"); } -TEST(BitcoinScript, LockScriptForP2SHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("37rHiL4DN2wkt8pgCAUfYJRxhir98ZGN1y").get(), TWCoinTypeBitcoin)); +TEST(TWBitcoinScript, LockScriptForP2SHAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("37rHiL4DN2wkt8pgCAUfYJRxhir98ZGN1y").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9144391adbec172cad6a9fc3eebca36aeec6640abda87"); - auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("3HV63hgTNAgdiEp4FbJRPSVrjaV4ZoX4Bs").get(), TWCoinTypeBitcoin)); + auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("3HV63hgTNAgdiEp4FbJRPSVrjaV4ZoX4Bs").get(), TWCoinTypeBitcoin)); auto scriptPub2Data = WRAPD(TWBitcoinScriptData(scriptPub2.get())); assertHexEqual(scriptPub2Data, "a914ad40768af6419a20bdb94d83c06b6c8c94721dc087"); } -TEST(BitcoinScript, LockScriptForP2WPKHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1q6hppaw7uld68amnnu5vpp5dd5u7k92c2vtdtkq").get(), TWCoinTypeBitcoin)); +TEST(TWBitcoinScript, LockScriptForP2WPKHAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bc1q6hppaw7uld68amnnu5vpp5dd5u7k92c2vtdtkq").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014d5c21ebbdcfb747eee73e51810d1ada73d62ab0a"); - auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1qqw0jllft9pcr7r5uw0x08njkft0thd0g5yus0x").get(), TWCoinTypeBitcoin)); + auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bc1qqw0jllft9pcr7r5uw0x08njkft0thd0g5yus0x").get(), TWCoinTypeBitcoin)); auto scriptPub2Data = WRAPD(TWBitcoinScriptData(scriptPub2.get())); assertHexEqual(scriptPub2Data, "0014039f2ffd2b28703f0e9c73ccf3ce564adebbb5e8"); } -TEST(BitcoinScript, LockScriptForP2WSHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1qcuqamesrt803xld4l2j2vxx8rxmrx7aq82mkw7xwxh643wzqjlnqutkcv2").get(), TWCoinTypeBitcoin)); +TEST(TWBitcoinScript, LockScriptForP2WSHAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bc1qcuqamesrt803xld4l2j2vxx8rxmrx7aq82mkw7xwxh643wzqjlnqutkcv2").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0020c701dde60359df137db5faa4a618c719b6337ba03ab76778ce35f558b84097e6"); } -TEST(BitcoinScript, LockScriptForCashAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoin)); +TEST(TWBitcoinScript, LockScriptForCashAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a914b1fb7e043152fd1eed7bfaf66679ad3b6c9068f387"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); } + +TEST(TWBitcoinSigHashType, HashTypeForCoin) { + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoin), (uint32_t)TWBitcoinSigHashTypeAll); + 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(TWCoinTypeBitcoinGold), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG); +} + +TEST(TWBitcoinSigHashType, IsSingle) { + EXPECT_TRUE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeSingle)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeAll)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeNone)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeFork)); +} + +TEST(TWBitcoinSigHashType, IsNone) { + EXPECT_TRUE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeNone)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeSingle)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeAll)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeFork)); +} diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 931e8dfe46f..1ed3bff6931 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -9,10 +9,12 @@ #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 @@ -21,12 +23,109 @@ #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) << 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); +} + TEST(BitcoinSigning, EncodeP2WPKH) { - auto emptyScript = WRAP(TWBitcoinScript, TWBitcoinScriptCreate()); auto unsignedTx = Transaction(1, 0x11); auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); @@ -43,97 +142,231 @@ TEST(BitcoinSigning, EncodeP2WPKH) { auto outScript1 = Script(parse_hex("76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac")); unsignedTx.outputs.emplace_back(223450000, outScript1); - auto unsignedData = std::vector(); - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" - "01000000" - "02" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff" - "02" - "202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" - "9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" - "11000000"); + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + ASSERT_EQ(unsignedData.size(), 164); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "00" "" "eeffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "202cb20600000000" "19" "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" + "9093510d00000000" "19" "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" + // witness + "00" + "00" + "11000000" // nLockTime + ); } -TEST(BitcoinSigning, SignP2WPKH) { - // Build transaction - auto unsignedTx = Transaction(1, 0x11); - +Proto::SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int64_t utxo0Amount, int64_t utxo1Amount, bool useMaxAmount = false) { auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto outpoint0 = OutPoint(hash0, 0); - unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xffffffee); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - auto outpoint1 = OutPoint(hash1, 1); - unsignedTx.inputs.emplace_back(outpoint1, Script(), UINT32_MAX); - - auto outScript0 = Script(parse_hex("76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac")); - unsignedTx.outputs.emplace_back(112'340'000, outScript0); - - auto outScript1 = Script(parse_hex("76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac")); - unsignedTx.outputs.emplace_back(223'450'000, outScript1); // Setup input Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(335'790'000); + 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 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); + 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 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.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("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); + auto scriptPub1 = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + Data scriptHash; scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + auto scriptHashHex = hex(scriptHash); + assert(scriptHashHex == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + 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(625'000'000); + 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("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); - utxo1->set_amount(600'000'000); + 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); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + 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) << 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) << 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 result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "03b30d55430f08365d19a62d3bd32e459ab50984fbcf22921ecc85f1e09dc6ed" - // witid = "20bc58d07d91a3bae9e6f4d617d8f6271723d1a7673e486cc0ecaf9e758e2c22" + 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) << result.error(); + auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "01" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100b6006eb0fe2da8cbbd204f702b1ffdb1e29c49f3de51c4983d420bf9f9125635022032a195b153ccb2c4978333b4aad72aaa7e6a0b334a14621d5d817a42489cb0d301" "ffffffff" - "02" - "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "0000000000" + 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 ); } @@ -146,70 +379,238 @@ TEST(BitcoinSigning, EncodeP2WSH) { auto outScript0 = Script(parse_hex("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac")); unsignedTx.outputs.emplace_back(1000, outScript0); - auto unsignedData = std::vector(); - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" - "01000000" - "01" - "00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff" - "01" - "e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac" - "00000000"); + 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 + ); } -TEST(BitcoinSigning, SignP2WSH) { - // Setup input +Proto::SigningInput buildInputP2WSH(uint32_t hashType, bool omitScript = false, bool omitKeys = false) { Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashType); input.set_amount(1000); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - auto utxoKey0 = parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); + 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()); + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + } - auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); - auto scriptHash = "593128f9f90e38b706c18623151e37d2da05c229"; - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHash] = scriptString; + 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 = DATA("0001000000000000000000000000000000000000000000000000000000000000"); - utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + 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 result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" - // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" + 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) << 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) << 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) << result.error(); + auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized), "01000000" - "0001" - "01" - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" - "01" - "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "02" - "4730440220252e92b8757f1e5577c54ce5deb8072914c1f03333128777dee96ebceeb6a99b02202b7298789316779d0aa7595abeedc03054405c42ab9859e67d9253d2c9a0cdfa01232103596d3451025c" - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" + 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, 226)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + +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); +} + +TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { + // Setup input + auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll); + auto plan = Bitcoin::TransactionPlan(); + input.mutable_plan()->clear_utxos(); + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { auto unsignedTx = Transaction(1, 0x492); @@ -222,22 +623,23 @@ TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { auto outScript1 = Script(parse_hex("76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac")); unsignedTx.outputs.emplace_back(800'000'000, outScript1); - auto unsignedData = std::vector(); - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" - "01000000" - "01" - "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff" - "02" - "b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" - "0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" - "92040000"); + 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 + ); } -TEST(BitcoinSigning, SignP2SH_P2WPKH) { +Proto::SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = false) { // Setup input Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); input.set_amount(200'000'000); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); @@ -246,36 +648,92 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - ASSERT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "79091972186c449eb1ded22b78e40d009bdf0089"); - input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - - auto redeemScript = Script::buildPayToWitnessPubkeyHash(utxoPubkeyHash); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - ASSERT_EQ(hex(scriptHash.begin(), scriptHash.end()), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; + assert(hex(utxoPubkeyHash) == "79091972186c449eb1ded22b78e40d009bdf0089"); + if (!omitKeys) { + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + } + + if (!omitScript) { + 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; + } auto utxo0 = input.add_utxo(); auto utxo0Script = Script(parse_hex("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387")); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(1000'000'000); + 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 result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "060046204220fd00b81fd6426e391acb9670d1e61e8f0224f37276cc34f49e8c" - // witid = "3911b16643972437d27a759b5647a552c7a2e433364b531374f3761967bf8fd7" - Data serialized; - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized), "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089ffffffff0200c2eb0b000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac1e07af2f000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac02473044022009195d870ecc40f54130008e392904e77d32b738c1add19d1d8ebba4edf812e602204f49de6dc60d9a3c3703e1e642942f8834f3a2cd81a6562a34b293942ce42f40012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687300000000"); + 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, 226)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + +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); } TEST(BitcoinSigning, EncodeP2SH_P2WSH) { @@ -291,16 +749,17 @@ TEST(BitcoinSigning, EncodeP2SH_P2WSH) { auto outScript1 = Script(parse_hex("76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac")); unsignedTx.outputs.emplace_back(0x00000000052f83c0, outScript1); - auto unsignedData = std::vector(); - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" - "01000000" - "01" - "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff" - "02" - "00e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" - "c0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" - "00000000"); + 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) { @@ -335,7 +794,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { 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.begin(), scriptHash.end())] = scriptString; + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; auto witnessScript = Script(parse_hex("" "56" @@ -349,7 +808,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { )); auto witnessScriptHash = Hash::ripemd(Hash::sha256(witnessScript.bytes)); auto witnessScriptString = std::string(witnessScript.bytes.begin(), witnessScript.bytes.end()); - (*input.mutable_scripts())[hex(witnessScriptHash.begin(), witnessScriptHash.end())] = witnessScriptString; + (*input.mutable_scripts())[hex(witnessScriptHash)] = witnessScriptString; auto utxo0Script = Script(parse_hex("a9149993a429037b5d912407a71c252019287b8d27a587")); auto utxo = input.add_utxo(); @@ -364,36 +823,320 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { signer.transaction = unsignedTx; signer.plan.utxos = {*utxo}; auto result = signer.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << 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)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + // Fails as there are 0 utxos + ASSERT_FALSE(result); +} + +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, 226)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + +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) << result.error(); auto signedTx = result.payload(); - auto expected = "" - "01000000" - "0001" - "01" - "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff" + 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) << 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) << 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" - "00e9a43500000000" "1976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" - "c0832f0500000000" "1976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" - "08" - "00" - "47304402201992f5426ae0bab04cf206d7640b7e00410297bfe5487637f6c2427ee8496be002204ad4e64dc2d269f593cc4820db1fc1e8dc34774f602945115ce485940e05c64200" - "47304402201e412363fa554b994528fd44149f3985b18bb901289ef6b71105b27c7d0e336c0220595e4a1e67154337757562ed5869127533e3e5084c3c2e128518f5f0b85b721800" - "473044022003b0a20ccf545b3f12c5ade10db8717e97b44da2e800387adfd82c95caf529d902206aee3a2395530d52f476d0ddd9d20ba062820ae6f4e1be4921c3630395743ad900" - "483045022100ed7a0eeaf72b84351bceac474b0c0510f67065b1b334f77e6843ed102f968afe022004d97d0cfc4bf5651e46487d6f87bd4af6aef894459f9778f2293b0b2c5b7bc700" - "483045022100934a0c364820588154aed2d519cbcc61969d837b91960f4abbf0e374f03aa39d022036b5c58b754bd44cb5c7d34806c89d9778ea1a1c900618a841e9fbfbe805ff9b00" - "473044022044e3b59b06931d46f857c82fa1d53d89b116a40a581527eac35c5eb5b7f0785302207d0f8b5d063ffc6749fb4e133db7916162b540c70dee40ec0b21e142d8843b3a00" - "cf56" - "210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3" - "2103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b" - "21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a" - "21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4" - "2103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16" - "2102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b" - "56ae" - "00000000"; - - auto serialized = std::vector(); - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); + "47" "304402204139b82927dd80445f27a5d2c29fa4881dbd2911714452a4a706145bc43cc4bf022016fbdf4b09bc5a9c43e79edb1c1061759779a20c35535082bdc469a61ed0771f01" + "21" "02499e327a05cc8bb4b3c34c8347ecfcb152517c9927c092fa273be5379fde3226" + "00000000" // nLockTime + ); } diff --git a/tests/Bitcoin/TWBitcoinTransactionTests.cpp b/tests/Bitcoin/TWBitcoinTransactionTests.cpp index 079f7377218..4086289a438 100644 --- a/tests/Bitcoin/TWBitcoinTransactionTests.cpp +++ b/tests/Bitcoin/TWBitcoinTransactionTests.cpp @@ -32,9 +32,10 @@ TEST(BitcoinTransaction, Encode) { auto oscript1 = Script(parse_hex("0x76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac")); transaction.outputs.emplace_back(400000000, oscript1); - - auto unsignedData = std::vector(); - transaction.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" + + Data unsignedData; + transaction.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(unsignedData.size(), 201); + ASSERT_EQ(hex(unsignedData), "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); } diff --git a/tests/Bitcoin/TransactionPlanTests.cpp b/tests/Bitcoin/TransactionPlanTests.cpp index aa41368a739..6767a3ca21f 100644 --- a/tests/Bitcoin/TransactionPlanTests.cpp +++ b/tests/Bitcoin/TransactionPlanTests.cpp @@ -4,11 +4,12 @@ // 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/UnspentSelector.h" #include "Bitcoin/TransactionPlan.h" #include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/FeeCalculator.h" #include "proto/Bitcoin.pb.h" #include @@ -17,96 +18,278 @@ using namespace TW; using namespace TW::Bitcoin; -auto const txOutPoint = OutPoint(std::vector(32), 0); +TEST(TransactionPlan, OneTypical) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto sigingInput = buildSigningInput(50'000, byteFee, utxos); -inline auto sum(const std::vector& utxos) { - int64_t s = 0u; - for (auto& utxo : utxos) { - s += utxo.amount(); - } - return s; + 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), 226); +} + +TEST(TransactionPlan, OneInsufficient) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(200'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); } -inline auto buildUTXO(const OutPoint& outPoint, Amount amount) { - Proto::UnspentTransaction utxo; - utxo.set_amount(amount); - utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); - utxo.mutable_out_point()->set_index(outPoint.index); - return utxo; +TEST(TransactionPlan, OneInsufficientEqual) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(100'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, OneInsufficientHigher) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(99'900, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, OneFitsExactly) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto expectedFee = 147; + auto sigingInput = buildSigningInput(100'000 - 226, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 226, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 226); +} + +TEST(TransactionPlan, OneFitsExactlyHighFee) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 10; + auto expectedFee = 1470; + auto sigingInput = buildSigningInput(100'000 - 2260, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 2260, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 2260); +} + +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 - 226 - 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 - 226 - 10, 215)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(feeCalculator.calculate(2, 2, 1), 374); + + // Now 100'000 fits with no dust; 546 is the dust limit + sigingInput = buildSigningInput(100'000 - 226 - 546, 1, utxos); + txPlan = TransactionBuilder::plan(sigingInput); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 226 - 546, 147)); + + // One more and we are over dust limit + sigingInput = buildSigningInput(100'000 - 226 - 546 + 1, 1, utxos); + txPlan = TransactionBuilder::plan(sigingInput); + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 546 + 1, 215)); } -inline auto 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); - *input.mutable_utxo() = { utxos.begin(), utxos.end() }; - return input; +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 = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, 4000)); - utxos.push_back(buildUTXO(txOutPoint, 2000)); - utxos.push_back(buildUTXO(txOutPoint, 6000)); - utxos.push_back(buildUTXO(txOutPoint, 1000)); - utxos.push_back(buildUTXO(txOutPoint, 50000)); - utxos.push_back(buildUTXO(txOutPoint, 120000)); + 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, UnpsentsInsufficient) { + auto utxos = buildTestUTXOs({4000, 4000, 4000}); + auto sigingInput = buildSigningInput(15000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, NoUTXOs) { + auto utxos = buildTestUTXOs({}); + auto sigingInput = buildSigningInput(15000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +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), 22814); +} + +TEST(TransactionPlan, Target0) { + auto utxos = buildTestUTXOs({2000, 3000}); + auto sigingInput = buildSigningInput(0, 1, utxos); - auto sigingInput = buildSigningInput(10000, 1, utxos, false, TWCoinTypeBitcoin); auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.amount, 10000); - ASSERT_EQ(txPlan.change, 39774); + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); } TEST(TransactionPlan, MaxAmount) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, 4000)); - utxos.push_back(buildUTXO(txOutPoint, 2000)); - utxos.push_back(buildUTXO(txOutPoint, 15000)); - utxos.push_back(buildUTXO(txOutPoint, 15000)); - utxos.push_back(buildUTXO(txOutPoint, 3000)); - utxos.push_back(buildUTXO(txOutPoint, 200)); + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 32; + auto sigingInput = buildSigningInput(39200, byteFee, utxos, true); - ASSERT_EQ(sum(utxos), 39200); + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4736); - auto sigingInput = buildSigningInput(39200, 32, utxos, true, TWCoinTypeBitcoin); + // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.availableAmount, 30000); - ASSERT_EQ(txPlan.amount, 19120); - ASSERT_EQ(txPlan.change, 0); - ASSERT_EQ(txPlan.fee, 10880); + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - 5792, 5792)); } -TEST(TransactionPlan, MaxAmountDoge) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, Amount(100000000))); - utxos.push_back(buildUTXO(txOutPoint, Amount(2000000000))); - utxos.push_back(buildUTXO(txOutPoint, Amount(200000000))); +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, MaxAmountLowerRequested) { + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 32; + auto sigingInput = buildSigningInput(10, byteFee, utxos, true); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4736); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - 5792, 5792)); +} + +TEST(TransactionPlan, MaxAmount4of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + auto byteFee = 3; + auto sigingInput = buildSigningInput(100, byteFee, utxos, true); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 951; + EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1000}, 2'900 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 444); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1908); +} + +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); - ASSERT_EQ(sum(utxos), Amount(2300000000)); + // Fee is reduced to availableAmount + EXPECT_TRUE(verifyPlan(txPlan, {170}, 170 - expectedFee, expectedFee)); + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 192); +} + +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); - ASSERT_EQ(txPlan.availableAmount, Amount(2300000000)); - ASSERT_EQ(txPlan.amount, Amount(2299951200)); - ASSERT_EQ(txPlan.change, 0); - ASSERT_EQ(txPlan.fee, 48800); + EXPECT_TRUE(verifyPlan(txPlan, {100000000, 2000000000, 200000000}, 2299951200, 48800)); } TEST(TransactionPlan, AmountDecred) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, Amount(39900000))); - + auto utxos = buildTestUTXOs({Amount(39900000)}); auto sigingInput = buildSigningInput(Amount(10000000), 10, utxos, false, TWCoinTypeDecred); + auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.availableAmount, Amount(39900000)); - ASSERT_EQ(txPlan.amount, Amount(10000000)); - ASSERT_EQ(txPlan.change, 29897460); - ASSERT_EQ(txPlan.fee, 2540); + EXPECT_TRUE(verifyPlan(txPlan, {39900000}, 10000000, 2540)); } diff --git a/tests/Bitcoin/TxComparisonHelper.cpp b/tests/Bitcoin/TxComparisonHelper.cpp new file mode 100644 index 00000000000..d7246846750 --- /dev/null +++ b/tests/Bitcoin/TxComparisonHelper.cpp @@ -0,0 +1,234 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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) { + 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; + } + 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 new file mode 100644 index 00000000000..0fdc88264bc --- /dev/null +++ b/tests/Bitcoin/TxComparisonHelper.h @@ -0,0 +1,57 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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); + +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 index c818e26d682..57c8ce56e90 100644 --- a/tests/Bitcoin/UnspentSelectorTests.cpp +++ b/tests/Bitcoin/UnspentSelectorTests.cpp @@ -6,190 +6,375 @@ #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; -auto transactionOutPoint = OutPoint(std::vector(32), 0); - -inline auto sum(const std::vector& utxos) { - int64_t s = 0u; - for (auto& utxo : utxos) { - s += utxo.amount(); - } - return s; -} - -inline auto buildUTXO(const OutPoint& outPoint, int64_t amount) { - Proto::UnspentTransaction utxo; - utxo.set_amount(amount); - utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); - utxo.mutable_out_point()->set_index(outPoint.index); - return utxo; -} - -TEST(UnspentSelector, SelectUnpsents1) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 6000)); - utxos.push_back(buildUTXO(transactionOutPoint, 1000)); - utxos.push_back(buildUTXO(transactionOutPoint, 11000)); - utxos.push_back(buildUTXO(transactionOutPoint, 12000)); +TEST(BitcoinUnspentSelector, SelectUnpsents1) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 11000, 12000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 5000, 1); - ASSERT_EQ(sum(selected), 11000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {11000})); } -TEST(UnspentSelector, SelectUnpsents2) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 6000)); - utxos.push_back(buildUTXO(transactionOutPoint, 1000)); - utxos.push_back(buildUTXO(transactionOutPoint, 50000)); - utxos.push_back(buildUTXO(transactionOutPoint, 120000)); +TEST(BitcoinUnspentSelector, SelectUnpsents2) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 10000, 1); - ASSERT_EQ(sum(selected), 50000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {50000})); } -TEST(UnspentSelector, SelectUnpsents3) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 5000)); +TEST(BitcoinUnspentSelector, SelectUnpsents3) { + auto utxos = buildTestUTXOs({4000, 2000, 5000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 6000, 1); - ASSERT_EQ(sum(selected), 9000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {4000, 5000})); } -TEST(UnspentSelector, SelectUnpsents4) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 40000)); - utxos.push_back(buildUTXO(transactionOutPoint, 30000)); - utxos.push_back(buildUTXO(transactionOutPoint, 30000)); +TEST(BitcoinUnspentSelector, SelectUnpsents4) { + auto utxos = buildTestUTXOs({40000, 30000, 30000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 50000, 1); - ASSERT_EQ(sum(selected), 70000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {30000, 40000})); } -TEST(UnspentSelector, SelectUnpsents5) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 1000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 3000)); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 5000)); - utxos.push_back(buildUTXO(transactionOutPoint, 6000)); - utxos.push_back(buildUTXO(transactionOutPoint, 7000)); - utxos.push_back(buildUTXO(transactionOutPoint, 8000)); - utxos.push_back(buildUTXO(transactionOutPoint, 9000)); +TEST(BitcoinUnspentSelector, SelectUnpsents5) { + auto utxos = buildTestUTXOs({1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 28000, 1); - ASSERT_EQ(sum(selected), 30000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {6000, 7000, 8000, 9000})); } -TEST(UnspentSelector, SelectUnpsentsInsufficient) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); +TEST(BitcoinUnspentSelector, SelectUnpsentsInsufficient) { + auto utxos = buildTestUTXOs({4000, 4000, 4000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 15000, 1); - ASSERT_TRUE(selected.empty()); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectCustomCase) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 794121)); - utxos.push_back(buildUTXO(transactionOutPoint, 2289357)); +TEST(BitcoinUnspentSelector, SelectCustomCase) { + auto utxos = buildTestUTXOs({794121, 2289357}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 2287189, 61); - ASSERT_EQ(sum(selected), 3083478); + 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 = 226; + auto selected = selector.select(utxos, 100'000 - expectedFee, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + ASSERT_EQ(feeCalculator.calculate(1, 2, 1), expectedFee); + ASSERT_EQ(feeCalculator.calculate(1, 1, 1), 192); + + // 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 = 2260; + auto selected = selector.select(utxos, 100'000 - expectedFee, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + ASSERT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); + ASSERT_EQ(feeCalculator.calculate(1, 1, byteFee), 1920); + + // 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 selector = UnspentSelector(); + auto selected = selector.select(utxos, 100'000 - 226 - 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})); + + // Now 100'000 fits with no dust; 546 is the dust limit + selected = selector.select(utxos, 100'000 - 226 - 546, 1); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + // One more and we are over dust limit + selected = selector.select(utxos, 100'000 - 226 - 546 + 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(UnspentSelector, SelectMaxCase) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 10189534)); +TEST(BitcoinUnspentSelector, SelectTwoBoth) { + auto utxos = buildTestUTXOs({20'000, 80'000}); auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 10189342, 1); + 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); + auto selected = selector.select(utxos, 375'000 - 522 - 546, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); + + ASSERT_EQ(feeCalculator.calculate(3, 2, 1), 522); + + // one more, and it's too much + selected = selector.select(utxos, 375'000 - 522 - 546 + 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), 226); +} + +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), 226); +} + +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), 148); + EXPECT_EQ(feeCalculator.calculate(5, 1, byteFee), 784); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmount4of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto byteFee = 3; + auto selected = selector.selectMaxAmount(utxos, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {500, 600, 800, 1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 444); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1908); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmount1of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto byteFee = 6; + auto selected = selector.selectMaxAmount(utxos, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 888); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 1152); +} + +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), 1480); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmountNoUTXOs) { + auto utxos = buildTestUTXOs({}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto selected = selector.selectMaxAmount(utxos, 1); - ASSERT_EQ(sum(selected), 10189534); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectZcashUnpsents) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 100000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2592)); - utxos.push_back(buildUTXO(transactionOutPoint, 73774)); +TEST(BitcoinUnspentSelector, SelectZcashUnpsents) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); - auto selector = UnspentSelector(calculator); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); auto selected = selector.select(utxos, 10000, 1); - ASSERT_EQ(sum(selected), 73774); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {73774})); } -TEST(UnspentSelector, SelectGroestlUnpsents) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 499971976)); +TEST(BitcoinUnspentSelector, SelectGroestlUnpsents) { + auto utxos = buildTestUTXOs({499971976}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeGroestlcoin); - auto selector = UnspentSelector(calculator); - auto selected = selector.select(utxos, 499951976, 1, 2); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); + auto selected = selector.select(utxos, 499951976, 1, 1); - ASSERT_EQ(sum(selected), 499971976); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {499971976})); } -TEST(UnspentSelector, SelectZcashMaxUnpsents) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 100000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2592)); - utxos.push_back(buildUTXO(transactionOutPoint, 73774)); +TEST(BitcoinUnspentSelector, SelectZcashMaxAmount) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); - auto selector = UnspentSelector(calculator); - auto selected = selector.select(utxos, 166366, 1); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); + auto selected = selector.selectMaxAmount(utxos, 1); - ASSERT_EQ(sum(selected), 176366); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100000, 2592, 73774})); } -TEST(UnspentSelector, SelectZcashMaxUnpsents2) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 100000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2592)); - utxos.push_back(buildUTXO(transactionOutPoint, 73774)); +TEST(BitcoinUnspentSelector, SelectZcashMaxUnpsents2) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); - auto selector = UnspentSelector(calculator); - auto selected = selector.select(utxos, 176360, 1); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); + auto selected = selector.select(utxos, 176366 - 6, 1); - ASSERT_EQ(sum(selected), 0); - ASSERT_TRUE(selected.size() == 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/BitcoinCash/TWBitcoinCashTests.cpp index bddceeaf3f6..30dfb7a4850 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/BitcoinCash/TWBitcoinCashTests.cpp @@ -5,6 +5,7 @@ // 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" @@ -33,7 +34,7 @@ TEST(BitcoinCash, ValidAddress) { auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinCash)); ASSERT_NE(address.get(), nullptr); - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(string.get(), TWCoinTypeBitcoinCash)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(string.get(), TWCoinTypeBitcoinCash)); ASSERT_FALSE(TWBitcoinScriptSize(script.get()) == 0); } @@ -67,7 +68,7 @@ TEST(BitcoinCash, LockScript) { auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l").get(), TWCoinTypeBitcoinCash)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l").get(), TWCoinTypeBitcoinCash)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914b9604b7820876bc510009b8247316c4b801aff8a87"); } @@ -87,8 +88,8 @@ TEST(BitcoinCash, ExtendedKeys) { TEST(BitcoinCash, DeriveFromXPub) { auto xpub = STRING("xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/9").get()); auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2, TWCoinTypeBitcoinCash)); auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); @@ -107,7 +108,7 @@ TEST(BitcoinCash, SignTransaction) { // https://blockchair.com/bitcoin-cash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 auto input = Proto::SigningInput(); - input.set_hash_type(TWBitcoinSigHashTypeFork | TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinCash)); input.set_amount(amount); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); @@ -129,6 +130,10 @@ TEST(BitcoinCash, SignTransaction) { 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" diff --git a/tests/BitcoinGold/TWAddressTests.cpp b/tests/BitcoinGold/TWAddressTests.cpp new file mode 100644 index 00000000000..77c192461bb --- /dev/null +++ b/tests/BitcoinGold/TWAddressTests.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp new file mode 100644 index 00000000000..93bb9c230e1 --- /dev/null +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -0,0 +1,125 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "Bitcoin/SegwitAddress.h" +#include "proto/Bitcoin.pb.h" +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "Bitcoin/SigHashType.h" +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWBitcoinGoldScript, LockScriptTest) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("btg1q6572ulr0kmywle8a30lvagm9xsg9k9n5cmzfdj").get(), TWCoinTypeBitcoinGold)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07").get(), TWCoinTypeBitcoinGold)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "0014ebae10950c8a35a506e0e265b928305233e802ab"); +} + +TEST(BitcoinGoldKey, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("shoot island position soft burden budget tooth cruel issue economy destroy above").get(), + STRING("TREZOR").get() + )); + + // .bip84 + auto zprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoinGold, TWHDVersionZPRV)); + auto zpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoinGold, TWHDVersionZPUB)); + + assertStringsEqual(zprv, "zprvAdB7dYnT955ubXEkdBWhDqFSyeDfVpKQmVJPbRXGiAg4mnGT7dCBsZZFeik1mNt6bS4zkdZSNtZm8dqu1Lrp1brQ16NgYgeEoiz6ftUfVAW"); + assertStringsEqual(zpub, "zpub6rAU34KLySeCp1KDjD3hayCBXg49uH3G8iDzPovtGWD3eabbfAWSRMsjVyfuRfCCquiKTD6YV42nHUBtwh2TbVPvWqxrGuyEvHN17c3XUXw"); +} + +TEST(BitcoinGoldKey, DeriveFromZPub) { + auto zpub = STRING("zpub6rAU34KLySeCp1KDjD3hayCBXg49uH3G8iDzPovtGWD3eabbfAWSRMsjVyfuRfCCquiKTD6YV42nHUBtwh2TbVPvWqxrGuyEvHN17c3XUXw"); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoinGold, STRING("m/84'/156'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoinGold, STRING("m/84'/156'/0'/0/9").get()); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2, TWCoinTypeBitcoinGold)); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + + auto address9 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey9, TWCoinTypeBitcoinGold)); + auto address9String = WRAPS(TWAnyAddressDescription(address9.get())); + + assertStringsEqual(address2String, "btg1qkdgxykht6nww9l9rn0xhslf78nl605gwka9zak"); + assertStringsEqual(address9String, "btg1qzw2ptuyaw023gm7te2r5e3xkufn9wrm3kzrg8t"); +} + +TEST(TWBitcoinGoldTxGeneration, TxGeneration) { + const int64_t amount = 5000; + + // Setup input + Proto::SigningInput input; + input.set_coin_type(TWCoinTypeBitcoinGold); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinGold)); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("btg1qmd6x5awe4t5fjhgntv0pngzdwajjg250wxdcs0"); + input.set_change_address("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07"); + + 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 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("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(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + txSigner.encodeTx(signedTx, serialized); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f" "00000000" "00" "" "fdffffff" + "02" // outputs + "8813000000000000" "16" "0014db746a75d9aae8995d135b1e19a04d7765242a8f" + "fb12000000000000" "16" "0014ebae10950c8a35a506e0e265b928305233e802ab" + // witness + "02" "47" "304402202b371b7cae885463c06357d1fc6ca95ab155613f212711bc7fb115500654946d0220430af77cbbb30afe7d7dcaccb72a55da802ee0a2bfea790dfe7c4e1a4c53fd7d41" "21" "03e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" + "71890900" // nLockTime + ); +} + diff --git a/tests/BitcoinGold/TWCoinTypeTests.cpp b/tests/BitcoinGold/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ddbad39b6ec --- /dev/null +++ b/tests/BitcoinGold/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 = TWStringCreateWithUTF8Bytes("2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinGold, txId)); + auto accId = TWStringCreateWithUTF8Bytes("GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinGold, accId)); + 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 new file mode 100644 index 00000000000..9d2f4965807 --- /dev/null +++ b/tests/BitcoinGold/TWSegwitAddressTests.cpp @@ -0,0 +1,49 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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) { + std::pair result = Bitcoin::SegwitAddress::decode("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); + + ASSERT_TRUE(result.second); + ASSERT_EQ(result.first.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); +} diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/BitcoinGold/TWSignerTests.cpp new file mode 100644 index 00000000000..f731709700c --- /dev/null +++ b/tests/BitcoinGold/TWSignerTests.cpp @@ -0,0 +1,101 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "Bitcoin/SegwitAddress.h" +#include "proto/Bitcoin.pb.h" +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "Bitcoin/SigHashType.h" +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" +#include "../Bitcoin/TxComparisonHelper.h" + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWBitcoinGoldSigner, SignTransaction) { + const int64_t amount = 10000; + + // Setup input + Proto::SigningInput input; + input.set_coin_type(TWCoinTypeBitcoinGold); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinGold)); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("btg1qmd6x5awe4t5fjhgntv0pngzdwajjg250wxdcs0"); + input.set_change_address("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07"); + + 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 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("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); + + + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {99'000}, amount, 141)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(149); + input.mutable_plan()->set_change(88851); + + // Sign + auto txSigner = TransactionSigner(std::move(input)); + txSigner.transaction.lockTime = 0x00098971; + auto result = txSigner.sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + txSigner.encodeTx(signedTx, serialized); + // BitcoinGold Mainnet: https://btg2.trezor.io/tx/db26faec66d070045df0da56140349beb5a12bd14bca12b162fded8f84d18afa + EXPECT_EQ(serialized.size(), 222); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "01" + "1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03" "01000000" "00" "fdffffff" + "02" + "1027000000000000" "160014db746a75d9aae8995d135b1e19a04d7765242a8f" + "135b010000000000" "160014ebae10950c8a35a506e0e265b928305233e802ab" + "02" + "4730440220325c56363b17e1b1329efeb400c0933a3d9adfb304f29889b3ef01084aef19e302202a69d9be9ef668b5a5517fbfa42e1fc26b3f8b582c721bd1eabd721322bc2b6c41" + "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" + "71890900" + ); +} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be1569b89cd..f6acfc476be 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,7 +6,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/gtest/staging/googletest-release-1.8.1 +add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.8.1 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) diff --git a/tests/Cardano/AddressTests.cpp b/tests/Cardano/AddressTests.cpp index ee84f5c66a9..a7e828aea14 100644 --- a/tests/Cardano/AddressTests.cpp +++ b/tests/Cardano/AddressTests.cpp @@ -93,37 +93,37 @@ TEST(CardanoAddress, MnemonicToAddressV2) { EXPECT_EQ("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", addr); } { - PrivateKey privKey0 = wallet.getKey(DerivationPath("m/44'/1815'/0'/0/0")); + 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(DerivationPath("m/44'/1815'/0'/0/1")); + 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(DerivationPath("m/44'/1815'/0'/0/2")); + 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(DerivationPath("m/1852'/1815'/0'/0/0")); + 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(DerivationPath("m/1852'/1815'/0'/0/1")); + 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(DerivationPath("m/1852'/1815'/0'/0/2")); + 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()); @@ -151,7 +151,7 @@ TEST(CardanoAddress, MnemonicToAddressV2) { // 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(DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + 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); @@ -160,7 +160,7 @@ TEST(CardanoAddress, MnemonicToAddressV2) { // 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(DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + 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); @@ -170,7 +170,7 @@ TEST(CardanoAddress, MnemonicToAddressV2) { // 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(DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + 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); diff --git a/tests/CborTests.cpp b/tests/CborTests.cpp index 48f454ad2fe..2c07cc1b1dc 100644 --- a/tests/CborTests.cpp +++ b/tests/CborTests.cpp @@ -80,6 +80,8 @@ TEST(Cbor, EncNegInt) { 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()); } @@ -92,10 +94,18 @@ TEST(Cbor, EncString) { "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) { - EXPECT_EQ("c506", hex(Encode::tag(5, Encode::uint(6)).encoded())); + { + 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())); } @@ -146,7 +156,7 @@ TEST(Cbor, DecInt) { EXPECT_EQ(0xffffffffffffffff, Decode(parse_hex("1bffffffffffffffff")).getValue()); } -TEST(Cbor, DecMinortypeInvlalid) { +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 @@ -195,11 +205,11 @@ TEST(Cbor, DecMemoryref) { delete dummy; } -TEST(Cbor, getValue) { +TEST(Cbor, GetValue) { EXPECT_EQ(5, Decode(parse_hex("05")).getValue()); } -TEST(Cbor, getValueInvalid) { +TEST(Cbor, GetValueInvalid) { try { Decode(parse_hex("83010203")).getValue(); // array } catch (exception& ex) { @@ -208,7 +218,7 @@ TEST(Cbor, getValueInvalid) { FAIL() << "Expected exception"; } -TEST(Cbor, getString) { +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()); @@ -216,6 +226,26 @@ TEST(Cbor, getString) { 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(); @@ -364,6 +394,45 @@ TEST(Cbor, MapNested) { 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)) @@ -380,6 +449,10 @@ TEST(Cbor, ArrayIndef) { 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) { @@ -418,3 +491,23 @@ TEST(Cbor, ArrayInfefErrorNoBreak) { // 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 index 16ac09114df..0efb09a78d4 100644 --- a/tests/CoinAddressDerivationTests.cpp +++ b/tests/CoinAddressDerivationTests.cpp @@ -9,6 +9,9 @@ #include +#include +#include + namespace TW { TEST(Coin, DeriveAddress) { @@ -71,6 +74,38 @@ TEST(Coin, DeriveAddress) { EXPECT_EQ(TW::deriveAddress(TWCoinTypeFilecoin, privateKey), "f1qsx7qwiojh5duxbxhbqgnlyx5hmpcf7mcz5oxsy"); EXPECT_EQ(TW::deriveAddress(TWCoinTypeNEAR, privateKey), "NEAR2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5HYrdGj"); EXPECT_EQ(TW::deriveAddress(TWCoinTypeSolana, privateKey), "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeElrond, privateKey), "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); +} + +int countThreadReady = 0; +std::mutex countThreadReadyMutex; + +void useCoinFromThread() { + const int tryCount = 20; + for (int i = 0; i < tryCount; ++i) { + // perform some operations + const auto coinTypes = TW::getCoinTypes(); + TW::validateAddress(TWCoinTypeEthereum, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + } + 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/CoinAddressValidationTests.cpp index 7b08cf9f61b..35b38ff2ea8 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/CoinAddressValidationTests.cpp @@ -322,6 +322,14 @@ TEST(Coin, ValidateAddresKavaa) { EXPECT_FALSE(validateAddress(TWCoinTypeKava, "kava1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z09wt000")); } +TEST(Coin, ValidateAddresBand) { + EXPECT_TRUE(validateAddress(TWCoinTypeBandChain, "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh")); + // wrong prefix + EXPECT_FALSE(validateAddress(TWCoinTypeBandChain, "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp")); + // wrong checksum + EXPECT_FALSE(validateAddress(TWCoinTypeBandChain, "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc8000")); +} + TEST(Coin, ValidateAddresCardano) { // valid V3 address EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe")); @@ -373,4 +381,10 @@ TEST(Coin, ValidateAddressVeChain) { EXPECT_EQ(normalizeAddress(TWCoinTypeVeChain, "0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f"), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); } +TEST(Coin, ValidateAddressElrond) { + EXPECT_TRUE(validateAddress(TWCoinTypeElrond, "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); + EXPECT_FALSE(validateAddress(TWCoinTypeElrond, "xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); +} + + } // namespace TW diff --git a/tests/Dash/TWDashTests.cpp b/tests/Dash/TWDashTests.cpp index 5ae94be6fd5..c1ad9c58117 100644 --- a/tests/Dash/TWDashTests.cpp +++ b/tests/Dash/TWDashTests.cpp @@ -11,11 +11,11 @@ #include TEST(Dash, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("XgkpWEFe59pX3aMhx6PrDawjNnoZKHeZbp").get(), TWCoinTypeDash)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("XgkpWEFe59pX3aMhx6PrDawjNnoZKHeZbp").get(), TWCoinTypeDash)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91442914f5b70c61619eca5359df57d0b9bdcf8ccff88ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("7eprxeVjKfVgS8p2RNsZ89K72YV61xg4gq").get(), TWCoinTypeDash)); + 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 index fc73e0cb011..49f627dabe9 100644 --- a/tests/Decred/AddressTests.cpp +++ b/tests/Decred/AddressTests.cpp @@ -42,6 +42,6 @@ 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(path)); + 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 index f2889377aab..e022d6b0d4f 100644 --- a/tests/Decred/SignerTests.cpp +++ b/tests/Decred/SignerTests.cpp @@ -6,7 +6,6 @@ #include "Decred/Address.h" #include "Decred/Signer.h" -#include "proto/Bitcoin.pb.h" #include "proto/Decred.pb.h" #include "Hash.h" @@ -19,7 +18,7 @@ using namespace TW; using namespace TW::Decred; -TEST(DecredSigner, Sign) { +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)); @@ -62,7 +61,7 @@ TEST(DecredSigner, Sign) { 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(), 32); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); utxo0->mutable_out_point()->set_index(0); @@ -114,3 +113,305 @@ TEST(DecredSigner, Sign) { 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); + + + // 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); + + 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) << result.error(); +} diff --git a/tests/Decred/TWAnySignerTests.cpp b/tests/Decred/TWAnySignerTests.cpp index 4fc02091ff7..c8febd6c718 100644 --- a/tests/Decred/TWAnySignerTests.cpp +++ b/tests/Decred/TWAnySignerTests.cpp @@ -103,6 +103,7 @@ TEST(TWAnySignerDecred, PlanAndSign) { ANY_SIGN(input, TWCoinTypeDecred); ASSERT_TRUE(output.error().empty()); + ASSERT_EQ(output.encoded().size(), 251); ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ace23bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402203e6ee9e16d6bc36bb4242f7a4cac333a1c2a150ea16143412b88b721f6ae16bf02201019affdf815a5c22e4b0fb7e4685c4707094922d6a41354f9055d3bb0f26e630121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); } diff --git a/tests/Decred/TWDecredTests.cpp b/tests/Decred/TWDecredTests.cpp index b43172f8b8d..037211ad2d8 100644 --- a/tests/Decred/TWDecredTests.cpp +++ b/tests/Decred/TWDecredTests.cpp @@ -28,18 +28,18 @@ TEST(Decred, ExtendedKeys) { TEST(Decred, DerivePubkeyFromDpub) { auto dpub = STRING("dpubZFUmm9oh5zmQkR2Tr2AXS4tCkTWg4B27SpCPFkapZrrAqgU1EwgEFgrmi6EnLGXhak86yDHhXPxFAnGU58W5S4e8NCKG1ASUVaxwRqqNdfP"); - auto pubKey0 = TWHDWalletGetPublicKeyFromExtended(dpub.get(), STRING("m/44'/42'/0'/0/0").get()); + auto pubKey0 = TWHDWalletGetPublicKeyFromExtended(dpub.get(), TWCoinTypeDecred, STRING("m/44'/42'/0'/0/0").get()); auto address0 = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeDecred, pubKey0)); assertStringsEqual(address0, "DsksmLD2wDoA8g8QfFvm99ASg8KsZL8eJFd"); } TEST(Decred, Lockscripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx").get(), TWCoinTypeDecred)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx").get(), TWCoinTypeDecred)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a914f5916158e3e2c4551c1796708db8367207ed13bb87"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("DsfD7KYsJtRraYXPZM61ui7779oYJCakYvH").get(), TWCoinTypeDecred)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DsfD7KYsJtRraYXPZM61ui7779oYJCakYvH").get(), TWCoinTypeDecred)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9149c417596dea6570f8e546674555b5ce5087ce2c288ac"); } diff --git a/tests/DerivationPathTests.cpp b/tests/DerivationPathTests.cpp index 04dfd5de255..4b2269ad864 100644 --- a/tests/DerivationPathTests.cpp +++ b/tests/DerivationPathTests.cpp @@ -11,7 +11,7 @@ namespace TW { TEST(DerivationPath, InitWithIndices) { - const auto path = DerivationPath(TWPurposeBIP44, TWCoinTypeEthereum, 0, 0, 0); + 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)); diff --git a/tests/DigiByte/TWDigiByteTests.cpp b/tests/DigiByte/TWDigiByteTests.cpp index 68f66ae695a..f39ea2186da 100644 --- a/tests/DigiByte/TWDigiByteTests.cpp +++ b/tests/DigiByte/TWDigiByteTests.cpp @@ -69,7 +69,7 @@ TEST(DigiByteTransaction, SignTransaction) { ASSERT_EQ(fee, signer.plan.fee); Data serialized; - signedTx.encode(false, serialized); + signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), "01000000" @@ -119,29 +119,30 @@ TEST(DigiByteTransaction, SignP2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized), "0100000000010180a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea59400100000000ffffffff0280841e00000000001600145c91bc8d2073529224e8be0764128ac22f0005647e351e00000000001600144b62694cfdd7bdac59cbed211288ccd5c0dabd02024730440220346eed84d79a2e45f0f291c9af91bc71d6217cff3488047b35c492a3690cd2ce02204724529e4689060532f9286532c87e3b6efb877008e8e44218c1fced7edf949a012102ac2e56f40d38530fcbf21f1eba0c3c668aa839cda8f2c615e99df44b6447772600000000"); + signer.encodeTx(signedTx, serialized); + ASSERT_EQ(hex(serialized), "0100000000010180a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea59400100000000ffffffff0280841e00000000001600145c91bc8d2073529224e8be0764128ac22f000564d3351e00000000001600144b62694cfdd7bdac59cbed211288ccd5c0dabd0202473044022057b876880b6c98511d9e5baab00428c50bf96868bdf4dc50bd61c2477ed8438b0220312ff89a078ab5a38b7b909ceb58310d93a5b4e2d637b37b77c4d7baf35a1815012102ac2e56f40d38530fcbf21f1eba0c3c668aa839cda8f2c615e99df44b6447772600000000"); } TEST(DigiByteTransaction, LockScripts) { // https://dgb2.trezor.io/tx/966b80caa754148158339a0fa70363996f15819599adf72de0eb3590c3bd63ea - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4").get(), TWCoinTypeDigiByte)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4").get(), TWCoinTypeDigiByte)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("dgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3d").get(), TWCoinTypeDigiByte)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("dgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3d").get(), TWCoinTypeDigiByte)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "0014885534ab5dc680b68d95c0af49ec2acc2e9915c4"); // https://dgb2.trezor.io/tx/965eb4afcd0aa6e3f4f8fc3513ca042f09e6e2235367fa006cbd1f546c293a2a - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo").get(), TWCoinTypeDigiByte)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo").get(), TWCoinTypeDigiByte)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a91452356ed3d2d31eb8b263ace5d164e3cf3b37096687"); } diff --git a/tests/Dogecoin/TWDogeTests.cpp b/tests/Dogecoin/TWDogeTests.cpp index d073bc2293f..76f83bdb96a 100644 --- a/tests/Dogecoin/TWDogeTests.cpp +++ b/tests/Dogecoin/TWDogeTests.cpp @@ -11,11 +11,11 @@ #include TEST(Doge, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("DLSSSUS3ex7YNDACJDxMER1ZMW579Vy8Zy").get(), TWCoinTypeDogecoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DLSSSUS3ex7YNDACJDxMER1ZMW579Vy8Zy").get(), TWCoinTypeDogecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914a7d191ec42aa113e28cd858cceaa7c733ba2f77788ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("AETZJzedcmLM2rxCM6VqCGF3YEMUjA3jMw").get(), TWCoinTypeDogecoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("AETZJzedcmLM2rxCM6VqCGF3YEMUjA3jMw").get(), TWCoinTypeDogecoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914f191149f72f235548746654f5b473c58258f7fb687"); } diff --git a/tests/Elrond/AddressTests.cpp b/tests/Elrond/AddressTests.cpp new file mode 100644 index 00000000000..3dd1fb6f166 --- /dev/null +++ b/tests/Elrond/AddressTests.cpp @@ -0,0 +1,79 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..35110688955 --- /dev/null +++ b/tests/Elrond/SerializationTests.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..39a019caf65 --- /dev/null +++ b/tests/Elrond/SignerTests.cpp @@ -0,0 +1,93 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..0124614bf06 --- /dev/null +++ b/tests/Elrond/TWAnySignerTests.cpp @@ -0,0 +1,57 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 new file mode 100644 index 00000000000..fa8238e79f4 --- /dev/null +++ b/tests/Elrond/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 = TWStringCreateWithUTF8Bytes("1fc9785cb8bea0129a16cf7bddc97630c176a556ea566f0e72923c882b5cb3c8"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeElrond, txId)); + auto accId = TWStringCreateWithUTF8Bytes("erd12yne790km8ezwetkz7m3hmqy9utdc6vdkgsunfzpwguec6v04p2qtk9uqj"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeElrond, accId)); + 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 new file mode 100644 index 00000000000..55060002abd --- /dev/null +++ b/tests/Elrond/TestAccounts.h @@ -0,0 +1,17 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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/EncryptTests.cpp b/tests/EncryptTests.cpp index 5b59d696136..9870f4e2937 100644 --- a/tests/EncryptTests.cpp +++ b/tests/EncryptTests.cpp @@ -8,6 +8,8 @@ #include "Data.h" #include "HexCoding.h" +#include + #include using namespace TW::Encrypt; @@ -20,24 +22,24 @@ inline void assertHexEqual(const Data& data, const char* expected) { } TEST(Encrypt, paddingSize) { - EXPECT_EQ(paddingSize(0, 16, PadWithZeros), 0); - EXPECT_EQ(paddingSize(1, 16, PadWithZeros), 15); - EXPECT_EQ(paddingSize(8, 16, PadWithZeros), 8); - EXPECT_EQ(paddingSize(15, 16, PadWithZeros), 1); - EXPECT_EQ(paddingSize(16, 16, PadWithZeros), 0); - EXPECT_EQ(paddingSize(17, 16, PadWithZeros), 15); - EXPECT_EQ(paddingSize(24, 16, PadWithZeros), 8); - EXPECT_EQ(paddingSize(31, 16, PadWithZeros), 1); - EXPECT_EQ(paddingSize(32, 16, PadWithZeros), 0); - EXPECT_EQ(paddingSize(0, 16, PadWithPaddingSize), 16); - EXPECT_EQ(paddingSize(1, 16, PadWithPaddingSize), 15); - EXPECT_EQ(paddingSize(8, 16, PadWithPaddingSize), 8); - EXPECT_EQ(paddingSize(15, 16, PadWithPaddingSize), 1); - EXPECT_EQ(paddingSize(16, 16, PadWithPaddingSize), 16); - EXPECT_EQ(paddingSize(17, 16, PadWithPaddingSize), 15); - EXPECT_EQ(paddingSize(24, 16, PadWithPaddingSize), 8); - EXPECT_EQ(paddingSize(31, 16, PadWithPaddingSize), 1); - EXPECT_EQ(paddingSize(32, 16, PadWithPaddingSize), 16); + 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); } TEST(Encrypt, AESCBCEncrypt) { @@ -53,13 +55,13 @@ TEST(Encrypt, AESCBCEncryptWithPadding) { const Data message = TW::data("secret message"); { Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - Data encrypted = AESCBCEncrypt(key, message, iv, PadWithPaddingSize); + Data encrypted = AESCBCEncrypt(key, message, iv, TWAESPaddingModePKCS7); assertHexEqual(encrypted, "7f896315e90e172bed65d005138f224d"); } { // with no padding Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - Data encrypted = AESCBCEncrypt(key, message, iv, PadWithZeros); + Data encrypted = AESCBCEncrypt(key, message, iv, TWAESPaddingModeZero); assertHexEqual(encrypted, "11bcbfebb2db19fb5a5cbf458e0f699e"); } } @@ -77,7 +79,7 @@ TEST(Encrypt, AESCBCDecryptWithPadding) { { const Data encryptedPadded = parse_hex("7f896315e90e172bed65d005138f224d"); Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - Data encrypted = AESCBCDecrypt(key, encryptedPadded, iv, PadWithPaddingSize); + Data encrypted = AESCBCDecrypt(key, encryptedPadded, iv, TWAESPaddingModePKCS7); assertHexEqual(encrypted, hex(TW::data("secret message")).c_str()); } { diff --git a/tests/Ethereum/AbiTests.cpp b/tests/Ethereum/AbiTests.cpp index 280425e57c0..f0bfd49a17c 100644 --- a/tests/Ethereum/AbiTests.cpp +++ b/tests/Ethereum/AbiTests.cpp @@ -12,8 +12,6 @@ using namespace TW; using namespace TW::Ethereum::ABI; -///// Util - ///// Parameter types @@ -361,6 +359,8 @@ TEST(EthereumAbi, ParamString) { "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()); @@ -417,10 +417,14 @@ TEST(EthereumAbi, ParamByteArray) { 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()); @@ -441,6 +445,8 @@ TEST(EthereumAbi, ParamByteArrayFix) { 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)); @@ -472,12 +478,16 @@ TEST(EthereumAbi, ParamArrayByte) { 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()); @@ -504,11 +514,15 @@ TEST(EthereumAbi, ParamArrayAddress) { 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.getVal().size()); @@ -518,6 +532,38 @@ TEST(EthereumAbi, ParamArrayAddress) { } } +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) { @@ -538,6 +584,28 @@ TEST(EthereumAbi, EncodeVectorByte) { "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; @@ -646,6 +714,28 @@ TEST(EthereumAbi, DecodeByteArray10) { 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) { @@ -658,6 +748,7 @@ TEST(EthereumAbi, EncodeParamsSimple) { p.encode(encoded); EXPECT_EQ(3 * 32, encoded.size()); + EXPECT_EQ(3 * 32, p.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000010" "0000000000000000000000000000000000000000000000000000000000000011" @@ -682,6 +773,7 @@ TEST(EthereumAbi, EncodeParamsMixed) { p.encode(encoded); EXPECT_EQ(13 * 32, encoded.size()); + EXPECT_EQ(13 * 32, p.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000045" "00000000000000000000000000000000000000000000000000000000000000a0" @@ -972,3 +1064,44 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { 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); +} diff --git a/tests/Ethereum/ContractCallTests.cpp b/tests/Ethereum/ContractCallTests.cpp new file mode 100644 index 00000000000..26006faa46d --- /dev/null +++ b/tests/Ethereum/ContractCallTests.cpp @@ -0,0 +1,169 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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()); +} diff --git a/tests/Ethereum/Data/custom.json b/tests/Ethereum/Data/custom.json new file mode 100644 index 00000000000..711a8c86217 --- /dev/null +++ b/tests/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/Ethereum/Data/ens.json b/tests/Ethereum/Data/ens.json new file mode 100644 index 00000000000..a7c263019c8 --- /dev/null +++ b/tests/Ethereum/Data/ens.json @@ -0,0 +1,53 @@ +{ + "1896f70a": { + "constant": false, + "inputs": [{ + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, { + "internalType": "address", + "name": "resolver", + "type": "address" + }], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "acf1a841": { + "constant": false, + "inputs": [{ + "internalType": "string", + "name": "name", + "type": "string" + }, { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }], + "name": "renew", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + "ac9650d8": { + "constant": false, + "inputs": [{ + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }], + "name": "multicall", + "outputs": [{ + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/erc20.json b/tests/Ethereum/Data/erc20.json new file mode 100644 index 00000000000..b4163266f55 --- /dev/null +++ b/tests/Ethereum/Data/erc20.json @@ -0,0 +1,50 @@ +{ + "095ea7b3": { + "constant": false, + "inputs": [{ + "name": "_spender", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "a9059cbb": { + "constant": false, + "inputs": [{ + "name": "_to", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "23b872dd": { + "constant": false, + "inputs": [{ + "name": "_from", + "type": "address" + }, { + "name": "_to", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/erc721.json b/tests/Ethereum/Data/erc721.json new file mode 100644 index 00000000000..76e96bf86e7 --- /dev/null +++ b/tests/Ethereum/Data/erc721.json @@ -0,0 +1,78 @@ +{ + "095ea7b3": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "23b872dd": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "from", + "type": "address" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "42842e0e": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "from", + "type": "address" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "a22cb465": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "bool", + "name": "approved", + "type": "bool" + }], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/kyber_proxy.json b/tests/Ethereum/Data/kyber_proxy.json new file mode 100644 index 00000000000..cf36edd7c7e --- /dev/null +++ b/tests/Ethereum/Data/kyber_proxy.json @@ -0,0 +1,88 @@ +{ + "cb3c28c7": { + "inputs": [{ + "internalType": "contract IERC20", + "name": "src", + "type": "address" + }, { + "internalType": "uint256", + "name": "srcAmount", + "type": "uint256" + }, { + "internalType": "contract IERC20", + "name": "dest", + "type": "address" + }, { + "internalType": "address payable", + "name": "destAddress", + "type": "address" + }, { + "internalType": "uint256", + "name": "maxDestAmount", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "minConversionRate", + "type": "uint256" + }, { + "internalType": "address payable", + "name": "platformWallet", + "type": "address" + }], + "name": "trade", + "outputs": [{ + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "payable", + "type": "function" + }, + "ae591d54": { + "inputs": [{ + "internalType": "contract IERC20", + "name": "src", + "type": "address" + }, { + "internalType": "uint256", + "name": "srcAmount", + "type": "uint256" + }, { + "internalType": "contract IERC20", + "name": "dest", + "type": "address" + }, { + "internalType": "address payable", + "name": "destAddress", + "type": "address" + }, { + "internalType": "uint256", + "name": "maxDestAmount", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "minConversionRate", + "type": "uint256" + }, { + "internalType": "address payable", + "name": "platformWallet", + "type": "address" + }, { + "internalType": "uint256", + "name": "platformFeeBps", + "type": "uint256" + }, { + "internalType": "bytes", + "name": "hint", + "type": "bytes" + }], + "name": "tradeWithHintAndFee", + "outputs": [{ + "internalType": "uint256", + "name": "destAmount", + "type": "uint256" + }], + "stateMutability": "payable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/uniswap_router_v2.json b/tests/Ethereum/Data/uniswap_router_v2.json new file mode 100644 index 00000000000..3bf37d19b23 --- /dev/null +++ b/tests/Ethereum/Data/uniswap_router_v2.json @@ -0,0 +1,168 @@ +{ + "7ff36ab5": { + "inputs": [{ + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactETHForTokens", + "outputs": [{ + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }], + "stateMutability": "payable", + "type": "function" + }, + "b6f9de95": { + "inputs": [{ + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactETHForTokensSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + "18cbafe5": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForETH", + "outputs": [{ + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }], + "stateMutability": "nonpayable", + "type": "function" + }, + "791ac947": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForETHSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + "38ed1739": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForTokens", + "outputs": [{ + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }], + "stateMutability": "nonpayable", + "type": "function" + }, + "5c11d795": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/RLPTests.cpp b/tests/Ethereum/RLPTests.cpp index 4c65ce9d454..99fdb55505b 100644 --- a/tests/Ethereum/RLPTests.cpp +++ b/tests/Ethereum/RLPTests.cpp @@ -80,3 +80,112 @@ 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, DecodeInvalid) { + + // invalid raw tx + EXPECT_EQ(RLP::decodeRawTransaction(parse_hex("0xc0")).size(), 0); + + // 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/TWAnySignerTests.cpp b/tests/Ethereum/TWAnySignerTests.cpp index 5b7e04299d9..c1a1dace516 100644 --- a/tests/Ethereum/TWAnySignerTests.cpp +++ b/tests/Ethereum/TWAnySignerTests.cpp @@ -58,10 +58,29 @@ TEST(TWAnySignerEthereum, Sign) { input.set_payload(data.data(), data.size()); input.set_private_key(key.data(), key.size()); - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); + std::string expected = "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"; - ASSERT_EQ(hex(output.encoded()), "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"); + { + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + } + + { + // encode test + Data encoded; + ANY_ENCODE(input, TWCoinTypeEthereum); + ASSERT_EQ(hex(encoded), expected); + } + + { + // wrong coin + Data encoded; + ANY_ENCODE(input, TWCoinTypeNano); + ASSERT_EQ(hex(encoded), ""); + } } TEST(TWAnySignerEthereum, SignJSON) { @@ -81,3 +100,18 @@ TEST(TWAnySignerEthereum, PlanNotSupported) { auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), TWCoinTypeEthereum)); EXPECT_EQ(TWDataSize(outputTWData.get()), 0); } + +TEST(TWAnySignerEthereum, Decode) { + auto rawData = DATA("f86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"); + auto decodedData = WRAPD(TWAnySignerDecode(rawData.get(), TWCoinTypeEthereum)); + auto jsonData = TW::data(TWDataBytes(decodedData.get()), TWDataSize(decodedData.get())); + auto json = std::string(jsonData.begin(), jsonData.end()); + + EXPECT_EQ(json, R"({"gas":"0x5208","gasPrice":"0x051f4d5ce9","input":"0x","nonce":"0xa9","r":"0xad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475","s":"0x0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65","to":"0x515778891c99e3d2e7ae489980cb7c77b37b5e76","v":"0x25","value":"0x1b48eb57e000"})"); + + // wrong coin + auto emptyData = WRAPD(TWDataCreateWithSize(0)); + auto empty = WRAPD(TWAnySignerDecode(emptyData.get(), TWCoinTypeNano)); + + EXPECT_EQ(TWDataSize(empty.get()), 0); +} diff --git a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp b/tests/Ethereum/TWEthereumAbiTests.cpp similarity index 78% rename from tests/Ethereum/TWEthereumAbiEncoderTests.cpp rename to tests/Ethereum/TWEthereumAbiTests.cpp index 638c4159e9c..cf8b00bbd2f 100644 --- a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp +++ b/tests/Ethereum/TWEthereumAbiTests.cpp @@ -4,7 +4,7 @@ // 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 @@ -13,13 +13,14 @@ #include "HexCoding.h" #include "uint256.h" +#include "../interface/TWTestUtilities.h" #include #include namespace TW::Ethereum { TEST(TWEthereumAbi, FuncCreateEmpty) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); TWString* type = TWEthereumAbiFunctionGetType(func); @@ -27,19 +28,19 @@ TEST(TWEthereumAbi, FuncCreateEmpty) { EXPECT_EQ("baz()", type2); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, FuncCreate1) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); - int p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); + auto p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); EXPECT_EQ(0, p1index); - int p2index = TWEthereumAbiFunctionAddParamUInt64(func, 9, true); + auto p2index = TWEthereumAbiFunctionAddParamUInt64(func, 9, true); EXPECT_EQ(0, p2index); // check back get value - int p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); + auto p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); EXPECT_EQ(9, p2val2); TWString* type = TWEthereumAbiFunctionGetType(func); @@ -47,22 +48,22 @@ TEST(TWEthereumAbi, FuncCreate1) { EXPECT_EQ("baz(uint64)", type2); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, FuncCreate2) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); TWString* p1valStr = TWStringCreateWithUTF8Bytes("0045"); TWData* p1val = TWDataCreateWithHexString(p1valStr); - int p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val, false); + auto p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val, false); EXPECT_EQ(0, p1index); //TWDataDelete(p1val); TWStringDelete(p1valStr); Data dummy(0); - int p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); + auto p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); EXPECT_EQ(0, p2index); // check back get value @@ -74,16 +75,16 @@ TEST(TWEthereumAbi, FuncCreate2) { EXPECT_EQ("baz(uint256)", std::string(TWStringUTF8Bytes(type))); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, EncodeFuncCase1) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("sam")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("sam")); EXPECT_TRUE(func != nullptr); EXPECT_EQ(0, TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("64617665")), false)); EXPECT_EQ(1, TWEthereumAbiFunctionAddParamBool(func, true, false)); - int paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); + auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); EXPECT_EQ(2, paramArrIdx); EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("01")))); EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("02")))); @@ -92,7 +93,7 @@ TEST(TWEthereumAbi, EncodeFuncCase1) { TWString* type = TWEthereumAbiFunctionGetType(func); EXPECT_EQ("sam(bytes,bool,uint256[])", std::string(TWStringUTF8Bytes(type))); - TWData* encoded = TWEthereumAbiEncoderEncode(func); + TWData* encoded = TWEthereumAbiEncode(func); Data enc2 = data(TWDataBytes(encoded), TWDataSize(encoded)); EXPECT_EQ("a5643bf2" "0000000000000000000000000000000000000000000000000000000000000060" @@ -107,15 +108,15 @@ TEST(TWEthereumAbi, EncodeFuncCase1) { hex(enc2)); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, EncodeFuncCase2) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("f")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("f")); EXPECT_TRUE(func != nullptr); EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt256(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0123")), false)); - int paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); + auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); EXPECT_EQ(1, paramArrIdx); EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x456)); EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x789)); @@ -125,7 +126,7 @@ TEST(TWEthereumAbi, EncodeFuncCase2) { TWString* type = TWEthereumAbiFunctionGetType(func); EXPECT_EQ("f(uint256,uint32[],bytes10,string)", std::string(TWStringUTF8Bytes(type))); - TWData* encoded = TWEthereumAbiEncoderEncode(func); + TWData* encoded = TWEthereumAbiEncode(func); Data enc2 = data(TWDataBytes(encoded), TWDataSize(encoded)); EXPECT_EQ("47b941bf" "0000000000000000000000000000000000000000000000000000000000000123" @@ -140,12 +141,12 @@ TEST(TWEthereumAbi, EncodeFuncCase2) { hex(enc2)); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, EncodeFuncMonster) { // Monster function with all parameters types - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("monster")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("monster")); EXPECT_TRUE(func != nullptr); TWEthereumAbiFunctionAddParamUInt8(func, 1, false); @@ -164,8 +165,8 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { TWEthereumAbiFunctionAddParamString(func, TWStringCreateWithUTF8Bytes("Hello, world!"), false); TWEthereumAbiFunctionAddParamAddress(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")), false); - TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353")), false); - TWEthereumAbiFunctionAddParamBytesFix(func, 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353")), false); + TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435")), false); + TWEthereumAbiFunctionAddParamBytesFix(func, 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435")), false); TWEthereumAbiFunctionAddInArrayParamUInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); TWEthereumAbiFunctionAddInArrayParamUInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), 2); @@ -183,8 +184,8 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { TWEthereumAbiFunctionAddInArrayParamString(func, TWEthereumAbiFunctionAddParamArray(func, false), TWStringCreateWithUTF8Bytes("Hello, world!")); TWEthereumAbiFunctionAddInArrayParamAddress(func, TWEthereumAbiFunctionAddParamArray(func, false), TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077"))); - TWEthereumAbiFunctionAddInArrayParamBytes(func, TWEthereumAbiFunctionAddParamArray(func, false), TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353"))); - TWEthereumAbiFunctionAddInArrayParamBytesFix(func, TWEthereumAbiFunctionAddParamArray(func, false), 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353"))); + TWEthereumAbiFunctionAddInArrayParamBytes(func, TWEthereumAbiFunctionAddParamArray(func, false), TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435"))); + TWEthereumAbiFunctionAddInArrayParamBytesFix(func, TWEthereumAbiFunctionAddParamArray(func, false), 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435"))); // check back out params EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, false)); @@ -199,16 +200,16 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { "uint8[],uint16[],uint32[],uint64[],uint168[],uint256[],int8[],int16[],int32[],int64[],int168[],int256[],bool[],string[],address[],bytes[],bytes5[])", std::string(TWStringUTF8Bytes(type))); - TWData* encoded = TWEthereumAbiEncoderEncode(func); + TWData* encoded = TWEthereumAbiEncode(func); Data enc2 = data(TWDataBytes(encoded), TWDataSize(encoded)); EXPECT_EQ(4 + 76 * 32, enc2.size()); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, DecodeOutputFuncCase1) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("readout")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("readout")); EXPECT_TRUE(func != nullptr); TWEthereumAbiFunctionAddParamAddress(func, @@ -221,17 +222,17 @@ TEST(TWEthereumAbi, DecodeOutputFuncCase1) { // decode auto encoded = TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0000000000000000000000000000000000000000000000000000000000000045")); - EXPECT_EQ(true, TWEthereumAbiEncoderDecodeOutput(func, encoded)); + EXPECT_EQ(true, TWEthereumAbiDecodeOutput(func, encoded)); // new output value EXPECT_EQ(0x45, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, GetParamWrongType) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("func")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("func")); ASSERT_TRUE(func != nullptr); // add parameters EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt8(func, 1, true)); @@ -258,7 +259,28 @@ TEST(TWEthereumAbi, GetParamWrongType) { EXPECT_EQ("", hex(*(static_cast(TWEthereumAbiFunctionGetParamAddress(func, 99, true))))); // delete - TWEthereumAbiEncoderDeleteFunction(func); + 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/TWEthereumAbiValueDecoderTests.cpp b/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp new file mode 100644 index 00000000000..84075126924 --- /dev/null +++ b/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "Data.h" +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" +#include + +using namespace TW; +using namespace std; + +TEST(TWEthereumAbiValue, decodeUInt256) { + const char * expected = "10020405371567"; + auto inputs = vector{ + "091d0eb3e2af", + "0000000000000000000000000000000000000000000000000000091d0eb3e2af", + "0000000000000000000000000000000000000000000000000000091d0eb3e2af0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000091d0eb3e2af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }; + for (auto &input: inputs) { + auto data = WRAPD(TWDataCreateWithHexString(STRING(input.c_str()).get())); + auto result = WRAPS(TWEthereumAbiValueDecodeUInt256(data.get())); + assertStringsEqual(result, expected); + } +} diff --git a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp b/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp index 9b3e7e83cb2..8821f72bcb5 100644 --- a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp +++ b/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include +#include #include "Data.h" #include "HexCoding.h" @@ -19,10 +19,10 @@ const string brownFox = "The quick brown fox jumps over the lazy dog"; const string brownFoxDot = "The quick brown fox jumps over the lazy dog."; const string hello = "hello"; -TEST(TWEthereumAbiValueEncoder, encodeBool) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBool(false)).get())), +TEST(TWEthereumAbiValue, encodeBool) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBool(false)).get())), "0000000000000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBool(true)).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBool(true)).get())), "0000000000000000000000000000000000000000000000000000000000000001"); } @@ -31,41 +31,41 @@ TWData* _Nonnull buildUInt256(const uint256_t& value) { return TWDataCreateWithBytes(data.data(), data.size()); } -TEST(TWEthereumAbiValueEncoder, encodeInt) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt32(69)).get())), +TEST(TWEthereumAbiValue, encodeInt) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt32(69)).get())), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt32(-1)).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt32(-1)).get())), "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeUInt32(69)).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeUInt32(69)).get())), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeUInt256(buildUInt256(uint256_t(69)))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeUInt256(buildUInt256(uint256_t(69)))).get())), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt256(buildUInt256(uint256_t(69)))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt256(buildUInt256(uint256_t(69)))).get())), "0000000000000000000000000000000000000000000000000000000000000045"); // int256(-1) - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt256(buildUInt256(~uint256_t(0)))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt256(buildUInt256(~uint256_t(0)))).get())), "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); } -TEST(TWEthereumAbiValueEncoder, encodeAddress) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeAddress(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), +TEST(TWEthereumAbiValue, encodeAddress) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeAddress(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), "0000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed"); } -TEST(TWEthereumAbiValueEncoder, encodeString) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeString(TWStringCreateWithUTF8Bytes("trustwallet"))).get())), +TEST(TWEthereumAbiValue, encodeString) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeString(TWStringCreateWithUTF8Bytes("trustwallet"))).get())), "31924c4e2bb082322d1efa718bf67c73ca297b481dac9f76ad35670cff0056a3"); } -TEST(TWEthereumAbiValueEncoder, encodeBytes) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("45")))).get())), +TEST(TWEthereumAbiValue, encodeBytes) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("45")))).get())), "4500000000000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed000000000000000000000000"); } -TEST(TWEthereumAbiValueEncoder, encodeBytesDyn) { +TEST(TWEthereumAbiValue, encodeBytesDyn) { std::string valueStr = "trustwallet"; - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBytesDyn(TWDataCreateWithBytes(reinterpret_cast(valueStr.c_str()), valueStr.length()))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBytesDyn(TWDataCreateWithBytes(reinterpret_cast(valueStr.c_str()), valueStr.length()))).get())), "31924c4e2bb082322d1efa718bf67c73ca297b481dac9f76ad35670cff0056a3"); } diff --git a/tests/Ethereum/ValueDecoderTests.cpp b/tests/Ethereum/ValueDecoderTests.cpp new file mode 100644 index 00000000000..9f9f9ecfcc4 --- /dev/null +++ b/tests/Ethereum/ValueDecoderTests.cpp @@ -0,0 +1,27 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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")); +} diff --git a/tests/FIO/AddressTests.cpp b/tests/FIO/AddressTests.cpp index 62d117e99d9..f78e3ae25f2 100644 --- a/tests/FIO/AddressTests.cpp +++ b/tests/FIO/AddressTests.cpp @@ -35,10 +35,9 @@ TEST(FIOAddress, ValidateData) { } TEST(FIOAddress, FromString) { - ASSERT_EQ( - Address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o").string(), - "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o" - ); + Address addr("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + ASSERT_EQ(addr.string(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + ASSERT_EQ(hex(addr.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f343fc54e"); } TEST(FIOAddress, FromStringInvalid) { diff --git a/tests/FIO/SignerTests.cpp b/tests/FIO/SignerTests.cpp index ab452f6a544..cdf1277a11b 100644 --- a/tests/FIO/SignerTests.cpp +++ b/tests/FIO/SignerTests.cpp @@ -4,6 +4,7 @@ // 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" diff --git a/tests/FIO/TWCoinTypeTests.cpp b/tests/FIO/TWCoinTypeTests.cpp index 60c5bf35440..b7bdaa4469a 100644 --- a/tests/FIO/TWCoinTypeTests.cpp +++ b/tests/FIO/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWFIOCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFIO)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFIO, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("f5axfpgffiqz"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFIO, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFIO)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFIO)); @@ -27,8 +27,8 @@ TEST(TWFIOCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFIO)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFIO)); assertStringsEqual(symbol, "FIO"); - assertStringsEqual(txUrl, "https://fio.foundation/?t123"); - assertStringsEqual(accUrl, "https://fio.foundation/?a12"); + 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/FIO/TWFIOAccountTests.cpp new file mode 100644 index 00000000000..3c4764d4801 --- /dev/null +++ b/tests/FIO/TWFIOAccountTests.cpp @@ -0,0 +1,46 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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(TWFIO, Account) { + { + auto string = STRING("FIO6XKiobBGrmPZDaHne47TF25CamjFc3cRe8hs7oAiCN59TbJzes"); + auto account = TWFIOAccountCreateWithString(string.get()); + auto description = WRAPS(TWFIOAccountDescription(account)); + assertStringsEqual(description, "rpxtnpsr3gxd"); + TWFIOAccountDelete(account); + } + + { + auto account = TWFIOAccountCreateWithString(STRING("lhp1ytjibtea").get()); + auto account2 = TWFIOAccountCreateWithString(STRING("wrcjejslfplp").get()); + auto account3 = TWFIOAccountCreateWithString(STRING("rewards@wallet").get()); + auto description = WRAPS(TWFIOAccountDescription(account)); + auto description2 = WRAPS(TWFIOAccountDescription(account2)); + auto description3 = WRAPS(TWFIOAccountDescription(account3)); + assertStringsEqual(description, "lhp1ytjibtea"); + assertStringsEqual(description2, "wrcjejslfplp"); + assertStringsEqual(description3, "rewards@wallet"); + + TWFIOAccountDelete(account); + TWFIOAccountDelete(account2); + TWFIOAccountDelete(account3); + } + + { + auto string = STRING("asdf19s"); + auto string2 = STRING("0x320196ef1b137786be51aa391e78e0a2c756f46b"); + auto account = TWFIOAccountCreateWithString(string.get()); + auto account2 = TWFIOAccountCreateWithString(string2.get()); + + ASSERT_EQ(account, nullptr); + ASSERT_EQ(account2, nullptr); + } +} diff --git a/tests/Filecoin/AddressTests.cpp b/tests/Filecoin/AddressTests.cpp index 2eb28ebd9ee..db657eda357 100644 --- a/tests/Filecoin/AddressTests.cpp +++ b/tests/Filecoin/AddressTests.cpp @@ -13,6 +13,8 @@ using namespace TW; using namespace TW::Filecoin; +// clang-format off + struct address_test { std::string encoded; const char* hex; diff --git a/tests/Filecoin/SignerTests.cpp b/tests/Filecoin/SignerTests.cpp index b20ee5b0d92..30886b6fc47 100644 --- a/tests/Filecoin/SignerTests.cpp +++ b/tests/Filecoin/SignerTests.cpp @@ -36,11 +36,11 @@ TEST(FilecoinSigner, Sign) { Data signature = Signer::sign(privateKey, tx); - ASSERT_EQ( - hex(tx.serialize(signature)), - "828855018ac93840e869c06b79b748a3c504b696bb339a7f5501cf01bf485f61435e6770b52615bf455e043a23" - "6101430017704200024200c80040584201477bcf9a71c9e19af77fbd92677230a9612d927877e439eb4aa30643" - "0a4ffae42251194820df211d00075e88e68c9704a880448532ec0092045a1e00e8f1cbb900"); + ASSERT_EQ(hex(tx.serialize(signature)), + "82890055018ac93840e869c06b79b748a3c504b696bb339a7f5501cf01bf485f" + "61435e6770b52615bf455e043a2361014300177042000218c80040584201c95e" + "32222984251892b06b7da692e38003d6dd146da5f8a0bd67590099fa4a0937e0" + "c075310a35a85c000b598e07920090cbc768231219a11e3ef7e9bed11da000"); } } // namespace TW::Filecoin diff --git a/tests/Filecoin/TWAnySignerTests.cpp b/tests/Filecoin/TWAnySignerTests.cpp index 6df308c8c47..bd063aebc51 100644 --- a/tests/Filecoin/TWAnySignerTests.cpp +++ b/tests/Filecoin/TWAnySignerTests.cpp @@ -42,20 +42,40 @@ TEST(TWAnySignerFilecoin, Sign) { auto outputData = TWAnySignerSign(inputData, TWCoinTypeFilecoin); ASSERT_EQ(hex(TWDataBytes(outputData), TWDataBytes(outputData) + TWDataSize(outputData)), - "0aa4018288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99b" - "b13c577fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac" - "351052600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60" - "befa485903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); + "0aa401" + "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de8" + "6bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e" + "6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040" + "584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa" + "1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922" + "176cc900"); Proto::SigningOutput output; - output.ParseFromArray(TWDataBytes(outputData), TWDataSize(outputData)); + output.ParseFromArray(TWDataBytes(outputData), static_cast(TWDataSize(outputData))); ASSERT_EQ(hex(output.encoded()), - "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c57" - "7fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac351052" - "600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60befa48" - "5903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); + "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de8" + "6bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e" + "6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040" + "584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa" + "1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922" + "176cc900"); TWDataDelete(inputData); TWDataDelete(outputData); } + +TEST(TWAnySignerFilecoin, SignJSON) { + auto json = STRING( + R"({"toAddress":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","nonce":"2","value":"IIasNRBSYAAA","gasPrice":"Ag==","gasLimit":"1000"})"); + auto key = DATA("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeFilecoin)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeFilecoin)); + assertStringsEqual(result, "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de8" + "6bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e" + "6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040" + "584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa" + "1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922" + "176cc900"); +} diff --git a/tests/Filecoin/TWCoinTypeTests.cpp b/tests/Filecoin/TWCoinTypeTests.cpp index c56a4abcf51..750d5d7f199 100644 --- a/tests/Filecoin/TWCoinTypeTests.cpp +++ b/tests/Filecoin/TWCoinTypeTests.cpp @@ -27,8 +27,12 @@ TEST(TWFilecoinCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFilecoin)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFilecoin)); assertStringsEqual(symbol, "FIL"); - assertStringsEqual(txUrl, "https://filscan.io/#/message/detail?cid=bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe"); - assertStringsEqual(accUrl, "https://filscan.io/#/address/detail?address=t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i"); + assertStringsEqual(txUrl, + "https://filscan.io/#/message/" + "detail?cid=bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe"); + assertStringsEqual( + accUrl, + "https://filscan.io/#/address/detail?address=t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i"); assertStringsEqual(id, "filecoin"); assertStringsEqual(name, "Filecoin"); } diff --git a/tests/Filecoin/TransactionTests.cpp b/tests/Filecoin/TransactionTests.cpp index 08224f2a4b7..2c6d42957d4 100644 --- a/tests/Filecoin/TransactionTests.cpp +++ b/tests/Filecoin/TransactionTests.cpp @@ -27,10 +27,10 @@ TEST(FilecoinTransaction, Serialize) { /*gasLimit*/ 50); ASSERT_EQ(hex(tx.message().encoded()), - "8855013d403ac3911e9f806228326fa68619d36a4641d455013d413d4c3fe3d89f99495a48c6046224a7" - "1f0cd71b0000001234567890430003e84200014200320040"); + "890055013d403ac3911e9f806228326fa68619d36a4641d455013d413d4c3fe3" + "d89f99495a48c6046224a71f0cd71b0000001234567890430003e842000118320040"); ASSERT_EQ(hex(tx.cid()), - "0171a0e4022020421f7d6207c9575803d5d96387c399acbf4bdac3026196cb4d423bc8966bb1"); + "0171a0e40220bebd2d8facba996f5e04e7c64e1b0f088c63078263a4587b838ed3573419d743"); } } // namespace TW::Filecoin diff --git a/tests/Groestlcoin/AddressTests.cpp b/tests/Groestlcoin/AddressTests.cpp index 9f0089fb5da..d3910b6c382 100644 --- a/tests/Groestlcoin/AddressTests.cpp +++ b/tests/Groestlcoin/AddressTests.cpp @@ -40,6 +40,6 @@ 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(path)); + const auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); ASSERT_EQ(address, "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); } diff --git a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp index 28d75b48d4c..7e81ec429b1 100644 --- a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -11,6 +11,7 @@ #include "PrivateKey.h" #include "proto/Bitcoin.pb.h" #include "../interface/TWTestUtilities.h" +#include "../Bitcoin/TxComparisonHelper.h" #include #include @@ -43,9 +44,25 @@ TEST(GroestlcoinSigning, SignP2WPKH) { 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"); } @@ -69,9 +86,25 @@ TEST(GroestlcoinSigning, SignP2PKH) { 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"); } @@ -91,7 +124,7 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { EXPECT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "2fc7d70acef142d1f7b5ef2f20b1a9b759797674"); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - auto redeemScript = Script::buildPayToWitnessPubkeyHash(utxoPubkeyHash); + 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()); @@ -106,10 +139,25 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { 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"); } @@ -136,10 +184,6 @@ TEST(GroestlcoinSigning, PlanP2WPKH) { Proto::TransactionPlan plan; ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); - EXPECT_EQ(plan.amount(), 2500); - EXPECT_EQ(plan.available_amount(), 4774); - EXPECT_EQ(plan.fee(), 226); - EXPECT_EQ(plan.change(), 2048); - EXPECT_EQ(plan.utxos_size(), 1); + EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); EXPECT_EQ(plan.branch_id(), ""); } diff --git a/tests/Groestlcoin/TWGroestlcoinTests.cpp b/tests/Groestlcoin/TWGroestlcoinTests.cpp index e0eab5b94d7..f39ea0cfb6f 100644 --- a/tests/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/Groestlcoin/TWGroestlcoinTests.cpp @@ -31,19 +31,19 @@ TEST(Groestlcoin, Address) { } TEST(Groestlcoin, BuildForLegacyAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM").get(), TWCoinTypeGroestlcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM").get(), TWCoinTypeGroestlcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91498af0aaca388a7e1024f505c033626d908e3b54a88ac"); } TEST(Groestlcoin, BuildForSegwitP2SHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P").get(), TWCoinTypeGroestlcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P").get(), TWCoinTypeGroestlcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e487"); } TEST(Groestlcoin, BuildForNativeSegwitAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne").get(), TWCoinTypeGroestlcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne").get(), TWCoinTypeGroestlcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "00147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce"); } @@ -77,8 +77,8 @@ TEST(Groestlcoin, ExtendedKeys) { TEST(Groestlcoin, DeriveFromZpub) { auto zpub = STRING("zpub6qXFnWiY6FdT5BQptrzEhHfm1WpaBTFc6MHzR4KwscXGdt6xCqUtrAEjrHdeEsjaYEwVMgjtTvENQ83yo2fmkYYGjTpJoH7vFWKQJp1bg1X"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/17'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/17'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeGroestlcoin, STRING("m/84'/17'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeGroestlcoin, STRING("m/84'/17'/0'/0/11").get()); auto address4 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey4, TWCoinTypeGroestlcoin)); auto address4String = WRAPS(TWAnyAddressDescription(address4.get())); diff --git a/tests/HDWalletTests.cpp b/tests/HDWalletTests.cpp index 7da132a108e..4cc59d363dd 100644 --- a/tests/HDWalletTests.cpp +++ b/tests/HDWalletTests.cpp @@ -20,7 +20,8 @@ namespace TW { TEST(HDWallet, privateKeyFromXPRV) { const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + 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); @@ -28,9 +29,44 @@ TEST(HDWallet, privateKeyFromXPRV) { 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, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 4)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(mptv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 4)); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto witness = Data{0x00, 0x14}; @@ -49,7 +85,7 @@ TEST(HDWallet, privateKeyFromMptv) { TEST(HDWallet, privateKeyFromZprv) { const std::string zprv = "zprvAdzGEQ44z4WPLNCRpDaup2RumWxLGgR8PQ9UVsSmJigXsHVDaHK1b6qGM2u9PmxB2Gx264ctAz4yRoN3Xwf1HZmKcn6vmjqwsawF4WqQjfd"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(zprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoin, 0, 0, 5)); + 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"); @@ -59,7 +95,7 @@ TEST(HDWallet, privateKeyFromZprv) { TEST(HDWallet, privateKeyFromDGRV) { const std::string dgpv = "dgpv595jAJYGBLanByCJXRzrWBZFVXdNisfuPmKRDquCQcwBbwKbeR21AtkETf4EpjBsfsK3kDZgMqhcuky1B9PrT5nxiEcjghxpUVYviHXuCmc"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(dgpv, DerivationPath(TWPurposeBIP44, TWCoinTypeDogecoin, 0, 0, 1)); + 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)); @@ -69,7 +105,7 @@ TEST(HDWallet, privateKeyFromDGRV) { TEST(HDWallet, privateKeyFromXPRVForDGB) { const std::string xprvForDgb = "xprv9ynLofyuR3uCqCMJADwzBaPnXB53EVe5oLujvPfdvCxae3NzgEpYjZMgcUeS8EUeYfYVLG61ZgPXm9TZWiwBnLVCgd551vCwpXC19hX3mFJ"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprvForDgb, DerivationPath(TWPurposeBIP44, TWCoinTypeDigiByte, 0, 0, 1)); + 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)); diff --git a/tests/HexCodingTests.cpp b/tests/HexCodingTests.cpp new file mode 100644 index 00000000000..3c06029227e --- /dev/null +++ b/tests/HexCodingTests.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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/IoTeX/StakingTests.cpp b/tests/IoTeX/StakingTests.cpp index 96a79d3734f..0668a4a927d 100644 --- a/tests/IoTeX/StakingTests.cpp +++ b/tests/IoTeX/StakingTests.cpp @@ -4,193 +4,334 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "IoTeX/Staking.h" -#include "IoTeX/Signer.h" #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; -static const uint64_t pyggyIndex01 = 1001; -static std::string IOTEX_STAKING_CONTRACT = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; -static const Data IOTEX_STAKING_TEST = TW::data(std::string("this is a test")); -static const Data candidate12 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +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()); -TEST(IoTeXStaking, Stake) { - Data result; - stakingStake(candidate12, pyggyIndex01, true, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "07c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); + auto stake = stakingCreate(candidate, amount, 10000, true, payload); + ASSERT_EQ(hex(stake), "0a29696f313964307033616834673877773964376b63786671383779786537666e723872" + "7074683573686a120331303018904e20012a077061796c6f6164"); } -TEST(IoTeXStaking, Unstake) { - Data result; - stakingUnstake(pyggyIndex01, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "c8fd6ed000000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +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(IoTeXStaking, Withdraw) { - Data result; - stakingWithdraw(pyggyIndex01, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "030ba25d00000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +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(IoTeXStaking, AddStake) { - Data result; - stakingAddStake(pyggyIndex01, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "6e7b301700000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +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(IoTeXStaking, MoveStake) { - Data result; - stakingMoveStake(pyggyIndex01, candidate12, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "d3e41fd200000000000000000000000000000000000000000000000000000000000003e90102030405060708090a0b0c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +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(IoTeXStaking, SignStake) { - 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()); +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); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - // data = "this is a test" here, it could be null (user leaves data empty when signing the tx) - Data stake; - stakingStake(candidate12, pyggyIndex01, true, IOTEX_STAKING_TEST, stake); - staking->set_data(stake.data(), stake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "cc3c8f7a0129455d70457c4be42a8b31d8e1df59594c99041b6b6d091b295b32"); - // build() signs the tx - auto output = signer.build(); - // signed action's serialized bytes - auto encoded = output.encoded(); - ASSERT_EQ(hex(encoded.begin(), encoded.end()), "0a86020801107b18f806220339393962f7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611ac40107c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74686973206973206120746573740000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41a558bc9a4bfba920242ccd4d5c5da363ec534d4dd5eb67f88e9db7aaad5c50ad62dfe298c0e54e311ebba045f48cea1136e42a123a8e6b03d3e6ed82d4ec2b9401"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "41b1f8be5f6b884c06556fba2611716e8e514b507f5a653fc02ac50ba13fbd6c"); + ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c" + "64326e6b7079636333677a611a077061796c6f6164"); } -TEST(IoTeXStaking, SignUnstake) { - 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()); +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); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data unstake; - stakingUnstake(pyggyIndex01, Data{}, unstake); - staking->set_data(unstake.data(), unstake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "784f3d71246dfe897c1cb02da94e8ef1ac2381ac7f25ecfee80eaa78237db95b"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "b93a2874a72ce4eb8a41a20c209cf3fd188671ed8be8239a57960cbed887e962"); + ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c6432" + "6e6b7079636333677a611a077061796c6f6164"); } -TEST(IoTeXStaking, SignWithdraw) { - 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()); +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); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data withdraw; - stakingWithdraw(pyggyIndex01, Data{}, withdraw); - staking->set_data(withdraw.data(), withdraw.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "ff55882624b2a1d6ae2d9fdec5f8a0f13b2f23c8b28c8ba91773b63f49b97fcc"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "2b2657247a72cb262de214b4e793c7a01fa2139fd5d12a46d43c24f87f9e2396"); + ASSERT_EQ(hex(stake), + "0a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a3539" + "7937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a64" + "7472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b637866" + "71383779786537666e7238727074683573686a32077061796c6f6164"); } -TEST(IoTeXStaking, SignAddStake) { - 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()); +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); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data addStake; - stakingAddStake(pyggyIndex01, Data{}, addStake); - staking->set_data(addStake.data(), addStake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "7581e7f779429aa502879581fdc29f87917acfe638069255b6f033c45d7f24fe"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "c71058812a5febe5cdcdaf9499ba0b2c895f88d1acd3203e5097b307c2a5f1d1"); + ASSERT_EQ(hex(stake), "0a04746573741229696f31636c36726c32657635646661393838716d677a6732783468" + "66617a6d7039766e326736366e671a29696f316a757678356730363365753474733833" + "326e756b7034766763776b32676e6335637539617964"); } -TEST(IoTeXStaking, SignMoveStake) { +TEST(TWIoTeXStaking, SignAll) { 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_nonce(0); + input.set_gaslimit(1000000); + input.set_gasprice("10"); + auto keyhex = parse_hex("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"); input.set_privatekey(keyhex.data(), keyhex.size()); + Proto::SigningOutput output; + + { // sign stakecreate + 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"); + input.release_stakecreate(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakeadddeposit + 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"); + input.release_stakeadddeposit(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakeunstake + 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"); + input.release_stakeunstake(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakewithdraw + 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"); + input.release_stakewithdraw(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakerestake + 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"); + input.release_stakerestake(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakechangecandidate + 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"); + input.release_stakechangecandidate(); + output.release_encoded(); + output.release_hash(); + } + { // sign staketransfer + 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"); + input.release_staketransferownership(); + output.release_encoded(); + output.release_hash(); + } + { // sign candidateupdate + 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"); + input.release_candidateupdate(); + output.release_encoded(); + output.release_hash(); + } + { // sign candidateregister + input.set_gasprice("1000"); + auto cbi = input.mutable_candidateregister()->mutable_candidate(); + cbi->set_name("test"); + cbi->set_operatoraddress("io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"); + cbi->set_rewardaddress("io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data moveStake; - stakingMoveStake(pyggyIndex01, candidate12, Data{}, moveStake); - staking->set_data(moveStake.data(), moveStake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "818637b9708ec9e075c7a17f23757cb6895eae6dd3331f7e44129efae6ca9a21"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "33290ded342efaebf795855be73d34cbac149a2415ff9558de10303e6126f30d"); + 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/TWIoTeXStakingTests.cpp b/tests/IoTeX/TWIoTeXStakingTests.cpp deleted file mode 100644 index 502e5dc62f9..00000000000 --- a/tests/IoTeX/TWIoTeXStakingTests.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 "Data.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "IoTeX/Signer.h" -#include "proto/IoTeX.pb.h" -#include "../interface/TWTestUtilities.h" - -#include - -using namespace TW; -using namespace TW::IoTeX; - -static const char *_Nonnull IOTEX_STAKING_CONTRACT = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; -static const char * IOTEX_STAKING_TEST = "this is a test"; - -TEST(TWIoTeXStaking, SignStake) { - 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& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto name = Data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - auto candidate = std::string(name.begin(), name.end()); - auto& stake = *staking.mutable_stake(); - stake.set_candidate(candidate); - stake.set_duration(1001); - stake.set_nondecay(true); - stake.set_data(IOTEX_STAKING_TEST); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "cc3c8f7a0129455d70457c4be42a8b31d8e1df59594c99041b6b6d091b295b32"); - // build() signs the tx - auto output = signer.build(); - // signed action's serialized bytes - auto encoded = output.encoded(); - ASSERT_EQ(hex(encoded.begin(), encoded.end()), "0a86020801107b18f806220339393962f7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611ac40107c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74686973206973206120746573740000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41a558bc9a4bfba920242ccd4d5c5da363ec534d4dd5eb67f88e9db7aaad5c50ad62dfe298c0e54e311ebba045f48cea1136e42a123a8e6b03d3e6ed82d4ec2b9401"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "41b1f8be5f6b884c06556fba2611716e8e514b507f5a653fc02ac50ba13fbd6c"); -} - -TEST(TWIoTeXStaking, SignUnstake) { - 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& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto& unstake = *staking.mutable_unstake(); - unstake.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "784f3d71246dfe897c1cb02da94e8ef1ac2381ac7f25ecfee80eaa78237db95b"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "b93a2874a72ce4eb8a41a20c209cf3fd188671ed8be8239a57960cbed887e962"); -} - -TEST(TWIoTeXStaking, SignWithdraw) { - 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& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto& withdraw = *staking.mutable_withdraw(); - withdraw.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "ff55882624b2a1d6ae2d9fdec5f8a0f13b2f23c8b28c8ba91773b63f49b97fcc"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "2b2657247a72cb262de214b4e793c7a01fa2139fd5d12a46d43c24f87f9e2396"); -} - -TEST(TWIoTeXStaking, SignAddStake) { - 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& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto& add = *staking.mutable_addstake(); - add.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "7581e7f779429aa502879581fdc29f87917acfe638069255b6f033c45d7f24fe"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "c71058812a5febe5cdcdaf9499ba0b2c895f88d1acd3203e5097b307c2a5f1d1"); -} - -TEST(TWIoTeXStaking, SignMoveStake) { - 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& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto name = Data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - auto candidate = std::string(name.begin(), name.end()); - auto& move = *staking.mutable_movestake(); - move.set_candidate(candidate); - move.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "818637b9708ec9e075c7a17f23757cb6895eae6dd3331f7e44129efae6ca9a21"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "33290ded342efaebf795855be73d34cbac149a2415ff9558de10303e6126f30d"); -} diff --git a/tests/Keystore/Data/ethereum-wallet-address-no-0x.json b/tests/Keystore/Data/ethereum-wallet-address-no-0x.json index 87f0f870863..43cc9c51a9a 100644 --- a/tests/Keystore/Data/ethereum-wallet-address-no-0x.json +++ b/tests/Keystore/Data/ethereum-wallet-address-no-0x.json @@ -1 +1,25 @@ -{"activeAccounts":[{"address":"Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309","derivationPath":"m/44'/60'/0'/0/0"}],"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"630d0a62bc8a6187c47fdb6c8b7bd38c"},"ciphertext":"4ec6d0157bde53900020d45c9db9235acdb366f727fe456b5a4536f20ea0848c","kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"4c3d38c0d114cb92b2cb4a44e92e27cf588f9ecd59840c8dea087e89d350d836"},"mac":"9748b96453a03bcdec2e714153d8d69f16cde7d398750932281f68844a6b2616"},"id":"48aa6b37-8276-44fe-aa4e-819145771183","type":"private-key","version":3} \ No newline at end of file +{ + "activeAccounts": [{ + "address": "Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", + "derivationPath": "m/44'/60'/0'/0/0" + }], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "630d0a62bc8a6187c47fdb6c8b7bd38c" + }, + "ciphertext": "4ec6d0157bde53900020d45c9db9235acdb366f727fe456b5a4536f20ea0848c", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "4c3d38c0d114cb92b2cb4a44e92e27cf588f9ecd59840c8dea087e89d350d836" + }, + "mac": "9748b96453a03bcdec2e714153d8d69f16cde7d398750932281f68844a6b2616" + }, + "id": "48aa6b37-8276-44fe-aa4e-819145771183", + "type": "private-key", + "version": 3 +} \ No newline at end of file diff --git a/tests/Keystore/Data/myetherwallet.uu b/tests/Keystore/Data/myetherwallet.uu index 10e2dab212d..751f16207e5 100755 --- a/tests/Keystore/Data/myetherwallet.uu +++ b/tests/Keystore/Data/myetherwallet.uu @@ -1 +1,21 @@ -{"version":3,"id":"beb60dc6-7553-4215-9aa7-4b26883da373","address":"8562fcccbae3019f5a716997609b301ac31fe04a","Crypto":{"ciphertext":"ed9de8b5568bac97c8199b82b197b61977440d360acec2ecf4f74f3da91308ab","cipherparams":{"iv":"cf6730826d91ce908aa15771952ab3e8"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"2904ecde7e2e5c6cb89291b2741bdd3dff3088f98a7013d63084c746efd7524f","n":1024,"r":8,"p":1},"mac":"cdb237916f98742d4e8f313638e16b04a4a780e69dd61c6407932be42d269c95"}} \ No newline at end of file +{ + "version": 3, + "id": "beb60dc6-7553-4215-9aa7-4b26883da373", + "address": "8562fcccbae3019f5a716997609b301ac31fe04a", + "Crypto": { + "ciphertext": "ed9de8b5568bac97c8199b82b197b61977440d360acec2ecf4f74f3da91308ab", + "cipherparams": { + "iv": "cf6730826d91ce908aa15771952ab3e8" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "2904ecde7e2e5c6cb89291b2741bdd3dff3088f98a7013d63084c746efd7524f", + "n": 1024, + "r": 8, + "p": 1 + }, + "mac": "cdb237916f98742d4e8f313638e16b04a4a780e69dd61c6407932be42d269c95" + } +} \ No newline at end of file diff --git a/tests/Keystore/Data/web3j.json b/tests/Keystore/Data/web3j.json new file mode 100644 index 00000000000..b47dc6a2d51 --- /dev/null +++ b/tests/Keystore/Data/web3j.json @@ -0,0 +1,21 @@ +{ + "address": "34bae2218c254ed190c0f5b1dd4323aee8e7da09", + "id": "86066d8c-8dba-4d81-afd4-934e2a2b72a2", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "a4976ad73057007ad788d1f792db851d" + }, + "ciphertext": "5e4458d69964172c492616b751d6589b4ad7da4217dcfccecc3f4e515a934bb8", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "24c72d92bf88a4f7c7b3f5e3cb3620714d71fceabbb0bc6099f50c6d5d898e7c" + }, + "mac": "c15e3035ddcaca766dfc56648978d33e94d3c57d4a5e13fcf8b5f8dbb0902900" + } +} \ No newline at end of file diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp index d5648c661e8..858eb492df1 100644 --- a/tests/Keystore/StoredKeyTests.cpp +++ b/tests/Keystore/StoredKeyTests.cpp @@ -8,6 +8,7 @@ #include "Coin.h" #include "HexCoding.h" +#include "Data.h" #include "PrivateKey.h" #include @@ -19,9 +20,14 @@ namespace TW::Keystore { using namespace std; -const auto password = "password"; +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); @@ -63,7 +69,7 @@ TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { 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].coin, coinTypeBc); EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); } @@ -73,7 +79,7 @@ TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { 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].coin, coinTypeBc); EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), hex(privateKey)); @@ -110,25 +116,25 @@ TEST(StoredKey, AccountGetCreate) { // not exists, wallet nonnull, create const Account* acc3 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc3 != nullptr); - EXPECT_EQ(acc3->coin(), coinTypeBc); + EXPECT_EQ(acc3->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); // exists const Account* acc4 = key.account(coinTypeBc); EXPECT_TRUE(acc4 != nullptr); - EXPECT_EQ(acc4->coin(), coinTypeBc); + EXPECT_EQ(acc4->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); // exists, wallet nonnull, not create const Account* acc5 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc5 != nullptr); - EXPECT_EQ(acc5->coin(), coinTypeBc); + EXPECT_EQ(acc5->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); // exists, wallet null, not create const Account* acc6 = key.account(coinTypeBc, nullptr); EXPECT_TRUE(acc6 != nullptr); - EXPECT_EQ(acc6->coin(), coinTypeBc); + EXPECT_EQ(acc6->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); } @@ -136,11 +142,29 @@ 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("bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr", derivationPath, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - EXPECT_EQ(key.accounts.size(), 1); + { + 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); } @@ -175,15 +199,15 @@ TEST(StoredKey, LoadNonexistent) { 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("testpassword")), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + 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("Radchenko")), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); } TEST(StoredKey, LoadPBKDF2Key) { @@ -196,7 +220,7 @@ TEST(StoredKey, LoadPBKDF2Key) { EXPECT_EQ(boost::get(payload.kdfParams).iterations, 262144); EXPECT_EQ(hex(boost::get(payload.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); - EXPECT_EQ(hex(payload.decrypt("testpassword")), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } TEST(StoredKey, LoadLegacyMnemonic) { @@ -207,15 +231,23 @@ TEST(StoredKey, LoadLegacyMnemonic) { 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].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].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"); @@ -250,12 +282,12 @@ TEST(StoredKey, InvalidPassword) { TEST(StoredKey, EmptyAccounts) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/empty-accounts.json"); - ASSERT_NO_THROW(key.payload.decrypt("testpassword")); + 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("testpassword"); + const auto privateKey = key.payload.decrypt(TW::data("testpassword")); EXPECT_EQ(hex(privateKey), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } @@ -297,7 +329,7 @@ TEST(StoredKey, RemoveAccount) { EXPECT_EQ(key.accounts.size(), 2); key.removeAccount(TWCoinTypeEthereum); EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin(), coinTypeBc); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); } TEST(StoredKey, MissingAddress) { @@ -310,7 +342,7 @@ TEST(StoredKey, MissingAddress) { TEST(StoredKey, EtherWalletAddressNo0x) { auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/ethereum-wallet-address-no-0x.json"); - key.fixAddresses("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53"); + key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); } diff --git a/tests/Kusama/SignerTests.cpp b/tests/Kusama/SignerTests.cpp index d0f52b2bfaf..a4f3c72b3fa 100644 --- a/tests/Kusama/SignerTests.cpp +++ b/tests/Kusama/SignerTests.cpp @@ -5,205 +5,48 @@ // 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 -using namespace TW; -using namespace TW::Polkadot; -TEST(PolkadotSigner, SignTransfer) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); +namespace TW::Polkadot { + extern PrivateKey privateKey; + extern PublicKey toPublicKey; + auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - 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 output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -TEST(PolkadotSigner, SignTransferEra) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto blockHash = parse_hex("0xee17a6af2ea3f8c230e184e6821f7dfd56b60b9ca46b06e713acd9c8c834a5ee"); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); +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(genesisHash.data(), genesisHash.size()); + input.set_genesis_hash(genesisHashKSM.data(), genesisHashKSM.size()); input.set_nonce(0); - input.set_spec_version(1031); + input.set_spec_version(2019); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto &era = *input.mutable_era(); - era.set_phase(429119); - era.set_period(8); + 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_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(output.encoded()), "310284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee004898684cbde8f04384e381636326f5a7334dfa9617e66771eedb2b8cb1d3b9415531305ad70e4e38accd06ef0fbe5963d928b59979f1807500170981af335306720000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -TEST(PolkadotSigner, SignNominate) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &nominate = *stakingCall->mutable_nominate(); - nominate.add_nominators("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); - nominate.add_nominators("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "ad0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00855fe8ba83a200c7b363fe7c9f315cc5578e25745a4131584713c3642644ed43ae503a2fe10bcc6a4d5eb6ebc96d5f17e4035f3658e8d6de8998f68a66a3df01000000060508ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); -} - -TEST(PolkadotSigner, SignNominate2) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &nominate = *stakingCall->mutable_nominate(); - // payload size larger than 256, will be hashed - nominate.add_nominators("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); - nominate.add_nominators("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); - nominate.add_nominators("HJ1dGPxVr13KHGiCTGQfZjZMKGc8J52CsHEjcEXNMDyeGxf"); - nominate.add_nominators("F4LocUbsPrcC8xVap4wiTgDakzn3xFyXneuYDHRaHxnb6dH"); - nominate.add_nominators("DriCrAgdVV57NeQm5bWn5KQpVndVnXnm55BjRpe6qzZ5ktJ"); - nominate.add_nominators("GiBnzCGFofhmAvsUv9FUShUb8YJYYwWex3ThQNkbDDNprS6"); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "bd0484ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee006d071e9e29e9108f07b9127a6a71f5e2b44b8c30452878aeb4d0fe12f7f0c43bca5e7eca7e199e1287ab26422903069ce14afead5fb3878f9f036578c369a80e000000060518ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72ffd0bd030f88d88e9746e7e684a210d0e1b8b7db04d6a3dad1da047e7200c21e10ff6dd7ab69a1bccd2dbb782487f801509bf3aed97db9492b99f0d4e7ad08896e1cff38bc883d8fb7ae97eb3a7862cff1252172f4a901264c84b7feee0a900a64f77dffb6f0e5386adc413a67e628050d4ca4fc8ee2f170ed959465feff17bd88b7cf3c"); + ASSERT_EQ(hex(preimage), "04008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0000000e307000002000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); } -TEST(PolkadotSigner, SignChill) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "a10184ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee004c4801e3edba279dbb6e017f4c5fc9fc263a51a5d1e2f59e44e0f709541522d3c3cdd3d7ed1225913901e177ac4161df846632ddb7bad1f5b504612fa1ee620b0000000606"); -} - -TEST(PolkadotSigner, SignWithdraw) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto __attribute__((unused)) &withdraw = *stakingCall->mutable_withdraw_unbonded(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "a10184ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00c595893548338fa3f90f1cf1e9b650f357925e29d26148ae732d6307fd630d56639448786841b36d7ff13023a633891c47d2cf0e79f03a26105f369a69f8c2010000000603"); -} - -TEST(PolkadotSigner, SignUnbond) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &unbond = *stakingCall->mutable_unbond(); - - auto value = store(uint256_t(123456)); - unbond.set_value(value.data(), value.size()); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "b10184ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee005e29b1763a6287af1daa0487097850040038b8517df8cd47c57d8f9e8cb203872ff9f755c7b69ddefee98a0ad0dfbbcde757f33e4cba67331b323f8816af100a000000060202890700"); -} - -TEST(PolkadotSigner, SignBond) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &bond = *stakingCall->mutable_bond(); - - auto value = store(uint256_t(123456)); - - bond.set_validator("FfmSiZNJP72xtSaXiP2iUhBwWeMEvmjPrxY2ViVkWaeChDC"); - bond.set_value(value.data(), value.size()); - bond.set_reward_destination(Proto::RewardDestination::STAKED); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "390284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee006589694e6696e2173c0c4a1a0353267410546fb8f0970d037a6d079719c0e440a1af26bafae266fb383b41936682d74b68bb4018f7565f27fb7a6646a5c7cb0d0000000600ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0289070000"); -} +} // namespace diff --git a/tests/Kusama/TWAnySignerTests.cpp b/tests/Kusama/TWAnySignerTests.cpp index 726e860a289..5e4a0584079 100644 --- a/tests/Kusama/TWAnySignerTests.cpp +++ b/tests/Kusama/TWAnySignerTests.cpp @@ -16,26 +16,26 @@ using namespace TW; using namespace TW::Polkadot; TEST(TWAnySignerKusama, Sign) { - auto key = parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"); + 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(0); - input.set_spec_version(1031); + 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_extrinsic_version(4); + 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"); + 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()), "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); + ASSERT_EQ(hex(output.encoded()), "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402"); } diff --git a/tests/Litecoin/TWLitecoinTests.cpp b/tests/Litecoin/TWLitecoinTests.cpp index 963e246378f..a66f73ef058 100644 --- a/tests/Litecoin/TWLitecoinTests.cpp +++ b/tests/Litecoin/TWLitecoinTests.cpp @@ -32,14 +32,14 @@ TEST(Litecoin, Address) { assertStringsEqual(string, "ltc1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asjnaxwu"); } -TEST(Litecoin, BuildForAddressL) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); +TEST(Litecoin, LockScriptForAddressL) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } -TEST(Litecoin, BuildForAddressM) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); +TEST(Litecoin, LockScriptForAddressM) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); } @@ -73,8 +73,8 @@ TEST(Litecoin, ExtendedKeys) { TEST(Litecoin, DeriveFromZpub) { auto zpub = STRING("zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeLitecoin, STRING("m/44'/2'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeLitecoin, STRING("m/44'/2'/0'/0/11").get()); auto address4 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey4, TWCoinTypeLitecoin)); auto address4String = WRAPS(TWAnyAddressDescription(address4.get())); @@ -87,15 +87,15 @@ TEST(Litecoin, DeriveFromZpub) { } TEST(Litecoin, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("ltc1qs32zgdhe2tpzcnz55r7d9jvhce33063sfht3q0").get(), TWCoinTypeLitecoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("ltc1qs32zgdhe2tpzcnz55r7d9jvhce33063sfht3q0").get(), TWCoinTypeLitecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "001484542436f952c22c4c54a0fcd2c997c66317ea30"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } diff --git a/tests/Monacoin/TWMonacoinAddressTests.cpp b/tests/Monacoin/TWMonacoinAddressTests.cpp index 6b99f74dea1..b1a922a1d8e 100644 --- a/tests/Monacoin/TWMonacoinAddressTests.cpp +++ b/tests/Monacoin/TWMonacoinAddressTests.cpp @@ -34,19 +34,19 @@ TEST(Monacoin, Address) { } TEST(Monacoin, BuildForP2PKHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MFMy9FwJsV6HiN5eZDqDETw4pw52q3UGrb").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MFMy9FwJsV6HiN5eZDqDETw4pw52q3UGrb").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91451dadacc7021440cbe4ca148a5db563b329b4c0388ac"); } TEST(Monacoin, BuildForP2SHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("PHjTKtgYLTJ9D2Bzw2f6xBB41KBm2HeGfg").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("PHjTKtgYLTJ9D2Bzw2f6xBB41KBm2HeGfg").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146449f568c9cd2378138f2636e1567112a184a9e887"); } TEST(Monacoin, BuildForBech32Address) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("mona1q4kpn6psthgd5ur894auhjj2g02wlgmp8ke08ne").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("mona1q4kpn6psthgd5ur894auhjj2g02wlgmp8ke08ne").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014ad833d060bba1b4e0ce5af797949487a9df46c27"); } @@ -80,8 +80,8 @@ TEST(Monacoin, ExtendedKeys) { TEST(Monacoin, DeriveFromXpub) { auto xpub = STRING("xpub6CYWFE1BgTCW2vtbDm1RRT81i3hBkQrXCfGs5hYp211fpgLZV5xCEwXMWPAL3LgaBA9koXpLZSUo7rTyJ8q1JwqKhvzVpdzBKRGyyGb31KF"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/22'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/22'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeMonacoin, STRING("m/44'/22'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeMonacoin, STRING("m/44'/22'/0'/0/9").get()); auto address2 = TWBitcoinAddressCreateWithPublicKey(pubKey2, TWCoinTypeP2pkhPrefix(TWCoinTypeMonacoin)); auto address2String = WRAPS(TWBitcoinAddressDescription(address2)); @@ -94,15 +94,15 @@ TEST(Monacoin, DeriveFromXpub) { } TEST(Monacoin, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("mona1qw508d6qejxtdg4y5r3zarvary0c5xw7kg5lnx5").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("mona1qw508d6qejxtdg4y5r3zarvary0c5xw7kg5lnx5").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014751e76e8199196d454941c45d1b3a323f1433bd6"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("PCTzdjWauNipkYtToRZEHDMXb2adj9Evp8").get(), TWCoinTypeMonacoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("PCTzdjWauNipkYtToRZEHDMXb2adj9Evp8").get(), TWCoinTypeMonacoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MBamfEqEFDy5dsLWwu48BCizM1zpCoKw3U").get(), TWCoinTypeMonacoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MBamfEqEFDy5dsLWwu48BCizM1zpCoKw3U").get(), TWCoinTypeMonacoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac"); } diff --git a/tests/Monacoin/TWMonacoinTransactionTests.cpp b/tests/Monacoin/TWMonacoinTransactionTests.cpp index df153f0d713..a70196cc3f6 100644 --- a/tests/Monacoin/TWMonacoinTransactionTests.cpp +++ b/tests/Monacoin/TWMonacoinTransactionTests.cpp @@ -75,21 +75,21 @@ TEST(MonacoinTransaction, LockScripts) { // P2PKH // https://blockbook.electrum-mona.org/tx/79ebdce15e4ac933328e62dbe92302fc8b4833786e46df8a4f18295cb824fb67 - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("M8aShwteMWyAbUw4SGS4EHLqfo1EfnKHcM").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("M8aShwteMWyAbUw4SGS4EHLqfo1EfnKHcM").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914076df984229a2731cbf465ec8fbd35b8da94380f88ac"); // P2SH // https://blockbook.electrum-mona.org/tx/726ae7d5179bfd8c7d51a5b956c3d6a262fe5190c36ed7bcb3799dc5759d5830 - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("P91UYtoBS4XAD39fEzaeMaq7YmMa42FFNd").get(), TWCoinTypeMonacoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("P91UYtoBS4XAD39fEzaeMaq7YmMa42FFNd").get(), TWCoinTypeMonacoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914049880fc73bb6a5e0140404713cabe2592fb2c5587"); // BECH32 // https://blockbook.electrum-mona.org/tx/6d7ebe444cc12c14625fa526ed9d81058b04d2f0c3b5dad2fb0032eeec3ba511 - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("mona1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asju3qmd").get(), TWCoinTypeMonacoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("mona1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asju3qmd").get(), TWCoinTypeMonacoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "001422e6014ad3631f1939281c3625bc98db808fbfb0"); } diff --git a/tests/NEAR/TWCoinTypeTests.cpp b/tests/NEAR/TWCoinTypeTests.cpp index 2b996be3195..cb8d63d85fa 100644 --- a/tests/NEAR/TWCoinTypeTests.cpp +++ b/tests/NEAR/TWCoinTypeTests.cpp @@ -15,20 +15,20 @@ TEST(TWNEARCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNEAR)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNEAR, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("test-trust.vlad.near"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNEAR, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNEAR)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNEAR)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEAR), 18); + 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.nearprotocol.com/transactions/t123"); - assertStringsEqual(accUrl, "https://explorer.nearprotocol.com/accounts/a12"); + 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/Polkadot/ScaleCodecTests.cpp b/tests/Polkadot/ScaleCodecTests.cpp index e5636cbaa01..13231d58465 100644 --- a/tests/Polkadot/ScaleCodecTests.cpp +++ b/tests/Polkadot/ScaleCodecTests.cpp @@ -56,7 +56,7 @@ TEST(PolkadotCodec, EncodeAddress) { auto address = Kusama::Address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); auto encoded = encodeAddress(address); - ASSERT_EQ(hex(encoded), "ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"); + ASSERT_EQ(hex(encoded), "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"); } TEST(PolkadotCodec, EncodeVectorAddresses) { @@ -65,7 +65,7 @@ TEST(PolkadotCodec, EncodeVectorAddresses) { Kusama::Address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY") }; auto encoded = encodeAddresses(addresses); - ASSERT_EQ(hex(encoded), "08ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); + ASSERT_EQ(hex(encoded), "088eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); } TEST(PolkadotCodec, EncodeEra) { diff --git a/tests/Polkadot/SignerTests.cpp b/tests/Polkadot/SignerTests.cpp new file mode 100644 index 00000000000..a43156321de --- /dev/null +++ b/tests/Polkadot/SignerTests.cpp @@ -0,0 +1,195 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 { + auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); + auto toPublicKey = PublicKey(parse_hex("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519); + auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + +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_phase(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, SignNominate) { + auto blockHash = parse_hex("52bc855411f95698cb987dc9af93f719f2e06e87bfa7b75a83d58baafd08bca7"); + + 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(); + + nominate.add_nominators("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + nominate.add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "a1028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0038c999e3878beb7a927ff63483390f6e237120612678d5cc9c40c27028250d94c9a894cf8befe8444ff4b69a0b64c2220aa7c30f886f54e0589f73257f59ed0f0000000705082c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f18127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a"); +} + +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) { + auto blockHash = parse_hex("84c9509904b5b2b3ff0a35fc7f033db9306e6bf0700c32d44e049578bfd33ba1"); + 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 &unbond = *stakingCall->mutable_unbond(); + + auto value = store(uint256_t(123456)); + unbond.set_value(value.data(), value.size()); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "ad018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00cababd023dd35e6639b5a55fb35e5863142e15637d2942c7de58ab966a6d81c5794e9a2fa53b1da7c5f9ed272e52f865fb9ccf1ccf5881bbee7af357101bf90c000000070202890700"); +} + +TEST(PolkadotSigner, SignBond) { + auto blockHash = parse_hex("94f4552ef9412a85d8728717f09128bb21548a2e5eec2e7e5cefbe72af17aad7"); + 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 &bond = *stakingCall->mutable_bond(); + + auto value = store(uint256_t(123456)); + + bond.set_controller("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + bond.set_value(value.data(), value.size()); + bond.set_reward_destination(Proto::RewardDestination::STAKED); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "31028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0096fde35692962591e8f41782c5bbe4c3c01022bcfc140a0b11f00502997817263fcaf476477e0db6abbc1d3c9a231aed8ba0ee6d5105db08ac2a3dd68d3c870000000007002c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f180289070000"); +} + +} // namespace diff --git a/tests/Polkadot/TWCoinTypeTests.cpp b/tests/Polkadot/TWCoinTypeTests.cpp index 43b87491c5f..1799dc2b26f 100644 --- a/tests/Polkadot/TWCoinTypeTests.cpp +++ b/tests/Polkadot/TWCoinTypeTests.cpp @@ -16,20 +16,20 @@ TEST(TWPolkadotCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolkadot)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolkadot, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePolkadot, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePolkadot)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePolkadot)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolkadot), 15); + 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/t123"); - assertStringsEqual(accUrl, "https://polkadot.subscan.io/account/a12"); + 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 index 3a40e922994..9a640218cd6 100644 --- a/tests/PrivateKeyTests.cpp +++ b/tests/PrivateKeyTests.cpp @@ -55,6 +55,43 @@ TEST(PrivateKey, InvalidSECP256k1) { } } +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)); @@ -65,26 +102,33 @@ TEST(PrivateKey, PublicKey) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(privKeyData); { - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ( "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", hex(publicKey.bytes) ); } { - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ( "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", hex(publicKey.bytes) ); } { - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + 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, ClearMemory) { @@ -151,7 +195,6 @@ TEST(PrivateKey, Sign) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(privKeyData); Data messageData = TW::data("hello"); - Data result(32); Data hash = Hash::keccak256(messageData); Data actual = privateKey.sign(hash, TWCurveSECP256k1); @@ -160,3 +203,35 @@ TEST(PrivateKey, Sign) { 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); +} diff --git a/tests/PublicKeyTests.cpp b/tests/PublicKeyTests.cpp index dba84ba4392..571b4319b8d 100644 --- a/tests/PublicKeyTests.cpp +++ b/tests/PublicKeyTests.cpp @@ -41,6 +41,20 @@ TEST(PublicKeyTests, CreateInvalid) { 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); @@ -65,36 +79,162 @@ TEST(PublicKeyTests, CompressedExtended) { 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, Verify) { +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")); - const char* message = "Hello"; - const Data messageData = TW::data(message); - const Data digest = Hash::keccak256(messageData); + 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); +} - auto signature = privateKey.sign(digest, TWCurveSECP256k1); +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 publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.verify(signature, digest)); + 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, VerifyEd25519) { +TEST(PublicKeyTests, Verify) { const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - auto privateKey = PrivateKey(key); + const auto privateKey = PrivateKey(key); const char* message = "Hello"; const Data messageData = TW::data(message); const Data digest = Hash::sha256(messageData); - auto signature = privateKey.sign(digest, TWCurveED25519); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + { + 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); - auto signature2 = privateKey.sign(digest, TWCurveED25519Blake2bNano); - auto publicKey2 = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + 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)); +} - EXPECT_TRUE(publicKey.verify(signature, digest)); - EXPECT_TRUE(publicKey2.verify(signature2, 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"); } diff --git a/tests/Qtum/TWQtumAddressTests.cpp b/tests/Qtum/TWQtumAddressTests.cpp index 8f620b3fe70..846613a6729 100644 --- a/tests/Qtum/TWQtumAddressTests.cpp +++ b/tests/Qtum/TWQtumAddressTests.cpp @@ -34,15 +34,15 @@ TEST(Qtum, Address) { } TEST(Qtum, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeQtum)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeQtum)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("QYJHEEt8kS8TzUuCy1ia7aYe1cpNg4QYnn").get(), TWCoinTypeQtum)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("QYJHEEt8kS8TzUuCy1ia7aYe1cpNg4QYnn").get(), TWCoinTypeQtum)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a91480485018e46a9c8176282adf0acb4ff3e0de93ff88ac"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("qc1qxssrzt03ncm0uda02vd8tuvrk0eg9wrz8qm2qe").get(), TWCoinTypeQtum)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("qc1qxssrzt03ncm0uda02vd8tuvrk0eg9wrz8qm2qe").get(), TWCoinTypeQtum)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "00143420312df19e36fe37af531a75f183b3f282b862"); } @@ -76,8 +76,8 @@ TEST(Qtum, ExtendedKeys) { TEST(Qtum, DeriveFromXpub) { auto xpub = STRING("xpub6CAkJZPecMDxRXEXZpDwyxcQ6CGie8GdovuJhsGwc2gFbLxdGr1PyqBXmsL7aYds1wfY2rB3YMVZiEE3CB3Lkj6KGoq1rEJ1wuaGkMDBf1m"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/2301'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/2301'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/9").get()); auto address2 = TWBitcoinAddressCreateWithPublicKey(pubKey2, TWCoinTypeP2pkhPrefix(TWCoinTypeQtum)); auto address2String = WRAPS(TWBitcoinAddressDescription(address2)); @@ -91,8 +91,8 @@ TEST(Qtum, DeriveFromXpub) { TEST(Qtum, DeriveFromZpub) { auto zpub = STRING("zpub6rJJqJZcpaC7DrdsYiprLfUfvtaf11ZZWmrmYeWMkdZTx6tgfQLiBZuisraogskwBRLMGWfXoCyWRrXSypwPdNV2UWJXm5bDVQvBXvrzz9d"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2301'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2301'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/11").get()); auto address4 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPQtum, pubKey4)); auto address4String = WRAPS(TWSegwitAddressDescription(address4.get())); diff --git a/tests/Ravencoin/TWRavencoinTransactionTests.cpp b/tests/Ravencoin/TWRavencoinTransactionTests.cpp index 33516634dd5..8a62bcb5abf 100644 --- a/tests/Ravencoin/TWRavencoinTransactionTests.cpp +++ b/tests/Ravencoin/TWRavencoinTransactionTests.cpp @@ -73,7 +73,7 @@ TEST(RavencoinTransaction, SignTransaction) { ASSERT_EQ(fee, signer.plan.fee); Data serialized; - signedTx.encode(false, serialized); + signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000" @@ -84,14 +84,14 @@ TEST(RavencoinTransaction, LockScripts) { // P2PKH // https://blockbook.ravencoin.org/tx/3717b528eb4925461d9de5a596d2eefe175985740b4fda153255e10135f236a6 - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88").get(), TWCoinTypeRavencoin)); + 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 - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get(), TWCoinTypeRavencoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get(), TWCoinTypeRavencoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914bd92088bb7e82d611a9b94fbb74a0908152b784f87"); } diff --git a/tests/Solana/SignerTests.cpp b/tests/Solana/SignerTests.cpp index 10a83837729..0ad4500ecae 100644 --- a/tests/Solana/SignerTests.cpp +++ b/tests/Solana/SignerTests.cpp @@ -54,6 +54,35 @@ TEST(SolanaSigner, SingleSignTransaction) { } } +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")); diff --git a/tests/Solana/TWAnySignerTests.cpp b/tests/Solana/TWAnySignerTests.cpp index c8a31c3230b..3fd6df3afde 100644 --- a/tests/Solana/TWAnySignerTests.cpp +++ b/tests/Solana/TWAnySignerTests.cpp @@ -36,6 +36,26 @@ TEST(TWAnySignerSolana, SignTransfer) { 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(); diff --git a/tests/Solana/TWSolanaAddressTests.cpp b/tests/Solana/TWSolanaAddressTests.cpp index 5af3bfa161f..bb4b600cac9 100644 --- a/tests/Solana/TWSolanaAddressTests.cpp +++ b/tests/Solana/TWSolanaAddressTests.cpp @@ -19,7 +19,7 @@ TEST(TWSolanaAddress, HDWallet) { auto wallet = WRAP( TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeDerivationPath(TWCoinTypeSolana)); + auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeSolana, TWCoinTypeDerivationPath(TWCoinTypeSolana)); auto publicKey = TWPrivateKeyGetPublicKeyEd25519(privateKey); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeSolana)); auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/Solana/TransactionTests.cpp b/tests/Solana/TransactionTests.cpp index 635e87f32a3..428918be2c9 100644 --- a/tests/Solana/TransactionTests.cpp +++ b/tests/Solana/TransactionTests.cpp @@ -48,6 +48,23 @@ TEST(SolanaTransaction, TransferSerializeTransaction) { 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"); diff --git a/tests/Stellar/TWCoinTypeTests.cpp b/tests/Stellar/TWCoinTypeTests.cpp index a93bbfdbefa..52397d75d8a 100644 --- a/tests/Stellar/TWCoinTypeTests.cpp +++ b/tests/Stellar/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWStellarCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeStellar)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("d9aeabfa9d24df8c5755125f8af243b74cd3ff878656cfa72c566a8824bf6e84"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeStellar, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeStellar, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeStellar)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeStellar)); @@ -27,8 +27,8 @@ TEST(TWStellarCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeStellar)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeStellar)); assertStringsEqual(symbol, "XLM"); - assertStringsEqual(txUrl, "https://stellarscan.io/transaction/t123"); - assertStringsEqual(accUrl, "https://stellarscan.io/account/a12"); + 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/TON/TWTONAddressTests.cpp b/tests/TON/TWTONAddressTests.cpp index 835eba44b46..0a2e3375833 100644 --- a/tests/TON/TWTONAddressTests.cpp +++ b/tests/TON/TWTONAddressTests.cpp @@ -55,7 +55,7 @@ TEST(TWTONAddress, HDWallet) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeDerivationPath(TWCoinTypeTON)); + auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeTON, TWCoinTypeDerivationPath(TWCoinTypeTON)); auto publicKey = TWPrivateKeyGetPublicKeyEd25519(privateKey); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeTON)); auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/Tron/SerializationTests.cpp b/tests/Tron/SerializationTests.cpp index dd54363b9fc..a32b5799172 100644 --- a/tests/Tron/SerializationTests.cpp +++ b/tests/Tron/SerializationTests.cpp @@ -8,6 +8,7 @@ #include "Tron/Signer.h" #include "PrivateKey.h" #include "HexCoding.h" +#include "uint256.h" #include @@ -151,7 +152,8 @@ namespace TW::Tron { transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - transfer_contract.set_amount(1000); + Data amount = store(uint256_t(1000)); + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); transaction.set_timestamp(1539295479000); @@ -173,4 +175,35 @@ namespace TW::Tron { 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 index 0e255c39b4b..69dc5c37cb5 100644 --- a/tests/Tron/SignerTests.cpp +++ b/tests/Tron/SignerTests.cpp @@ -7,6 +7,7 @@ #include "Bitcoin/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "uint256.h" #include "proto/Tron.pb.h" #include "Tron/Signer.h" @@ -315,7 +316,8 @@ TEST(TronSigner, SignTransferTrc20Contract) { transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - transfer_contract.set_amount(1000); + Data amount = store(uint256_t(1000)); + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); transaction.set_timestamp(1539295479000); diff --git a/tests/Viacoin/TWViacoinAddressTests.cpp b/tests/Viacoin/TWViacoinAddressTests.cpp index 544c125de9b..ad480209687 100644 --- a/tests/Viacoin/TWViacoinAddressTests.cpp +++ b/tests/Viacoin/TWViacoinAddressTests.cpp @@ -33,14 +33,14 @@ TEST(Viacoin, Address) { assertStringsEqual(string, "via1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asu2r3d2"); } -TEST(Viacoin, BuildForAddressV) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); +TEST(Viacoin, LockScriptForAddressV) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } -TEST(Viacoin, BuildForAddressE) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); +TEST(Viacoin, LockScriptForAddressE) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); } @@ -74,8 +74,8 @@ TEST(Viacoin, ExtendedKeys) { TEST(Viacoin, DeriveFromXpub) { auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/14'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/14'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/9").get()); auto address2 = TWBitcoinAddressCreateWithPublicKey(pubKey2, TWCoinTypeP2pkhPrefix(TWCoinTypeViacoin)); auto address2String = WRAPS(TWBitcoinAddressDescription(address2)); @@ -89,8 +89,8 @@ TEST(Viacoin, DeriveFromXpub) { TEST(Viacoin, DeriveFromZpub) { auto zpub = STRING("zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/14'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/14'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/11").get()); auto address4 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPViacoin, pubKey4)); auto address4String = WRAPS(TWSegwitAddressDescription(address4.get())); @@ -103,15 +103,15 @@ TEST(Viacoin, DeriveFromZpub) { } TEST(Viacoin, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("via1qs32zgdhe2tpzcnz55r7d9jvhce33063s8w4xre").get(), TWCoinTypeViacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("via1qs32zgdhe2tpzcnz55r7d9jvhce33063s8w4xre").get(), TWCoinTypeViacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "001484542436f952c22c4c54a0fcd2c997c66317ea30"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } diff --git a/tests/Waves/AddressTests.cpp b/tests/Waves/AddressTests.cpp index 0ed61aa772e..b5a8afe9232 100644 --- a/tests/Waves/AddressTests.cpp +++ b/tests/Waves/AddressTests.cpp @@ -75,9 +75,9 @@ TEST(WavesAddress, Derive) { "ill special axis adjust pull useful craft peace flee physical"; const auto wallet = HDWallet(mnemonic, ""); const auto address1 = TW::deriveAddress( - TWCoinTypeWaves, wallet.getKey(DerivationPath("m/44'/5741564'/0'/0'/0'"))); + TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/0'"))); const auto address2 = TW::deriveAddress( - TWCoinTypeWaves, wallet.getKey(DerivationPath("m/44'/5741564'/0'/0'/1'"))); + TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/1'"))); ASSERT_EQ(address1, "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); ASSERT_EQ(address2, "3PEXw52bkS9XuLhttWoKyykZjXqEY8zeLxf"); diff --git a/tests/Zcash/TWCoinTypeTests.cpp b/tests/Zcash/TWCoinTypeTests.cpp index fc887dd9891..da6af47b0d6 100644 --- a/tests/Zcash/TWCoinTypeTests.cpp +++ b/tests/Zcash/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWZcashCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZcash)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcash, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZcash, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZcash)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcash)); @@ -27,8 +27,8 @@ TEST(TWZcashCoinType, TWCoinType) { ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZcash)); ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZcash)); assertStringsEqual(symbol, "ZEC"); - assertStringsEqual(txUrl, "https://sochain.com/tx/ZEC/t123"); - assertStringsEqual(accUrl, "https://sochain.com/address/ZEC/a12"); + 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/Zcash/TWZcashAddressTests.cpp index 231c4e2b821..1afd9ae41ab 100644 --- a/tests/Zcash/TWZcashAddressTests.cpp +++ b/tests/Zcash/TWZcashAddressTests.cpp @@ -32,7 +32,7 @@ TEST(TWZcash, DeriveTransparentAddress) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto derivationPath = STRING("m/44'/133'/0'/0/5"); - auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), derivationPath.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeZcash, derivationPath.get())); auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(key.get(), true); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeZcash)); @@ -56,8 +56,8 @@ TEST(TWZcash, ExtendedKeys) { TEST(TWZcash, DerivePubkeyFromXpub) { auto xpub = STRING("xpub6CksSgKBhD9KaLgxLE9LXpSj74b2EB9d1yKvhWxrstk4Md8gmiJb5GwkMeBhpLxVjACMdNbRsAm2GG5ehVuyq42QBYYPAjXjcBxMVmpaaNL"); - auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/133'/0'/0/3").get()); - auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/133'/0'/0/5").get()); + auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcash, STRING("m/44'/133'/0'/0/3").get()); + auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcash, STRING("m/44'/133'/0'/0/5").get()); auto address3 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey3, TWCoinTypeZcash)); auto address3String = WRAPS(TWAnyAddressDescription(address3.get())); @@ -71,17 +71,17 @@ TEST(TWZcash, DerivePubkeyFromXpub) { TEST(TWZcash, DerivePubkeyFromXpub2) { auto xpub = STRING("xpub6C7HhMqpir3KBA6ammv5B58RT3XFTJqoZFoj3J56dz9XwehZ2puSH38ERtnz7HaXGxaZP8AHT4M2bSRHpBXUZrbsJ2xg3xs53DGKYCqj8mr"); - auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/133'/0'/0/0").get()); + auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcash, STRING("m/44'/133'/0'/0/0").get()); auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeZcash, pubKey)); assertStringsEqual(address, "t1TKCtCETHPrAdA6eY1fdhhnTkTmb371oPt"); } TEST(TWZcash, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("t1bjVPEY8NbpGxT2PgayX3HevfJ2YU5X2DS").get(), TWCoinTypeZcash)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("t1bjVPEY8NbpGxT2PgayX3HevfJ2YU5X2DS").get(), TWCoinTypeZcash)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914c3e968851fdb2bb943662befdb8b8573ecd4d08e88ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm").get(), TWCoinTypeZcash)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm").get(), TWCoinTypeZcash)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914ef8b3e86db855eb48bcf0b7585a90b6b9ece75c087"); } diff --git a/tests/Zcash/TWZcashTransactionTests.cpp b/tests/Zcash/TWZcashTransactionTests.cpp index e238ae2e69b..d312a424d7d 100644 --- a/tests/Zcash/TWZcashTransactionTests.cpp +++ b/tests/Zcash/TWZcashTransactionTests.cpp @@ -165,7 +165,7 @@ TEST(TWZcashTransaction, BlossomSigning) { // 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::buildForAddress(utxoAddr0, TWCoinTypeZcash); + 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()); diff --git a/tests/Zcoin/TWZCoinAddressTests.cpp b/tests/Zcoin/TWZCoinAddressTests.cpp index 4f0261b4304..583e8c41306 100644 --- a/tests/Zcoin/TWZCoinAddressTests.cpp +++ b/tests/Zcoin/TWZCoinAddressTests.cpp @@ -39,8 +39,8 @@ TEST(TWZCoin, ExtendedKeys) { TEST(TWZcoin, DeriveFromXpub) { auto xpub = STRING("xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/136'/0'/0/3").get()); - auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/136'/0'/0/5").get()); + auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/3").get()); + auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/5").get()); auto address3 = TWBitcoinAddressCreateWithPublicKey(pubKey3, TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin)); auto address3String = WRAPS(TWBitcoinAddressDescription(address3)); @@ -53,11 +53,11 @@ TEST(TWZcoin, DeriveFromXpub) { } TEST(TWZcoin, LockScripts) { - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeZcoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeZcoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9142a10f88e30768d2712665c279922b9621ce58bc788ac"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeZcoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeZcoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a914f010b17a9189e0f2737d71ae9790359eb5bbc13787"); } diff --git a/tests/Zelcash/TWZelcashAddressTests.cpp b/tests/Zelcash/TWZelcashAddressTests.cpp index c61a905c126..570442841e2 100644 --- a/tests/Zelcash/TWZelcashAddressTests.cpp +++ b/tests/Zelcash/TWZelcashAddressTests.cpp @@ -29,7 +29,7 @@ TEST(TWZelcash, DeriveTransparentAddress) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto derivationPath = STRING("m/44'/19167'/0'/0/5"); - auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), derivationPath.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeZelcash, derivationPath.get())); auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(key.get(), true); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeZelcash)); @@ -53,8 +53,8 @@ TEST(TWZelcash, ExtendedKeys) { TEST(TWZelcash, DerivePubkeyFromXpub) { auto xpub = STRING("xpub6DATuScKPEk6YvULrHPff1NKC49nyz5mCZQyxSDEQihq3kfoKDYCznLrsdW4KmXw9TryNfEZ9JSD8tJL9UTC3LnBA54YZL7nqMtJm7Ffnoz"); - auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/19167'/0'/0/3").get()); - auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/19167'/0'/0/5").get()); + auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZelcash, STRING("m/44'/19167'/0'/0/3").get()); + auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZelcash, STRING("m/44'/19167'/0'/0/5").get()); auto address3 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey3, TWCoinTypeZelcash)); auto address3String = WRAPS(TWAnyAddressDescription(address3.get())); @@ -68,7 +68,7 @@ TEST(TWZelcash, DerivePubkeyFromXpub) { TEST(TWZelcash, DerivePubkeyFromXpub2) { auto xpub = STRING("xpub6C7HhMqpir3KBA6ammv5B58RT3XFTJqoZFoj3J56dz9XwehZ2puSH38ERtnz7HaXGxaZP8AHT4M2bSRHpBXUZrbsJ2xg3xs53DGKYCqj8mr"); - auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/19167'/0'/0/0").get()); + auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZelcash, STRING("m/44'/19167'/0'/0/0").get()); auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeZelcash, pubKey)); assertStringsEqual(address, "t1TKCtCETHPrAdA6eY1fdhhnTkTmb371oPt"); } diff --git a/tests/interface/TWAESTests.cpp b/tests/interface/TWAESTests.cpp index 5616d41dd1c..8f1229ae5ca 100644 --- a/tests/interface/TWAESTests.cpp +++ b/tests/interface/TWAESTests.cpp @@ -11,28 +11,45 @@ #include auto key = DATA("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); +auto key2 = DATA("bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96"); -TEST(TWAES, CBCEncrypt) { +TEST(TWAES, CBCEncryptZeroPadding) { auto iv = DATA("000102030405060708090A0B0C0D0E0F"); auto data = DATA("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = WRAPD(TWAESCBCEncrypt(key.get(), data.get(), iv.get())); + auto encryptResult = WRAPD(TWAESEncryptCBC(key.get(), data.get(), iv.get(), TWAESPaddingModeZero)); assertHexEqual(encryptResult, "f58c4c04d6e5f1ba779eabfb5f7bfbd6"); } -TEST(TWAES, CBCDecrypt) { +TEST(TWAES, CBCDecryptZeroPadding) { auto iv = DATA("000102030405060708090A0B0C0D0E0F"); auto cipher = DATA("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - auto decryptResult = WRAPD(TWAESCBCDecrypt(key.get(), cipher.get(), iv.get())); + auto decryptResult = WRAPD(TWAESDecryptCBC(key.get(), cipher.get(), iv.get(), TWAESPaddingModeZero)); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } +TEST(TWAES, CBCEncryptPKCS7Padding) { + auto iv = DATA("37f8687086d31852979e228f4a97925b"); + auto data = DATA("7b226a736f6e727063223a22322e30222c226964223a313535343334333833343735323434362c226572726f72223a7b22636f6465223a2d33323030302c226d657373616765223a2253657373696f6e2052656a6563746564227d7d"); + + auto encryptResult = WRAPD(TWAESEncryptCBC(key2.get(), data.get(), iv.get(), TWAESPaddingModePKCS7)); + assertHexEqual(encryptResult, "23c75d1b3228742ddb12eeef5a5016e37a8980a77fabc6dd01e6a355d88851c611d37e0d17a2f9c30f659da6d42ba77aca9b84bd6a95e3924f47d9093fbf16e0fb55b165ec193489645b4f7d2573959305c8fa70f88fe5affc43e3084a5878d1"); +} + +TEST(TWAES, CBCDecryptPKCS7Padding) { + auto iv = DATA("debb62725b21c7577e4e498e10f096c7"); + auto cipher = DATA("e7df9810ce66defcc03023ee945f5958c1d4697bf97945daeab5059c2bc6262642cbca82982ac690e77e16671770c200f348f743a7c6e5df5c74eb892ef9b45a9b5ddf0f08fa60c49e5b694688d1b0b521b43975e65b4e8d557a83f4d1aab0af"); + + auto decryptResult = WRAPD(TWAESDecryptCBC(key2.get(), cipher.get(), iv.get(), TWAESPaddingModePKCS7)); + assertHexEqual(decryptResult, "7b226a736f6e727063223a22322e30222c226964223a313535343334333833343735323434362c226572726f72223a7b22636f6465223a2d33323030302c226d657373616765223a2253657373696f6e2052656a6563746564227d7d"); +} + TEST(TWAES, CTREncrypt) { auto iv = DATA("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto data = DATA("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = WRAPD(TWAESCTREncrypt(key.get(), data.get(), iv.get())); + auto encryptResult = WRAPD(TWAESEncryptCTR(key.get(), data.get(), iv.get())); assertHexEqual(encryptResult, "601ec313775789a5b7a7f504bbf3d228"); } @@ -40,6 +57,6 @@ TEST(TWAES, CTRDecrypt) { auto iv = DATA("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto cipher = DATA("601ec313775789a5b7a7f504bbf3d228"); - auto decryptResult = WRAPD(TWAESCTRDecrypt(key.get(), cipher.get(), iv.get())); + auto decryptResult = WRAPD(TWAESDecryptCTR(key.get(), cipher.get(), iv.get())); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index c35b6935a79..ab90a46891c 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -119,4 +119,18 @@ TEST(AnyAddress, Data) { auto keyHash = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(keyHash, "172bdf43265c0adfe105a1a8c45b3f406a38362f24"); } + // elrond + { + auto string = STRING("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeElrond)); + auto pubkey = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(pubkey, "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"); + } + // near + { + auto string = STRING("NEARTDDWrUMdoC2rA1eU6gNrSU2zyGKdR71TNucTvsQHyfAXjKcJb"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeNEAR)); + auto pubkey = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(pubkey, "3b83b07cab54824a59c3d3f2e203a7cd913b7fcdc4439595983e2402c2cf791d"); + } } diff --git a/tests/interface/TWBase58Tests.cpp b/tests/interface/TWBase58Tests.cpp new file mode 100644 index 00000000000..74a8d7ddced --- /dev/null +++ b/tests/interface/TWBase58Tests.cpp @@ -0,0 +1,68 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TWTestUtilities.h" + +#include + +#include + +TEST(TWBase58, Encode) { + const auto input = DATA("00769bdff96a02f9135a1d19b749db6a78fe07dc90"); + auto result = WRAPS(TWBase58Encode(input.get())); + assertStringsEqual(result, "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); +} + +TEST(TWBase58, EncodeNoCheck) { + const auto input = DATA("00769bdff96a02f9135a1d19b749db6a78fe07dc90c3507da5"); + auto result = WRAPS(TWBase58EncodeNoCheck(input.get())); + assertStringsEqual(result, "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); +} + +TEST(TWBase58, EncodeNoCheck2) { + const auto input = DATA("00769bdff96a02f9135a1d19b749db6a78fe07dc90deadbeef"); + auto result = WRAPS(TWBase58EncodeNoCheck(input.get())); + assertStringsEqual(result, "1Bp9U1ogV3A14FMvKbRJms7ctyso5FdSz2"); +} + +TEST(TWBase58, Decode) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + auto result = WRAPS(TWBase58Decode(input.get())); + assertHexEqual(result, "00769bdff96a02f9135a1d19b749db6a78fe07dc90"); +} + +TEST(TWBase58, DecodeNoCheck) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + auto result = WRAPS(TWBase58DecodeNoCheck(input.get())); + assertHexEqual(result, "00769bdff96a02f9135a1d19b749db6a78fe07dc90c3507da5"); +} + +TEST(TWBase58, Decode_WrongChecksum) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso5FdSz2"); + auto result = WRAPS(TWBase58Decode(input.get())); + ASSERT_EQ(result.get(), nullptr); +} + +TEST(TWBase58, DecodeNoCheck_WrongChecksum) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso5FdSz2"); + auto result = WRAPS(TWBase58DecodeNoCheck(input.get())); + // decodes despite wrong checksum + assertHexEqual(result, "00769bdff96a02f9135a1d19b749db6a78fe07dc90deadbeef"); +} + +TEST(TWBase58, Decode_InvalidChar) { + // 0 is invalid + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tc0"); + auto result = WRAPS(TWBase58Decode(input.get())); + ASSERT_EQ(result.get(), nullptr); +} + +TEST(TWBase58, DecodeNoCheck_InvalidChar) { + // 0 is invalid + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tc0"); + auto result = WRAPS(TWBase58DecodeNoCheck(input.get())); + ASSERT_EQ(result.get(), nullptr); +} diff --git a/tests/interface/TWDataTests.cpp b/tests/interface/TWDataTests.cpp index 496074ed0fd..6e648769c57 100644 --- a/tests/interface/TWDataTests.cpp +++ b/tests/interface/TWDataTests.cpp @@ -4,26 +4,157 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include #include "TWTestUtilities.h" #include -TEST(DataTests, ParseHex) { - auto zero = DATA("0x0"); - ASSERT_EQ(TWDataSize(zero.get()), 1); - ASSERT_EQ(TWDataBytes(zero.get())[0], 0); +TEST(TWData, CreateWithHexString) { + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + ASSERT_EQ(TWDataSize(data.get()), 4); + EXPECT_EQ(TWDataBytes(data.get())[0], 0xde); + EXPECT_EQ(TWDataBytes(data.get())[1], 0xad); + EXPECT_EQ(TWDataBytes(data.get())[2], 0xbe); + EXPECT_EQ(TWDataBytes(data.get())[3], 0xef); + assertHexEqual(data, "deadbeef"); + } - auto num = DATA("0xdeadbeef"); - ASSERT_EQ(TWDataSize(num.get()), 4); - ASSERT_EQ(TWDataBytes(num.get())[0], 0xde); - ASSERT_EQ(TWDataBytes(num.get())[1], 0xad); - ASSERT_EQ(TWDataBytes(num.get())[2], 0xbe); - ASSERT_EQ(TWDataBytes(num.get())[3], 0xef); + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("00").get())); + ASSERT_EQ(TWDataSize(data.get()), 1); + EXPECT_EQ(TWDataBytes(data.get())[0], 0); + assertHexEqual(data, "00"); + } + + { + // with 0x prefix + const auto data = WRAPD(TWDataCreateWithHexString(STRING("0xdeadbeef").get())); + assertHexEqual(data, "deadbeef"); + } + + { + // uppercase + const auto data = WRAPD(TWDataCreateWithHexString(STRING("DEADBEEF").get())); + assertHexEqual(data, "deadbeef"); + } + + { + // odd length is invalid (intended grouping to bytes is not obvious) + const auto data = WRAPD(TWDataCreateWithHexString(STRING("012").get())); + assertHexEqual(data, ""); + } + + { + // null input + TWString* nullstring = nullptr; + const auto data = WRAPD(TWDataCreateWithHexString(nullstring)); + ASSERT_EQ(data.get(), nullptr); + } +} + +TEST(TWData, CreateWithBytes) { + const uint8_t bytes[] = {0xde, 0xad, 0xbe, 0xef}; + const auto data = WRAPD(TWDataCreateWithBytes(bytes, 4)); + assertHexEqual(data, "deadbeef"); +} + +TEST(TWData, CreateWithSize) { + int n = 12; + const auto data = WRAPD(TWDataCreateWithSize(n)); + ASSERT_EQ(TWDataSize(data.get()), n); + for (int i = 0; i < n; ++i) { + EXPECT_EQ(TWDataBytes(data.get())[i], 0); + } +} + +TEST(TWData, CreateWithData) { + const auto data1 = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + const auto data2 = WRAPD(TWDataCreateWithData(data1.get())); + assertHexEqual(data2, "deadbeef"); +} + +TEST(TWData, Delete) { + const auto data = TWDataCreateWithSize(8); + ASSERT_TRUE(data != nullptr); + TWDataDelete(data); +} + +TEST(TWData, Get) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + EXPECT_EQ(TWDataGet(data.get(), 0), 0xde); + EXPECT_EQ(TWDataGet(data.get(), 1), 0xad); + EXPECT_EQ(TWDataGet(data.get(), 2), 0xbe); + EXPECT_EQ(TWDataGet(data.get(), 3), 0xef); } -TEST(DataTests, Equal) { - auto data1 = DATA("0xdeadbeef"); - auto data2 = DATA("0xdeadbeef"); +TEST(TWData, Set) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataSet(data.get(), 1, 0xff); + assertHexEqual(data, "deffbeef"); +} + +TEST(TWData, CopyBytes) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + uint8_t buffer[4]; + TWDataCopyBytes(data.get(), 0, 4, buffer); + const auto data2 = WRAPD(TWDataCreateWithBytes(buffer, 4)); + assertHexEqual(data2, "deadbeef"); +} + +TEST(TWData, ReplaceBytes) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + const uint8_t bytes[] = {0x12, 0x34}; + TWDataReplaceBytes(data.get(), 1, 2, bytes); + assertHexEqual(data, "de1234ef"); +} + +TEST(TWData, Append) { + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataAppendByte(data.get(), 0x12); + assertHexEqual(data, "deadbeef12"); + } + + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + const uint8_t bytes[] = {0x12, 0x34}; + TWDataAppendBytes(data.get(), bytes, 2); + assertHexEqual(data, "deadbeef1234"); + } + + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + const auto data2 = WRAPD(TWDataCreateWithHexString(STRING("1234").get())); + assertHexEqual(data2, "1234"); + TWDataAppendData(data.get(), data2.get()); + assertHexEqual(data, "deadbeef1234"); + } +} + +TEST(TWData, Reverse) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataReverse(data.get()); + assertHexEqual(data, "efbeadde"); +} + +TEST(TWData, Reset) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataReset(data.get()); + assertHexEqual(data, "00000000"); +} +TEST(TWData, Equal) { + const auto data1 = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + const auto data2 = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); ASSERT_TRUE(TWDataEqual(data1.get(), data2.get())); } diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 62f67695f0e..d2dbc7bb0d9 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -21,8 +21,10 @@ #include #include -auto words = STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); -auto passphrase = STRING("TREZOR"); +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 seedHex = "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"; +const auto passphrase = STRING("TREZOR"); auto valid = STRING("credit expect life fade cover suit response wash pear what skull force"); auto invalidWord = STRING("ripple scissors hisc mammal hire column oak again sun offer wealth tomorrow"); @@ -30,14 +32,26 @@ auto invalidWord1 = STRING("high culture ostrich wrist exist ignore interest hyb auto invalidChecksum = STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow"); auto invalidWordCount = STRING("credit expect life fade cover suit response wash what skull force"); -inline void assertSeedEq(std::shared_ptr& wallet, const char* expected) { - auto seed = WRAPD(TWHDWalletSeed(wallet.get())); +inline void assertSeedEq(const std::shared_ptr& wallet, const char* expected) { + const auto seed = WRAPD(TWHDWalletSeed(wallet.get())); assertHexEqual(seed, expected); } +inline void assertMnemonicEq(const std::shared_ptr& wallet, const char* expected) { + const auto mnemonic = WRAPS(TWHDWalletMnemonic(wallet.get())); + assertStringsEqual(mnemonic, expected); +} + +TEST(HDWallet, Mnemonic) { + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + assertSeedEq(wallet, seedHex); + assertMnemonicEq(wallet, wordsStr); +} + TEST(HDWallet, Seed) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); - assertSeedEq(wallet, "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithData(DATA("ba5821e8c356c05ba5f025d9532fe0f21f65d594").get(), passphrase.get())); + assertSeedEq(wallet, seedHex); + assertMnemonicEq(wallet, wordsStr); } TEST(HDWallet, IsValid) { @@ -126,13 +140,18 @@ TEST(HDWallet, DeriveAddressBitcoin) { TEST(HDWallet, DeriveEthereum) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeEthereum)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); + auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(key.get(), false); + auto publicKey2 = TWPrivateKeyGetPublicKeySecp256k1(key2.get(), false); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey)); auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeEthereum, publicKey)); + auto address2 = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeSmartChain, publicKey2)); assertHexEqual(publicKeyData, "0414acbe5a06c68210fcbb77763f9612e45a526990aeb69d692d705f276f558a5ae68268e9389bb099ed5ac84d8d6861110f63644f6e5b447e3f86b4bab5dee011"); assertStringsEqual(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); + assertStringsEqual(address2, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); } TEST(HDWallet, DeriveAddressEthereum) { @@ -231,6 +250,29 @@ 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)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeElrond, privateKey.get())); + + assertHexEqual(privateKeyData, "0eb593141de897d60a0883320793eb49e63d556ccdf783a87ec014f150d50453"); + assertStringsEqual(address, "erd1a6l7q9cfvrgr80xuzm37tapdr4zm3mwrtl6vt8f45df45x7eadfs8ds5vv"); +} + +TEST(HDWallet, DeriveBinance) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinance)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChainLegacy)); + auto keyData = WRAPD(TWPrivateKeyData(key.get())); + auto keyData2 = WRAPD(TWPrivateKeyData(key2.get())); + + auto expected = "ca81b1b0974aa063de2f74c17b9dc364a8208d105659f4f900c121fb170922fe"; + + assertHexEqual(keyData, expected); + assertHexEqual(keyData2, expected); +} + TEST(HDWallet, ExtendedKeys) { auto words = STRING("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"); auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); @@ -259,8 +301,8 @@ TEST(HDWallet, ExtendedKeys) { TEST(HDWallet, PublicKeyFromX) { auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); - auto xpubAddr2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/2").get()); - auto xpubAddr9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/9").get()); + auto xpubAddr2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get()); + auto xpubAddr9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/9").get()); auto data2 = WRAPD(TWPublicKeyData(xpubAddr2)); auto data9 = WRAPD(TWPublicKeyData(xpubAddr9)); @@ -269,10 +311,16 @@ TEST(HDWallet, PublicKeyFromX) { assertHexEqual(data9, "03786c1d274f2c804ff9a57d8e7289c281d4aef15e17187ad9f9c3722d81a6ae66"); } +TEST(HDWallet, PublicKeyInvalid) { + auto xpub = STRING("xpub0000"); + auto xpubAddr = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/0").get()); + ASSERT_EQ(xpubAddr, nullptr); +} + TEST(HDWallet, PublicKeyFromY) { auto ypub = STRING("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP"); - auto ypubAddr3 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), STRING("m/44'/0'/0'/0/3").get()); - auto ypubAddr10 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), STRING("m/44'/0'/0'/0/10").get()); + auto ypubAddr3 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/3").get()); + auto ypubAddr10 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/10").get()); auto data3 = WRAPD(TWPublicKeyData(ypubAddr3)); auto data10 = WRAPD(TWPublicKeyData(ypubAddr10)); @@ -283,8 +331,8 @@ TEST(HDWallet, PublicKeyFromY) { TEST(HDWallet, PublicKeyFromZ) { auto zpub = STRING("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"); - auto zpubAddr4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/0'/0'/0/4").get()); - auto zpubAddr11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/0'/0'/0/11").get()); + auto zpubAddr4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/4").get()); + auto zpubAddr11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/11").get()); auto data4 = WRAPD(TWPublicKeyData(zpubAddr4)); auto data11 = WRAPD(TWPublicKeyData(zpubAddr11)); @@ -297,6 +345,35 @@ TEST(HDWallet, PublicKeyFromZ) { assertStringsEqual(address4, "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"); } +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 + ASSERT_NE(xpubAddr.get(), nullptr); + auto data = WRAPD(TWPublicKeyData(xpubAddr.get())); + ASSERT_NE(data.get(), nullptr); + assertHexEqual(data, "03774c910fcf07fa96886ea794f0d5caed9afe30b44b83f7e213bb92930e7df4bd"); +} + +TEST(HDWallet, PublicKeyFromExtended_Negative) { + const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); + { // Ed25519 + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeSolana, STRING("m/44'/501'/0'").get())); // Solana + EXPECT_EQ(xpubAddr.get(), nullptr); + } + { // Ed25519Extended + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeCardano, STRING("m/1852'/1815'/0'/0/0").get())); // Cardano + EXPECT_EQ(xpubAddr.get(), nullptr); + } + { // Ed25519Blake2bNano + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeNano, STRING("m/44'/165'/0'").get())); // Nano + EXPECT_EQ(xpubAddr.get(), nullptr); + } + { // Curve25519 + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeWaves, STRING("m/44'/5741564'/0'/0'/0'").get())); // Waves + EXPECT_EQ(xpubAddr.get(), nullptr); + } +} + TEST(HDWallet, MultipleThreads) { auto passphrase = STRING(""); @@ -316,3 +393,10 @@ TEST(HDWallet, MultipleThreads) { th2.join(); 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)); + const auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + assertHexEqual(privateKeyData, "1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac"); +} diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 11af1a19e50..18517e8710a 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -27,7 +27,9 @@ TEST(TWHRP, StringForHRP) { ASSERT_STREQ(stringForHRP(TWHRPTerra), "terra"); ASSERT_STREQ(stringForHRP(TWHRPMonacoin), "mona"); ASSERT_STREQ(stringForHRP(TWHRPKava), "kava"); + ASSERT_STREQ(stringForHRP(TWHRPBandChain), "band"); ASSERT_STREQ(stringForHRP(TWHRPCardano), "addr"); + ASSERT_STREQ(stringForHRP(TWHRPElrond), "erd"); } TEST(TWHRP, HRPForString) { @@ -46,7 +48,9 @@ TEST(TWHRP, HRPForString) { ASSERT_EQ(hrpForString("terra"), TWHRPTerra); ASSERT_EQ(hrpForString("mona"), TWHRPMonacoin); ASSERT_EQ(hrpForString("kava"), TWHRPKava); + ASSERT_EQ(hrpForString("band"), TWHRPBandChain); ASSERT_EQ(hrpForString("addr"), TWHRPCardano); + ASSERT_EQ(hrpForString("erd"), TWHRPElrond); } TEST(TWHPR, HPRByCoinType) { @@ -64,7 +68,9 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPTerra, TWCoinTypeHRP(TWCoinTypeTerra)); ASSERT_EQ(TWHRPMonacoin, TWCoinTypeHRP(TWCoinTypeMonacoin)); ASSERT_EQ(TWHRPKava, TWCoinTypeHRP(TWCoinTypeKava)); + ASSERT_EQ(TWHRPBandChain, TWCoinTypeHRP(TWCoinTypeBandChain)); ASSERT_EQ(TWHRPCardano, TWCoinTypeHRP(TWCoinTypeCardano)); + ASSERT_EQ(TWHRPElrond, TWCoinTypeHRP(TWCoinTypeElrond)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeAion)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeCallisto)); diff --git a/tests/interface/TWPrivateKeyTests.cpp b/tests/interface/TWPrivateKeyTests.cpp index 2598f6a99e8..392e2de86e9 100644 --- a/tests/interface/TWPrivateKeyTests.cpp +++ b/tests/interface/TWPrivateKeyTests.cpp @@ -16,15 +16,34 @@ #include -TEST(PrivateKeyTests, CreateInvalid) { - uint8_t bytes[] = {0xde, 0xad, 0xbe, 0xef}; - auto data = WRAPD(TWDataCreateWithBytes(bytes, 4)); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); +const auto key1Hex = "22667b69166481c9f334756f49c8dddfd72c6bcdd68a7386886e97a82f741130"; + +TEST(TWPrivateKeyTests, Create) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get())); + ASSERT_TRUE(privateKey.get() != nullptr); + const auto data = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TW::hex(TW::data(TWDataBytes(data.get()), TWDataSize(data.get()))), key1Hex); +} + +TEST(TWPrivateKeyTests, CreateNewRandom) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreate()); + ASSERT_TRUE(privateKey.get() != nullptr); +} + +TEST(TWPrivateKeyTests, CreateInvalid) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("deadbeef").get())); ASSERT_EQ(privateKey.get(), nullptr); } -TEST(PrivateKeyTests, AllZeros) { +TEST(TWPrivateKeyTests, CreateCopy) { + const auto privateKey1 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get())); + ASSERT_TRUE(privateKey1.get() != nullptr); + const auto privateKey2 = WRAP(TWPrivateKey, TWPrivateKeyCreateCopy(privateKey1.get())); + ASSERT_TRUE(privateKey2.get() != nullptr); +} + +TEST(TWPrivateKeyTests, AllZeros) { auto bytes = TW::Data(32); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); @@ -32,7 +51,7 @@ TEST(PrivateKeyTests, AllZeros) { ASSERT_EQ(privateKey.get(), nullptr); } -TEST(PrivateKeyTests, Invalid) { +TEST(TWPrivateKeyTests, Invalid) { auto bytes = TW::parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); auto valid = TW::PrivateKey::isValid(bytes, TWCurveSECP256k1); @@ -43,27 +62,30 @@ TEST(PrivateKeyTests, Invalid) { ASSERT_EQ(valid2, false); } -TEST(PrivateKeyTests, IsValid) { - uint8_t bytes[] = {0xaf, 0xee, 0xfc, 0xa7, 0x4d, 0x9a, 0x32, 0x5c, 0xf1, 0xd6, 0xb6, 0x91, 0x1d, 0x61, 0xa6, 0x5c, 0x32, 0xaf, 0xa8, 0xe0, 0x2b, 0xd5, 0xe7, 0x8e, 0x2e, 0x4a, 0xc2, 0x91, 0x0b, 0xab, 0x45, 0xf5}; - auto data = WRAPD(TWDataCreateWithBytes(bytes, 32)); +TEST(TWPrivateKeyTests, IsValid) { + const auto data = DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); ASSERT_TRUE(TWPrivateKeyIsValid(data.get(), TWCurveSECP256k1)); ASSERT_TRUE(TWPrivateKeyIsValid(data.get(), TWCurveED25519)); } -TEST(PrivateKeyTests, PublicKey) { - uint8_t bytes[] = {0xaf, 0xee, 0xfc, 0xa7, 0x4d, 0x9a, 0x32, 0x5c, 0xf1, 0xd6, 0xb6, 0x91, 0x1d, 0x61, 0xa6, 0x5c, 0x32, 0xaf, 0xa8, 0xe0, 0x2b, 0xd5, 0xe7, 0x8e, 0x2e, 0x4a, 0xc2, 0x91, 0x0b, 0xab, 0x45, 0xf5}; - auto data = WRAPD(TWDataCreateWithBytes(bytes, 32)); - auto privateKey = TWPrivateKeyCreateWithData(data.get()); - auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey, false); - - uint8_t expected[] = {0x04, 0x99, 0xc6, 0xf5, 0x1a, 0xd6, 0xf9, 0x8c, 0x9c, 0x58, 0x3f, 0x8e, 0x92, 0xbb, 0x77, 0x58, 0xab, 0x2c, 0xa9, 0xa0, 0x41, 0x10, 0xc0, 0xa1, 0x12, 0x6e, 0xc4, 0x3e, 0x54, 0x53, 0xd1, 0x96, 0xc1, 0x66, 0xb4, 0x89, 0xa4, 0xb7, 0xc4, 0x91, 0xe7, 0x68, 0x8e, 0x6e, 0xbe, 0xa3, 0xa7, 0x1f, 0xc3, 0xa1, 0xa4, 0x8d, 0x60, 0xf9, 0x8d, 0x5c, 0xe8, 0x4c, 0x93, 0xb6, 0x5e, 0x42, 0x3f, 0xde, 0x91}; - for (auto i = 0; i < sizeof(expected); i += 1) { - ASSERT_EQ(publicKey->impl.bytes[i], expected[i]); +TEST(TWPrivateKeyTests, PublicKey) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + { + const auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false); + ASSERT_EQ(TW::hex(publicKey->impl.bytes), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); + } + { + const auto publicKey = TWPrivateKeyGetPublicKeyNist256p1(privateKey.get()); + ASSERT_EQ(TW::hex(publicKey->impl.bytes), "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"); + } + { + const auto publicKey = TWPrivateKeyGetPublicKeyCurve25519(privateKey.get()); + ASSERT_EQ(TW::hex(publicKey->impl.bytes), "686cfce9108566dd43fc6aa75e31f9a9f319c9e9c04d6ad0a52505b86bc17c3a"); } } -TEST(PrivateKeyTests, ClearMemory) { +TEST(TWPrivateKeyTests, ClearMemory) { auto privKey = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; auto privKeyData = TW::parse_hex(privKey); auto data = WRAPD(TWDataCreateWithBytes(privKeyData.data(), privKeyData.size())); @@ -82,19 +104,28 @@ TEST(PrivateKeyTests, ClearMemory) { ASSERT_GE(countDifferent, 32*2/3); } -TEST(PrivateKeyTests, Sign) { - uint8_t bytes[] = {0xaf, 0xee, 0xfc, 0xa7, 0x4d, 0x9a, 0x32, 0x5c, 0xf1, 0xd6, 0xb6, 0x91, 0x1d, 0x61, 0xa6, 0x5c, 0x32, 0xaf, 0xa8, 0xe0, 0x2b, 0xd5, 0xe7, 0x8e, 0x2e, 0x4a, 0xc2, 0x91, 0x0b, 0xab, 0x45, 0xf5}; - auto keyData = WRAPD(TWDataCreateWithBytes(bytes, 32)); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get())); +TEST(TWPrivateKeyTests, Sign) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); - auto message = "hello"; - auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); - auto hash = WRAPD(TWHashKeccak256(data.get())); + const auto message = "hello"; + const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); + const auto hash = WRAPD(TWHashKeccak256(data.get())); - auto actual = WRAPD(TWPrivateKeySign(privateKey.get(), hash.get(), TWCurveSECP256k1)); + const auto actual = WRAPD(TWPrivateKeySign(privateKey.get(), hash.get(), TWCurveSECP256k1)); - uint8_t expected[] = {0x87, 0x20, 0xa4, 0x6b, 0x5b, 0x39, 0x63, 0x79, 0x0d, 0x94, 0xbc, 0xc6, 0x1a, 0xd5, 0x7c, 0xa0, 0x2f, 0xd1, 0x53, 0x58, 0x43, 0x15, 0xbf, 0xa1, 0x61, 0xed, 0x34, 0x55, 0xe3, 0x36, 0xba, 0x62, 0x4d, 0x68, 0xdf, 0x01, 0x0e, 0xd9, 0x34, 0xb8, 0x79, 0x2c, 0x5b, 0x6a, 0x57, 0xba, 0x86, 0xc3, 0xda, 0x31, 0xd0, 0x39, 0xf9, 0x61, 0x2b, 0x44, 0xd1, 0xbf, 0x05, 0x41, 0x32, 0x25, 0x4d, 0xe9, 0x01}; - for (auto i = 0; i < sizeof(expected); i += 1) { - ASSERT_EQ(TWDataBytes(actual.get())[i], expected[i]); - } + ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), + "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); +} + +TEST(TWPrivateKeyTests, SignAsDER) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + + const auto message = "hello"; + 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)); + + ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), + "30450221008720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba6202204d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9"); } diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index 5dad2ea0572..b9ae558bc4e 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -18,6 +18,14 @@ using namespace TW; +TEST(TWPublicKeyTests, Create) { + const auto publicKeyHex = "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"; + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(publicKey != nullptr); + const auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(hex(*((Data*)(publicKeyData.get()))), publicKeyHex); +} + TEST(TWPublicKeyTests, CreateFromPrivateSecp256k1) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); @@ -33,6 +41,11 @@ TEST(TWPublicKeyTests, CreateFromPrivateSecp256k1) { TWPublicKeyDelete(publicKey); } +TEST(TWPublicKeyTests, CreateInvalid) { + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("deadbeef").get(), TWPublicKeyTypeSECP256k1)); + EXPECT_EQ(publicKey, nullptr); +} + TEST(TWPublicKeyTests, CompressedExtended) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); @@ -87,3 +100,20 @@ TEST(TWPublicKeyTests, VerifyEd25519) { ASSERT_TRUE(TWPublicKeyVerify(publicKey, signature.get(), digest.get())); ASSERT_TRUE(TWPublicKeyVerify(publicKey2, signature2.get(), digest.get())); } + +TEST(TWPublicKeyTests, Recover) { + const auto message = DATA("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = DATA("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyRecover(signature.get(), message.get())); + EXPECT_TRUE(publicKey.get() != nullptr); + EXPECT_EQ(TWPublicKeyKeyType(publicKey.get()), TWPublicKeyTypeSECP256k1Extended); + const auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(hex(*((Data*)(publicKeyData.get()))), + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); +} + +TEST(TWPublicKeyTests, RecoverInvalid) { + const auto deadbeef = DATA("deadbeef"); + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyRecover(deadbeef.get(), deadbeef.get())); + EXPECT_EQ(publicKey.get(), nullptr); +} diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index b769752db9b..ac36a9e3c31 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -10,7 +10,7 @@ #include #include #include - +#include #include "../src/HexCoding.h" #include @@ -24,17 +24,19 @@ using namespace std; /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct TWStoredKey *_Nonnull createAStoredKey(TWCoinType coin, const string& password) { +struct TWStoredKey *_Nonnull createAStoredKey(TWCoinType coin, TWData* password) { 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 password2 = WRAPS(TWStringCreateWithUTF8Bytes(password.c_str())); - const auto key = TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password2.get(), coin); + const auto key = TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password, coin); return key; } /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. struct TWStoredKey *_Nonnull createDefaultStoredKey() { - return createAStoredKey(TWCoinTypeBitcoin, "password"); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + return createAStoredKey(TWCoinTypeBitcoin, password.get()); } TEST(TWStoredKey, loadPBKDF2Key) { @@ -52,7 +54,8 @@ TEST(TWStoredKey, loadNonexistent) { TEST(TWStoredKey, createWallet) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); const auto key = TWStoredKeyCreate(name.get(), password.get()); const auto name2 = WRAPS(TWStoredKeyName(key)); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); @@ -66,7 +69,8 @@ TEST(TWStoredKey, importPrivateKey) { const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; const auto privateKey = WRAPD(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes(privateKeyHex))); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); + 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 = TWStoredKeyImportPrivateKey(privateKey.get(), name.get(), password.get(), coin); const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key, password.get())); @@ -83,7 +87,8 @@ TEST(TWStoredKey, importPrivateKey) { 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")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); + 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 = TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password.get(), coin); EXPECT_TRUE(TWStoredKeyIsMnemonic(key)); @@ -96,11 +101,13 @@ TEST(TWStoredKey, importHDWallet) { } TEST(TWStoredKey, addressAddRemove) { - const auto password = "password"; + 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); + const auto key = createAStoredKey(coin, password.get()); - const auto wallet = TWStoredKeyWallet(key, WRAPS(TWStringCreateWithUTF8Bytes(password)).get()); + const auto wallet = TWStoredKeyWallet(key, password.get()); const auto accountCoin = TWStoredKeyAccountForCoin(key, coin, wallet); const auto accountAddress = WRAPS(TWAccountAddress(accountCoin)); EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); @@ -119,6 +126,7 @@ TEST(TWStoredKey, addressAddRemove) { TWStoredKeyAddAccount(key, WRAPS(TWStringCreateWithUTF8Bytes(addressAdd)).get(), + TWCoinTypeBitcoin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath)).get(), WRAPS(TWStringCreateWithUTF8Bytes(extPubKeyAdd)).get()); EXPECT_EQ(TWStoredKeyAccountCount(key), 1); @@ -149,7 +157,7 @@ TEST(TWStoredKey, storeAndImportJSON) { ifstream ifs(outFileName); // get length of file: ifs.seekg (0, ifs.end); - int length = ifs.tellg(); + auto length = ifs.tellg(); ifs.seekg (0, ifs.beg); EXPECT_TRUE(length > 20); @@ -171,9 +179,11 @@ TEST(TWStoredKey, importJsonInvalid) { } TEST(TWStoredKey, fixAddresses) { - const auto password = "password"; - const auto key = createAStoredKey(TWCoinTypeBitcoin, password); - EXPECT_TRUE(TWStoredKeyFixAddresses(key, WRAPS(TWStringCreateWithUTF8Bytes(password)).get())); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + EXPECT_TRUE(TWStoredKeyFixAddresses(key, password.get())); TWStoredKeyDelete(key); } @@ -181,10 +191,11 @@ TEST(TWStoredKey, importInvalidKey) { auto bytes = TW::parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); auto name = WRAPS(TWStringCreateWithUTF8Bytes("test")); + auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(name.get())), TWStringSize(name.get()))); - auto eth = TWStoredKeyImportPrivateKey(data.get(), name.get(), name.get(), TWCoinTypeEthereum); - auto ont = TWStoredKeyImportPrivateKey(data.get(), name.get(), name.get(), TWCoinTypeOntology); - auto tezos = TWStoredKeyImportPrivateKey(data.get(), name.get(), name.get(), TWCoinTypeTezos); + auto eth = TWStoredKeyImportPrivateKey(data.get(), name.get(), password.get(), TWCoinTypeEthereum); + auto ont = TWStoredKeyImportPrivateKey(data.get(), name.get(), password.get(), TWCoinTypeOntology); + auto tezos = TWStoredKeyImportPrivateKey(data.get(), name.get(), password.get(), TWCoinTypeTezos); ASSERT_EQ(eth, nullptr); ASSERT_EQ(ont, nullptr); @@ -192,9 +203,11 @@ TEST(TWStoredKey, importInvalidKey) { } TEST(TWStoredKey, removeAccountForCoin) { - auto password = "password"; - auto key = TWStoredKeyCreate("Test KeyStore", password); - auto wallet = TWStoredKeyWallet(key, password); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + auto key = TWStoredKeyCreate("Test KeyStore", password.get()); + auto wallet = TWStoredKeyWallet(key, password.get()); ASSERT_NE(TWStoredKeyAccountForCoin(key, TWCoinTypeEthereum, wallet), nullptr); ASSERT_NE(TWStoredKeyAccountForCoin(key, TWCoinTypeBitcoin, wallet), nullptr); @@ -211,8 +224,12 @@ TEST(TWStoredKey, removeAccountForCoin) { TEST(TWStoredKey, getWalletPasswordInvalid) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto passwordInvalid = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); + const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); + auto key = TWStoredKeyCreate (name.get(), password.get()); ASSERT_NE(TWStoredKeyWallet(key, password.get()), nullptr); ASSERT_EQ(TWStoredKeyWallet(key, passwordInvalid.get()), nullptr); diff --git a/tests/interface/TWTestUtilities.cpp b/tests/interface/TWTestUtilities.cpp index 14d256a89bc..2c7412330c8 100644 --- a/tests/interface/TWTestUtilities.cpp +++ b/tests/interface/TWTestUtilities.cpp @@ -7,7 +7,6 @@ #include "TWTestUtilities.h" #include -#include using namespace std; diff --git a/tests/interface/TWTestUtilities.h b/tests/interface/TWTestUtilities.h index 11a0bbc6cd9..c2a7b6e4dfb 100644 --- a/tests/interface/TWTestUtilities.h +++ b/tests/interface/TWTestUtilities.h @@ -19,11 +19,11 @@ #define STRING(x) std::shared_ptr(TWStringCreateWithUTF8Bytes(x), TWStringDelete) #define DATA(x) std::shared_ptr(TWDataCreateWithHexString(STRING(x).get()), TWDataDelete) -inline void assertStringsEqual(std::shared_ptr& string, const char* expected) { +inline void assertStringsEqual(const std::shared_ptr& string, const char* expected) { ASSERT_STREQ(TWStringUTF8Bytes(string.get()), expected); } -inline void assertHexEqual(std::shared_ptr& data, const char* expected) { +inline void assertHexEqual(const std::shared_ptr& data, const char* expected) { auto hex = WRAPS(TWStringCreateWithHexData(data.get())); assertStringsEqual(hex, expected); } @@ -40,14 +40,21 @@ std::string getTestTempDir(void); 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()), TWDataSize(outputTWData.get()));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ + } +#define ANY_ENCODE(input, coin) \ + {\ + auto inputData = input.SerializeAsString();\ + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ + auto encodedData = WRAPD(TWAnySignerEncode(inputTWData.get(), coin));\ + encoded = TW::data(TWDataBytes(encodedData.get()), static_cast(TWDataSize(encodedData.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()), TWDataSize(outputTWData.get()));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ } #define DUMP_PROTO(input) \ { \ diff --git a/tools/android-release.sh b/tools/android-release.sh old mode 100644 new mode 100755 diff --git a/tools/coverage b/tools/coverage index aea89ceac39..a60dd5266d7 100755 --- a/tools/coverage +++ b/tools/coverage @@ -4,7 +4,23 @@ set -e -lcov --capture --directory . --output-file coverage.info +function install_llvm_gcov() { + cat << EOF > /tmp/llvm-gcov.sh +#!/bin/bash +exec /usr/bin/llvm-cov gcov "\$@" +EOF + sudo chmod 755 /tmp/llvm-gcov.sh +} + +if [[ `uname` == "Darwin" ]]; then + echo "gcov is llvm-cov on macOS" + lcov --capture --directory . --output-file coverage.info +else + echo "call llvm-cov on Linux" + install_llvm_gcov + lcov --gcov-tool /tmp/llvm-gcov.sh --capture --directory . --output-file coverage.info +fi + lcov --remove coverage.info '/usr/*' --output-file coverage.info lcov --remove coverage.info '/Applications/*' --output-file coverage.info lcov --remove coverage.info '*/build/*' --output-file coverage.info diff --git a/tools/docker-build b/tools/docker-build deleted file mode 100644 index ff76e32c3af..00000000000 --- a/tools/docker-build +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# -# This script builds the Docker image(s) - -docker build -t trustwallet/wallet-core:latest ./docker/wallet-core -#docker push trustwallet/wallet-core:latest diff --git a/tools/generate-files b/tools/generate-files index 20079f3c417..523835e157c 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -59,7 +59,6 @@ fi # Generate internal message protocol Protobuf files -- not every time "$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/IoTeX/Protobuf --cpp_out=src/IoTeX/Protobuf src/IoTeX/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 diff --git a/tools/install-dependencies b/tools/install-dependencies index 53536a5afd5..facc0e840d0 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -16,7 +16,7 @@ export LD_RUN_PATH="$PREFIX/lib" # Download Google Test export GTEST_VERSION=1.8.1 -GTEST_DIR="$ROOT/build/gtest/staging" +GTEST_DIR="$ROOT/build/local/src/gtest" mkdir -p "$GTEST_DIR" cd "$GTEST_DIR" if [ ! -f release-$GTEST_VERSION.tar.gz ]; then @@ -27,12 +27,13 @@ tar xzf release-$GTEST_VERSION.tar.gz # Build gtest cd googletest-release-$GTEST_VERSION cmake -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. -make +make -j4 make install +make clean # Download Check export CHECK_VERSION=0.12.0 -CHECK_DIR="$ROOT/build/check/staging" +CHECK_DIR="$ROOT/build/local/src/check" mkdir -p "$CHECK_DIR" cd "$CHECK_DIR" if [ ! -f check-$CHECK_VERSION.tar.gz ]; then @@ -45,10 +46,11 @@ cd check-$CHECK_VERSION ./configure --prefix="$PREFIX" make -j4 make install +make clean # Download Nlohmann JSON export JSON_VERSION=3.5.0 -JSON_DIR="$ROOT/build/json/staging" +JSON_DIR="$ROOT/build/local/json" mkdir -p "$JSON_DIR" cd "$JSON_DIR" if [ ! -f include.zip ]; then @@ -58,7 +60,7 @@ unzip -d "$PREFIX" -o include.zip # Download Protobuf sources export PROTOBUF_VERSION=3.9.0 -PROTOBUF_DIR="$ROOT/build/protobuf/staging" +PROTOBUF_DIR="$ROOT/build/local/src/protobuf" mkdir -p "$PROTOBUF_DIR" cd "$PROTOBUF_DIR" if [ ! -f protobuf-java-$PROTOBUF_VERSION.tar.gz ]; then @@ -75,10 +77,10 @@ make install make clean "$PREFIX/bin/protoc" --version -if [ -x "$(command -v swift)" ]; then +if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then # Download Swift Protobuf sources - export SWIFT_PROTOBUF_VERSION=1.7.0 - SWIFT_PROTOBUF_DIR="$ROOT/build/swift-protobuf/staging" + export SWIFT_PROTOBUF_VERSION=1.9.0 + SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" if [ ! -f $SWIFT_PROTOBUF_VERSION.tar.gz ]; then @@ -96,7 +98,8 @@ fi # Protobuf plugins cd "$ROOT/protobuf-plugin" cmake -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX -make -Cbuild +make -Cbuild -j12 make -Cbuild install +rm -rf build cd "$ROOT" diff --git a/tools/ios-build-framework b/tools/ios-build-framework index e0c3f66babc..811c1b2d8b3 100755 --- a/tools/ios-build-framework +++ b/tools/ios-build-framework @@ -34,7 +34,7 @@ function buildDevices() { function buildSimulators() { echo -e "\nBuilding for iOS Simulator..." - build "platform=iOS Simulator,name=iPhone 11,OS=13.3" "ios-sim" + build "platform=iOS Simulator,name=iPhone 11" "ios-sim" } function buildCatalyst() { @@ -66,8 +66,8 @@ function buildXCFramework() { function main() { init - buildDevices buildSimulators + buildDevices buildCatalyst buildMac buildXCFramework diff --git a/tools/ios-release b/tools/ios-release index a357705a9b0..8dca62ef2f3 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -5,9 +5,19 @@ require 'open3' require 'tempfile' +require 'json' version = ARGV[0] || `git describe --long --tags | cut -f 1 -d "-"`.strip +puts "Processing version: #{version}" + +# 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}') + # First build puts 'Building...' _, stderr, status = Open3.capture3('tools/ios-build') @@ -21,7 +31,7 @@ puts 'Archiving...' includes = Dir.glob('include/**/*.h') + Dir.glob('lib/**/*.{h,hpp}') sources = Dir.glob('swift/Sources/**/*.{swift,h,m}') libs = Dir.glob('build/ios/*.a') -files = includes + sources + libs +files = includes + sources + libs + ['LICENSE'] file_name = "TrustWalletCore-iOS-#{version}.zip" _, stderr, status = Open3.capture3('zip', file_name, *files) if status != 0 @@ -30,16 +40,21 @@ if status != 0 end # Upload archive -system("aws s3 cp #{file_name} s3://wallet-core/ --acl public-read") +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}" -# Upload Cocoapod +# Upload to Cocoapod puts 'Publishing...' podspec = <<-PODSPEC 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.homepage = 'https://github.com/trustwallet/wallet-core' s.license = 'MIT' s.authors = { 'Alejandro Isaza' => 'al@isaza.ca' } @@ -48,7 +63,7 @@ Pod::Spec.new do |s| s.swift_version = '5.1' s.source = { - http: "https://s3.amazonaws.com/wallet-core/TrustWalletCore-iOS-#{version}.zip" + http: "#{download_url}" } s.default_subspec = 'Core' diff --git a/tools/ios-test b/tools/ios-test index 1206d3c57a5..26489644a12 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -7,5 +7,5 @@ set -e pushd swift xcodegen pod install -xcrun xcodebuild -workspace TrustWalletCore.xcworkspace -scheme TrustWalletCore -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11,OS=13.0' test +fastlane scan --workspace TrustWalletCore.xcworkspace --scheme TrustWalletCore --sdk iphonesimulator --device='iPhone 11' --clean popd diff --git a/tools/lint-cppcheck-all b/tools/lint-cppcheck-all new file mode 100755 index 00000000000..09ee00310bf --- /dev/null +++ b/tools/lint-cppcheck-all @@ -0,0 +1,7 @@ +#!/bin/bash +# +# This script lints all of the C++ code with cppcheck. + +set -e + +find src \( -name "*.cpp" -o -name "*.h" \) -not -path "./src/proto/*" -not -path "./src/Tron/Protobuf/*" -exec cppcheck --force --quiet --enable=all '{}' \; diff --git a/tools/lint-cppcheck-diff b/tools/lint-cppcheck-diff new file mode 100755 index 00000000000..2b568769fea --- /dev/null +++ b/tools/lint-cppcheck-diff @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# +# This script lints the files modified in the current branch with cppcheck. + +merge_base = `git merge-base HEAD origin/master`.strip +changed_files = `git diff --name-only HEAD #{merge_base}`.strip.split(/\s/) +files = changed_files.select { |f| File.extname(f) == '.cpp' || File.extname(f) == '.h' } +if files.empty? + puts 'No files to lint' + exit 0 +end + +puts "Linting #{files.count} files" +system "cppcheck --quiet --force --enable=all #{files.join(' ')}" +exit $?.exitstatus diff --git a/tools/samples-build b/tools/samples-build new file mode 100755 index 00000000000..c956e7bc4c8 --- /dev/null +++ b/tools/samples-build @@ -0,0 +1,73 @@ +#!/bin/bash +# +# This script builds the sample applications. + +set -e + +if [ -z $1 ] +then + echo "No platform argument given, use all|linux|ios|android" + set $1 'linux' +fi + +if [ ! "$1" = "linux" ] && [ ! "$1" = "ios" ] && [ ! "$1" = "android" ] && [ ! "$1" = "all" ] +then + echo "Wrong platform argument given, use all|linux|ios|android" + set $1 'linux' +fi + +echo "Platform argument: "$1 + +build_cpp() { + pushd samples/cpp + echo "Building CPP sample app:"`pwd` + cmake . -DWALLET_CORE=../../ + make + echo "Building CPP sample app: done" + popd +} + +build_ios() { + pushd samples/osx/cocoapods + echo "Building OSX sample app:"`pwd` + pod repo update + pod install + echo "Building OSX sample app: done" + popd +} + +build_android() { + pushd samples/android + echo "Building Android sample app:"`pwd` + ./gradlew assembleDebug + echo "Building Android sample app: done" + popd +} + +build_go() { + pushd samples/go + echo "Building GO sample app:"`pwd` + go build -o main + echo "Building GO sample app: done" + popd +} + +if [ "$1" = "linux" ]; then + build_cpp + build_go +fi + +if [ "$1" = "ios" ]; then + build_ios +fi + +if [ "$1" = "android" ]; then + build_android +fi + +if [ "$1" = "all" ]; then + build_cpp + build_go + build_ios + build_android +fi diff --git a/tools/tests b/tools/tests index 1662d1c3e47..cff83bf35fb 100755 --- a/tools/tests +++ b/tools/tests @@ -5,7 +5,7 @@ set -e cmake -H. -Bbuild -make -Cbuild tests TrezorCryptoTests +make -Cbuild -j12 tests TrezorCryptoTests export CK_TIMEOUT_MULTIPLIER=4 build/trezor-crypto/tests/TrezorCryptoTests diff --git a/typescript/.gitignore b/typescript/.gitignore new file mode 100644 index 00000000000..1ea6743a064 --- /dev/null +++ b/typescript/.gitignore @@ -0,0 +1,2 @@ +dist/ +src/generated/ diff --git a/typescript/codegen/bin/codegen b/typescript/codegen/bin/codegen new file mode 100755 index 00000000000..bddbe68a570 --- /dev/null +++ b/typescript/codegen/bin/codegen @@ -0,0 +1,57 @@ +#! /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 new file mode 100644 index 00000000000..96eb09c76ae --- /dev/null +++ b/typescript/codegen/templates/core_types.ejs @@ -0,0 +1,23 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 new file mode 100644 index 00000000000..a04d833bfeb --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,45 @@ +{ + "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 new file mode 100644 index 00000000000..7fc83ba9b2e --- /dev/null +++ b/typescript/src/index.ts @@ -0,0 +1,10 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 new file mode 100644 index 00000000000..58d33709d7a --- /dev/null +++ b/typescript/tests/index.test.ts @@ -0,0 +1,66 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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'), + 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("0a01011201091a0504a817c800220252082a2a30783335333533353335333533353335333533353335333533353335333533353335333533353335333532080de0b6b3a764000042204646464646464646464646464646464646464646464646464646464646464646") + }) + + 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 new file mode 100755 index 00000000000..80f83b2e888 --- /dev/null +++ b/typescript/tools/check-gpr-version @@ -0,0 +1,17 @@ +#!/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 new file mode 100755 index 00000000000..bea97300bf1 --- /dev/null +++ b/typescript/tools/check-npm-version @@ -0,0 +1,13 @@ +#!/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 new file mode 100755 index 00000000000..336c57f830e --- /dev/null +++ b/typescript/tools/set-tag-version @@ -0,0 +1,9 @@ +#!/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 new file mode 100644 index 00000000000..808eaf5fc74 --- /dev/null +++ b/typescript/tsconfig.json @@ -0,0 +1,20 @@ +{ + "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 new file mode 100644 index 00000000000..605b4f9222d --- /dev/null +++ b/typescript/yarn.lock @@ -0,0 +1,1227 @@ +# 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.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +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/lib/Address.cpp b/walletconsole/lib/Address.cpp index 09b89ac32b5..7b2ef8a3a0e 100644 --- a/walletconsole/lib/Address.cpp +++ b/walletconsole/lib/Address.cpp @@ -79,17 +79,17 @@ bool Address::addrDefault(const string& coinid, string& res) { return true; } -bool Address::addrDP(const string& coinid, const string& derivPath, 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; + DerivationPath dp(derivPath); // get the private key string mnemo = _keys.getMnemo(); assert(mnemo.length() > 0); // a mnemonic is always set HDWallet wallet(mnemo, ""); - PrivateKey priKey = wallet.getKey(dp); - - Coin coin; - if (!_coins.findCoin(coinid, coin)) { return false; } - TWCoinType ctype = (TWCoinType)coin.c; + PrivateKey priKey = wallet.getKey(ctype, dp); // derive address res = TW::deriveAddress(ctype, priKey); diff --git a/walletconsole/lib/Address.h b/walletconsole/lib/Address.h index d1cc1906085..5e512ab3f68 100644 --- a/walletconsole/lib/Address.h +++ b/walletconsole/lib/Address.h @@ -32,7 +32,7 @@ class Address { /// Derive a default address, using default coin and current mnemonic bool addrDefault(const string& coinid, string& res); /// Derive a new address with the given derivation path - bool addrDP(const string& coinid, const string& derivPath, string& res); + bool deriveFromPath(const string& coinid, const string& derivPath, string& res); }; } // namespace TW::WalletConsole diff --git a/walletconsole/lib/CMakeLists.txt b/walletconsole/lib/CMakeLists.txt index 8e740a6bb4f..b1ac8a1afac 100644 --- a/walletconsole/lib/CMakeLists.txt +++ b/walletconsole/lib/CMakeLists.txt @@ -2,7 +2,7 @@ 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_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") diff --git a/walletconsole/lib/CommandExecutor.cpp b/walletconsole/lib/CommandExecutor.cpp index 20b36d3124f..2f9342066ed 100644 --- a/walletconsole/lib/CommandExecutor.cpp +++ b/walletconsole/lib/CommandExecutor.cpp @@ -131,7 +131,7 @@ bool CommandExecutor::executeOne(const string& cmd, const vector& params if (cmd == "addrpri") { if (!checkMinParams(params, 1)) { return false; } return _address.addrPri(_activeCoin, params[1], res); } if (cmd == "addr") { if (!checkMinParams(params, 1)) { return false; } return _address.addr(_activeCoin, params[1], res); } if (cmd == "addrdefault") { return _address.addrDefault(_activeCoin, res); } - if (cmd == "addrdp") { if (!checkMinParams(params, 1)) { return false; } return _address.addrDP(_activeCoin, params[1], res); } + if (cmd == "addrdp") { if (!checkMinParams(params, 1)) { return false; } return _address.deriveFromPath(_activeCoin, params[1], res); } if (cmd == "toninitmsg") { if (!checkMinParams(params, 1)) { return false; } setCoin("ton", false); return TonCoin::tonInitMsg(params[1], res); } diff --git a/walletconsole/lib/Keys.cpp b/walletconsole/lib/Keys.cpp index cf0ab045fd5..e73a2d2754c 100644 --- a/walletconsole/lib/Keys.cpp +++ b/walletconsole/lib/Keys.cpp @@ -45,7 +45,7 @@ bool Keys::newKey(const string& coinid, string& res) { HDWallet newWallet(256, ""); DerivationPath derivationPath = DerivationPath(coin.derivPath); - PrivateKey key = newWallet.getKey(derivationPath); + PrivateKey key = newWallet.getKey(TWCoinType(coin.c), derivationPath); privateKeyToResult(key, res); return true; } @@ -152,7 +152,7 @@ bool Keys::priDP(const string& coinid, const string& dp, string& res) { _out << "Using derivation path \"" << dp2 << "\" for coin " << coin.name << endl; HDWallet wallet(_currentMnemonic, ""); - PrivateKey priKey = wallet.getKey(dp3); + PrivateKey priKey = wallet.getKey(TWCoinType(coin.c), dp3); privateKeyToResult(priKey, res); return true; diff --git a/walletconsole/lib/Util.cpp b/walletconsole/lib/Util.cpp index 5a54509998e..689b35c5143 100644 --- a/walletconsole/lib/Util.cpp +++ b/walletconsole/lib/Util.cpp @@ -74,14 +74,15 @@ bool Util::fileR(const string& fileName, string& res) { ifstream infile(fileName, std::ios::in | std::ios::binary); // get length of file: infile.seekg (0, infile.end); - int length = infile.tellg(); + auto length = infile.tellg(); infile.seekg (0, infile.beg); char* buffer = new char[length]; if (!infile.read(buffer, length)) { _out << "Could not read file '" << fileName << "'" << endl; + delete[] buffer; return false; } - int red = infile.gcount(); + auto red = infile.gcount(); infile.close(); res = string(TW::hex(data((const byte*)buffer, red))); delete[] buffer;