diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index a575fe792ea..b8d6bb34c8a 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -12,7 +12,7 @@ concurrency: jobs: build: - runs-on: [ self-hosted, macOS, ARM64, wallet-core ] + runs-on: macos-latest-large if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 @@ -29,6 +29,15 @@ jobs: - name: Install system dependencies run: | tools/install-sys-dependencies-mac + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | tools/install-rust-dependencies - name: Install Android Dependencies @@ -39,7 +48,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }} - name: Install internal dependencies run: tools/install-dependencies @@ -51,8 +60,21 @@ jobs: - name: Build Kotlin doc run: tools/kotlin-doc - - name: Run test - run: tools/android-test + - name: Build tests + run: | + pushd android + ./gradlew assembleAndroidTest + popd + + - name: Run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 30 + target: google_apis + arch: x86 + ndk: 23.1.7779620 + cmake: 3.18.1 + script: cd android; ./gradlew connectedAndroidTest - name: Build sample app run: tools/samples-build android diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 7dcc89a194b..649b15e84a2 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -12,24 +12,37 @@ concurrency: jobs: build: - runs-on: [ self-hosted, macOS, ARM64, wallet-core ] + runs-on: macos-latest-xlarge if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 + - name: Install system dependencies run: | tools/install-sys-dependencies-mac + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | tools/install-rust-dependencies + - name: Cache internal dependencies id: internal_cache uses: actions/cache@v3 with: path: build/local key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies run: | tools/install-dependencies if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Run codegen tests run: tools/codegen-test @@ -37,6 +50,7 @@ jobs: run: | tools/generate-files ios tools/ios-test + - name: Build sample app run: | tools/samples-build ios diff --git a/.github/workflows/kotlin-ci.yml b/.github/workflows/kotlin-ci.yml index 2a158233357..1198c20ae3c 100644 --- a/.github/workflows/kotlin-ci.yml +++ b/.github/workflows/kotlin-ci.yml @@ -12,7 +12,7 @@ concurrency: jobs: build: - runs-on: [ self-hosted, macOS, ARM64, wallet-core ] + runs-on: macos-latest-xlarge if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 @@ -23,6 +23,9 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: @@ -31,6 +34,15 @@ jobs: - name: Install system dependencies run: | tools/install-sys-dependencies-mac + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | tools/install-rust-dependencies - name: Install emsdk @@ -59,10 +71,13 @@ jobs: run: | cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_UNITY_BUILD=ON -DTW_COMPILE_JAVA=ON -DTW_COMPILE_KOTLIN=ON -GNinja - # The build artifact will be used in `tools/kotlin-build` in the future. - name: Build JNI run: | ninja -Cbuild + mv build/libTrustWalletCore.dylib kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/ - name: Build Kotlin Multiplatform run: tools/kotlin-build + + - name: Run Kotlin Multiplatform tests + run: tools/kotlin-test diff --git a/.github/workflows/kotlin-sample-ci.yml b/.github/workflows/kotlin-sample-ci.yml index 4c42e44dcd5..f96b2a4b280 100644 --- a/.github/workflows/kotlin-sample-ci.yml +++ b/.github/workflows/kotlin-sample-ci.yml @@ -12,7 +12,7 @@ concurrency: jobs: build: - runs-on: [ self-hosted, macOS, ARM64, wallet-core ] + runs-on: macos-latest-xlarge if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 @@ -23,6 +23,9 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Install Kotlin Dependencies run: tools/install-kotlin-dependencies diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml index d255e25bb7b..e0dcee23cfd 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/linux-ci-rust.yml @@ -40,6 +40,9 @@ jobs: run: | tools/install-rust-dependencies dev + - name: Install emsdk + run: tools/install-wasm-dependencies + - name: Check code formatting run: | cargo fmt --check @@ -55,6 +58,9 @@ jobs: cargo llvm-cov nextest --profile ci --no-fail-fast --lcov --output-path coverage.info working-directory: rust + - name: Run tests in WASM + run: tools/rust-test wasm + - name: Rust Test Report uses: dorny/test-reporter@v1 if: success() || failure() @@ -68,3 +74,18 @@ jobs: - name: Gather and check Rust code coverage run: | tools/check-coverage rust/coverage.stats rust/coverage.info + + # Generate files for a blockchain in the end of the pipeline. + # Please note the blockchain should not be implemented in Rust at the moment of running this step, + # otherwise consider either generate files for another blockchain or remove this step at all. + - name: Test codegen-v2 new-blockchain-rust + run: | + cargo run -- new-blockchain-rust iotex + working-directory: codegen-v2 + + # Check if `new-blockchain-rust` command has generated files that do not break project compilation. + - name: Check Rust compiles + run: | + cargo check --tests + working-directory: rust + diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 8eb3d94358e..239858760cc 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -15,6 +15,12 @@ jobs: runs-on: ubuntu-latest if: github.event.pull_request.draft == false steps: + - name: Remove GCC 13 from runner image + shell: bash + run: | + sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list + sudo apt-get update + sudo apt-get install -y --allow-downgrades libc6=2.35-0ubuntu3.4 libc6-dev=2.35-0ubuntu3.4 libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04 - uses: actions/checkout@v3 - name: Install system dependencies run: | diff --git a/.github/workflows/linux-sampleapp-ci.yml b/.github/workflows/linux-sampleapp-ci.yml index ec1b8ec665d..fa44291f2f4 100644 --- a/.github/workflows/linux-sampleapp-ci.yml +++ b/.github/workflows/linux-sampleapp-ci.yml @@ -15,6 +15,12 @@ jobs: runs-on: ubuntu-latest if: github.event.pull_request.draft == false steps: + - name: Remove GCC 13 from runner image + shell: bash + run: | + sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list + sudo apt-get update + sudo apt-get install -y --allow-downgrades libc6=2.35-0ubuntu3.4 libc6-dev=2.35-0ubuntu3.4 libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04 - uses: actions/checkout@v3 - name: Install system dependencies run: | diff --git a/Dockerfile b/Dockerfile index ff8083071cf..7db09d1bd5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,7 @@ RUN apt-get update \ software-properties-common \ && apt-get clean && rm -rf /var/lib/apt/lists/* -# Add latest cmake SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - \ - && apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -sc) main" # Install required packages for dev RUN apt-get update \ 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 dc52b3f81be..7e44af34e2e 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 @@ -45,7 +45,7 @@ class CoinAddressDerivationTests { ETHEREUM, SMARTCHAIN, POLYGON, OPTIMISM, ZKSYNC, ARBITRUM, ARBITRUMNOVA, ECOCHAIN, AVALANCHECCHAIN, XDAI, FANTOM, CELO, CRONOSCHAIN, SMARTBITCOINCASH, KUCOINCOMMUNITYCHAIN, BOBA, METIS, AURORA, EVMOS, MOONRIVER, MOONBEAM, KAVAEVM, KLAYTN, METER, OKXCHAIN, POLYGONZKEVM, SCROLL, - CONFLUXESPACE, ACALAEVM, OPBNB, NEON, BASE, LINEA, GREENFIELD -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + CONFLUXESPACE, ACALAEVM, OPBNB, NEON, BASE, LINEA, GREENFIELD, MANTLE, ZENEON -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) RONIN -> assertEquals("ronin:8f348F300873Fd5DA36950B2aC75a26584584feE", address) ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address) GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address) @@ -57,7 +57,7 @@ class CoinAddressDerivationTests { XRP -> assertEquals("rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3", address) TEZOS -> assertEquals("tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX", address) THUNDERCORE -> assertEquals("0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7", address) - TOMOCHAIN -> assertEquals("0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424", address) + VICTION -> assertEquals("0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424", address) TRON -> assertEquals("TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio", address) VECHAIN -> assertEquals("0x1a553275dF34195eAf23942CB7328AcF9d48c160", address) WANCHAIN -> assertEquals("0xD5ca90b928279FE5D06144136a25DeD90127aC15", address) @@ -119,7 +119,7 @@ class CoinAddressDerivationTests { NERVOS -> assertEquals("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3", address) EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address) TON -> assertEquals("EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9", address) - APTOS -> assertEquals("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) + APTOS -> assertEquals("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) NEBL -> assertEquals("NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7", address) SUI -> assertEquals("0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2", address) HEDERA -> assertEquals("0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5", address) @@ -146,5 +146,7 @@ class CoinAddressDerivationTests { NOBLE -> assertEquals("noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa", address) ROOTSTOCK -> assertEquals("0xA2D7065F94F838a3aB9C04D67B312056846424Df", address) SEI -> assertEquals("sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj", address) + INTERNETCOMPUTER -> assertEquals("b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87", address) + TIA -> assertEquals("celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt index 5b92dd34849..d565a1a08be 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt @@ -32,7 +32,7 @@ class TestCoinType { assertEquals(CoinType.POANETWORK.value(), 178) assertEquals(CoinType.VECHAIN.value(), 818) assertEquals(CoinType.ICON.value(), 74) - assertEquals(CoinType.TOMOCHAIN.value(), 889) + assertEquals(CoinType.VICTION.value(), 889) assertEquals(CoinType.TEZOS.value(), 1729) assertEquals(CoinType.QTUM.value(), 2301) assertEquals(CoinType.NEBULAS.value(), 2718) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt index ed829d1ce76..4de24f9b60f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt @@ -67,7 +67,7 @@ class TestCosmosTransactions { assertEquals( output.serialized, - "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\"}" + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=\"}" ) assertEquals(output.errorMessage, "") } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index b1e90097602..235cd442b73 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -127,7 +127,7 @@ class TestBarz { assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae"); - assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16db98b365b1f89191996942612b14f1da4bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"); + assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"); } // https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 @@ -169,7 +169,7 @@ class TestBarz { assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937"); - assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392ae041bfbdbaa0cff9234a0c8f64df97b7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}") + assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}") } // https://testnet.bscscan.com/tx/0x872f709815a9f79623a349f2f16d93b52c4d5136967bab53a586f045edbe9203 @@ -224,6 +224,6 @@ class TestBarz { val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab"); - assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1e6c542ebc7c960c6a155a9094db838cef842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") + assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt index 1edb4127f62..b6ad3654115 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt @@ -1,10 +1,12 @@ package com.trustwallet.core.app.blockchains.ethereum +import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.EthereumAbi +import wallet.core.jni.CoinType +import wallet.core.jni.proto.EthereumAbi class TestEthereumAbiDecoder { @@ -35,15 +37,15 @@ class TestEthereumAbiDecoder { } """.trimIndent() - val decoded = EthereumAbi.decodeCall(call, abi) - val expected = """{"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"},{"name":"_value","type":"uint256","value":"1"}]}""" + val decoded = wallet.core.jni.EthereumAbi.decodeCall(call, abi) + val expected = """{"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"},{"name":"_value","type":"uint256","value":"1"}]}""" assertEquals(decoded, expected) } @Test fun testEthereumAbiEncodeTyped() { - val hash = EthereumAbi.encodeTyped( + val hash = wallet.core.jni.EthereumAbi.encodeTyped( """ { "types": { @@ -94,4 +96,117 @@ class TestEthereumAbiDecoder { """) assertEquals(Numeric.toHexString(hash), "0xa85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2") } + + @Test + fun testEthereumAbiEncodeFunction() { + val amountIn = EthereumAbi.NumberNParam.newBuilder().apply { + bits = 256 + value = ByteString.copyFrom(Numeric.hexStringToByteArray("0xde0b6b3a7640000")) // 1000000000000000000 + } + val amountOutMin = EthereumAbi.NumberNParam.newBuilder().apply { + bits = 256 + value = ByteString.copyFrom(Numeric.hexStringToByteArray("0x229f7e501ad62bdb")) // 2494851601099271131 + } + val deadline = EthereumAbi.NumberNParam.newBuilder().apply { + bits = 256 + value = ByteString.copyFrom(Numeric.hexStringToByteArray("0x5f0ed070")) // 1594806384 + } + val addressType = EthereumAbi.AddressType.newBuilder().build() + + val encodingInput = EthereumAbi.FunctionEncodingInput.newBuilder() + .setFunctionName("swapExactTokensForTokens") + .addTokens(EthereumAbi.Token.newBuilder().setNumberUint(amountIn)) + .addTokens(EthereumAbi.Token.newBuilder().setNumberUint(amountOutMin)) + .addTokens(EthereumAbi.Token.newBuilder().apply { + array = EthereumAbi.ArrayParam.newBuilder() + .setElementType(EthereumAbi.ParamType.newBuilder().setAddress(addressType)) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F")) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2")) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0xE41d2489571d322189246DaFA5ebDe1F4699F498")) + .build() + }) + .addTokens(EthereumAbi.Token.newBuilder().setAddress("0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1")) + .addTokens(EthereumAbi.Token.newBuilder().setNumberUint(deadline)) + .build() + + val encodingOutputData = wallet.core.jni.EthereumAbi.encodeFunction(CoinType.ETHEREUM, encodingInput.toByteArray()) + val encodingOutput = EthereumAbi.FunctionEncodingOutput.parseFrom(encodingOutputData) + + assertEquals(encodingOutput.error, EthereumAbi.AbiError.OK) + assertEquals(encodingOutput.functionType, "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)") + + val expectedEncoded = ByteString.copyFrom(Numeric.hexStringToByteArray("0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000229f7e501ad62bdb00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f1000000000000000000000000000000000000000000000000000000005f0ed07000000000000000000000000000000000000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498")) + assertEquals(encodingOutput.encoded, expectedEncoded) + } + + @Test + fun testEthereumAbiDecodeParams() { + val encoded = ByteString.copyFrom(Numeric.hexStringToByteArray("00000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b0c0000000000000000000000000000000000000000000000000000000000000001")) + + val addressType = EthereumAbi.AddressType.newBuilder().build() + val boolType = EthereumAbi.BoolType.newBuilder().build() + + val params = EthereumAbi.AbiParams.newBuilder() + .addParams(EthereumAbi.Param.newBuilder().apply { + name = "to" + param = EthereumAbi.ParamType.newBuilder().setAddress(addressType).build() + }) + .addParams(EthereumAbi.Param.newBuilder().apply { + name = "approved" + param = EthereumAbi.ParamType.newBuilder().setBoolean(boolType).build() + }) + val decodingInput = EthereumAbi.ParamsDecodingInput.newBuilder() + .setEncoded(encoded) + .setAbiParams(params) + .build() + + val decodingOutputData = wallet.core.jni.EthereumAbi.decodeParams(CoinType.ETHEREUM, decodingInput.toByteArray()) + val decodingOutput = EthereumAbi.ParamsDecodingOutput.parseFrom(decodingOutputData) + + assertEquals(decodingOutput.error, EthereumAbi.AbiError.OK) + + assertEquals(decodingOutput.getTokens(0).name, "to") + assertEquals(decodingOutput.getTokens(0).address, "0x88341d1a8F672D2780C8dC725902AAe72F143B0c") + + assertEquals(decodingOutput.getTokens(1).name, "approved") + assertEquals(decodingOutput.getTokens(1).boolean, true) + } + + @Test + fun testEthereumAbiDecodeValue() { + val encoded = ByteString.copyFrom(Numeric.hexStringToByteArray("000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000")) + + val decodingInput = EthereumAbi.ValueDecodingInput.newBuilder() + .setEncoded(encoded) + .setParamType("string") + .build() + + val decodingOutputData = wallet.core.jni.EthereumAbi.decodeValue(CoinType.ETHEREUM, decodingInput.toByteArray()) + val decodingOutput = EthereumAbi.ValueDecodingOutput.parseFrom(decodingOutputData) + + assertEquals(decodingOutput.error, EthereumAbi.AbiError.OK) + assertEquals(decodingOutput.token.stringValue, "Hello World! Hello World! Hello World!") + } + + @Test + fun testEthereumAbiDecodeContractCall() { + val encoded = ByteString.copyFrom(Numeric.hexStringToByteArray("c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000")) + val abi = """{"c47f0027":{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}}""" + val decodingInput = EthereumAbi.ContractCallDecodingInput.newBuilder() + .setEncoded(encoded) + .setSmartContractAbiJson(abi) + .build() + + val decodingOutputData = wallet.core.jni.EthereumAbi.decodeContractCall(CoinType.ETHEREUM, decodingInput.toByteArray()) + val decodingOutput = EthereumAbi.ContractCallDecodingOutput.parseFrom(decodingOutputData) + + assertEquals(decodingOutput.error, EthereumAbi.AbiError.OK) + + val expectedJson = """{"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]}""" + assertEquals(decodingOutput.decodedJson, expectedJson) + + assertEquals(decodingOutput.getTokens(0).name, "name") + assertEquals(decodingOutput.getTokens(0).stringValue, "deadbeef") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt index 02a2680b3e5..5afd77932f9 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt @@ -87,7 +87,7 @@ class TestEthereumAbiValue { fun testValueDecoderValue() { assertEquals("42", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("000000000000000000000000000000000000000000000000000000000000002a"), "uint")) assertEquals("24", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("0000000000000000000000000000000000000000000000000000000000000018"), "uint8")) - assertEquals("0xf784682c82526e245f50975190ef0fff4e4fc077", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")) + assertEquals("0xF784682C82526e245F50975190EF0fff4E4fC077", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")) assertEquals("Hello World! Hello World! Hello World!", EthereumAbiValue.decodeValue( Numeric.hexStringToByteArray("000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000"), "string")) @@ -103,7 +103,7 @@ class TestEthereumAbiValue { @Test fun testValueDecoderArray_address() { val input = Numeric.hexStringToByteArray("0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3") - assertEquals("[\"0xf784682c82526e245f50975190ef0fff4e4fc077\",\"0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3\"]", EthereumAbiValue.decodeArray(input, "address[]")) + assertEquals("[\"0xF784682C82526e245F50975190EF0fff4E4fC077\",\"0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3\"]", EthereumAbiValue.decodeArray(input, "address[]")) } @Test diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt new file mode 100644 index 00000000000..80abb39ff7f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt @@ -0,0 +1,57 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Common +import wallet.core.jni.proto.EthereumRlp + +class TestEthereumRlp { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumRlpEncodeEip1559() { + val chainId = ByteString.copyFrom("0x0a".toHexByteArray()) + val nonce = ByteString.copyFrom("0x06".toHexByteArray()) + val maxInclusionFeePerGas = ByteString.copyFrom("0x77359400".toHexByteArray()) // 2000000000 + val maxFeePerGas = ByteString.copyFrom("0xb2d05e00".toHexByteArray()) // 3000000000 + val gasLimit = ByteString.copyFrom("0x526c".toHexByteArray()) // 21100 + val to = "0x6b175474e89094c44da98b954eedeac495271d0f" + val amount = 0.toLong() + val payload = ByteString.copyFrom("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1".toHexByteArray()) + // An empty `accessList`. + val accessList = EthereumRlp.RlpList.newBuilder().build() + + val rlpList = EthereumRlp.RlpList.newBuilder() + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(chainId)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(nonce)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(maxInclusionFeePerGas)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(maxFeePerGas)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(gasLimit)) + .addItems(EthereumRlp.RlpItem.newBuilder().setAddress(to)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU64(amount)) + .addItems(EthereumRlp.RlpItem.newBuilder().setData(payload)) + .addItems(EthereumRlp.RlpItem.newBuilder().setList(accessList)) + .build() + + val encodingInput = EthereumRlp.EncodingInput.newBuilder().apply { + item = EthereumRlp.RlpItem.newBuilder().setList(rlpList).build() + }.build() + + val outputData = wallet.core.jni.EthereumRlp.encode(CoinType.ETHEREUM, encodingInput.toByteArray()) + val output = EthereumRlp.EncodingOutput.parseFrom(outputData) + + assertEquals(output.error, Common.SigningError.OK) + assert(output.errorMessage.isEmpty()) + assertEquals( + Numeric.toHexString(output.encoded.toByteArray()), + "0xf86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0" + ) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt new file mode 100644 index 00000000000..f4b9044e339 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.internetcomputer + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestInternetComputerAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false); + val address = AnyAddress(pubkey, CoinType.INTERNETCOMPUTER) + val expected = AnyAddress("2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", CoinType.INTERNETCOMPUTER) + + assertEquals(pubkey.data().toHex(), "0x048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8") + assertEquals(address.description(), expected.description()) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt new file mode 100644 index 00000000000..87a4261a158 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt @@ -0,0 +1,81 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.internetcomputer + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.* +import wallet.core.jni.proto.InternetComputer +import wallet.core.jni.proto.InternetComputer.SigningOutput + +class TestInternetComputerSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun InternetComputerTransactionSigning() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + amount = 100000000 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.signedTransaction.toByteArray().toHex(), "0x81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2") + } + + @Test + fun InternetComputerTransactionSigningWithInvalidToAccountIdentifier() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b" + amount = 100000000 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.error.number, 16) + } + + @Test + fun InternetComputerTransactionSigningWithInvalidAmount() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + amount = 0 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.error.number, 23) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt index d67f8d11559..eb40bbf1892 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt @@ -99,5 +99,10 @@ class TestNEOSigner { assertEquals( "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", hex) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // assertEquals( + // "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", + // hex) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt index abff3b06c6c..2bf777966fb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt @@ -76,6 +76,11 @@ class TestOntologySigning { assertEquals( "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // assertEquals( + // "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex) } @Test @@ -100,6 +105,11 @@ class TestOntologySigning { assertEquals( "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8aefaa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d743b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // assertEquals( + // "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt index aeb9239e671..d84bccd69c5 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt @@ -80,69 +80,10 @@ class TestPrivateKey { assertNotNull(privateKey) } - @Test - fun testGetSharedKey() { - val privateKeyData = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey = PrivateKey(privateKeyData) - - val publicKeyData = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) - - val derivedData = privateKey.getSharedKey(publicKey, Curve.SECP256K1) - assertNotNull(derivedData) - - assertEquals(derivedData?.toHex(), "0xef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a") - } - @Test fun testGetPublicKeyCoinType() { val privateKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5".toHexBytes() val privateKey = PrivateKey(privateKeyData) assertEquals(privateKey.getPublicKey(CoinType.ETHEREUM).data().toHex(), "0x0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); } - - @Test - fun testGetSharedKeyWycherproof() { - val privateKeyData = "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254".toHexBytes() - val privateKey = PrivateKey(privateKeyData) - - val publicKeyData = "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) - - val derivedData = privateKey.getSharedKey(publicKey, Curve.SECP256K1) - assertNotNull(derivedData) - - assertEquals(derivedData?.toHex(), "0x81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a") - } - - @Test - fun testGetSharedKeyBidirectional() { - val privateKeyData1 = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey1 = PrivateKey(privateKeyData1) - val publicKey1 = privateKey1.getPublicKeySecp256k1(true) - - val privateKeyData2 = "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a".toHexBytes() - val privateKey2 = PrivateKey(privateKeyData2) - val publicKey2 = privateKey2.getPublicKeySecp256k1(true) - - val derivedData1 = privateKey1.getSharedKey(publicKey2, Curve.SECP256K1) - assertNotNull(derivedData1) - - val derivedData2 = privateKey2.getSharedKey(publicKey1, Curve.SECP256K1) - assertNotNull(derivedData2) - - assertEquals(derivedData1?.toHex(), derivedData2?.toHex()) - } - - @Test - fun testGetSharedKeyError() { - val privateKeyData = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey = PrivateKey(privateKeyData) - - val publicKeyData = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) - - val derivedData = privateKey.getSharedKey(publicKey, Curve.ED25519) - assertNull(derivedData) - } } \ No newline at end of file diff --git a/codegen-v2/Cargo.lock b/codegen-v2/Cargo.lock index 32a0596b5ae..6caac47e7df 100644 --- a/codegen-v2/Cargo.lock +++ b/codegen-v2/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -27,11 +36,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "codegen-v2" version = "0.1.0" dependencies = [ + "aho-corasick", + "convert_case", "handlebars", "heck", + "pathdiff", "serde", "serde_json", "serde_yaml", + "toml_edit", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", ] [[package]] @@ -63,6 +85,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "generic-array" version = "0.14.7" @@ -93,6 +121,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + [[package]] name = "heck" version = "0.4.1" @@ -106,7 +140,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.2", ] [[package]] @@ -130,12 +174,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pest" version = "2.5.7" @@ -241,7 +297,7 @@ version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa", "ryu", "serde", @@ -290,6 +346,23 @@ dependencies = [ "syn", ] +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + [[package]] name = "typenum" version = "1.16.0" @@ -308,6 +381,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unsafe-libyaml" version = "0.2.8" @@ -319,3 +398,12 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml index a8906c0ae94..52ad244f84a 100644 --- a/codegen-v2/Cargo.toml +++ b/codegen-v2/Cargo.toml @@ -3,8 +3,6 @@ name = "codegen-v2" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] name = "libparser" path = "src/lib.rs" @@ -14,8 +12,12 @@ name = "parser" path = "src/main.rs" [dependencies] +aho-corasick = "1.1.2" +convert_case = "0.6.0" +pathdiff = "0.2.1" serde = { version = "1.0.159", features = ["derive"] } serde_json = "1.0.95" serde_yaml = "0.9.21" +toml_edit = "0.21.0" handlebars = "4.3.6" heck = "0.4.1" diff --git a/codegen-v2/README.md b/codegen-v2/README.md index 11490caee31..33e018ac407 100644 --- a/codegen-v2/README.md +++ b/codegen-v2/README.md @@ -2,7 +2,7 @@ This is a _work-in-progress_ parser meant to deprecate the existing Ruby parser in `codegen/`. As of now, we only support Swift binding generation. This project -will progess over multiple stages (PRs). +will progress over multiple stages (PRs). ## Execution diff --git a/codegen-v2/manifest/TWCoinType.yaml b/codegen-v2/manifest/TWCoinType.yaml index 5489b74cf98..872c3050766 100644 --- a/codegen-v2/manifest/TWCoinType.yaml +++ b/codegen-v2/manifest/TWCoinType.yaml @@ -179,7 +179,7 @@ enums: - name: boba value: 10000288 - name: metis - value: 1001088 + value: 10001088 - name: aurora value: 1323161554 - name: evmos diff --git a/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs b/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs new file mode 100644 index 00000000000..d0b4fcd552a --- /dev/null +++ b/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs @@ -0,0 +1,77 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::cpp_source_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_INCLUDES_END: &str = "end_of_coin_includes_marker_do_not_modify"; +const COIN_DISPATCHER_DECLARATIONS_END: &str = + "end_of_coin_dipatcher_declarations_marker_do_not_modify"; +const COIN_DISPATCHER_SWITCH_END: &str = "end_of_coin_dipatcher_switch_marker_do_not_modify"; + +fn dispatcher_coin_cpp_path() -> PathBuf { + cpp_source_directory().join("Coin.cpp") +} + +/// Represents `Coin.cpp`. +pub struct BlockchainDispatcherGenerator; + +impl BlockchainDispatcherGenerator { + pub fn generate_new_blockchain_type_dispatching(coin: &CoinItem) -> Result<()> { + let dispatcher_path = dispatcher_coin_cpp_path(); + println!("[EDIT] {dispatcher_path:?}"); + let mut file_content = FileContent::read(dispatcher_path)?; + + Self::generate_include_of_blockchain_entry(coin, &mut file_content)?; + Self::generate_blockchain_entry_constant(coin, &mut file_content)?; + Self::generate_blockchain_dispatcher_case(coin, &mut file_content)?; + + file_content.write() + } + + fn generate_include_of_blockchain_entry( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut line_marker = file_content.rfind_line(|line| line.contains(COIN_INCLUDES_END))?; + line_marker.push_line_before(format!(r#"#include "{blockchain_type}/Entry.h""#)); + + Ok(()) + } + + fn generate_blockchain_entry_constant( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut entries_region = + file_content.rfind_line(|line| line.contains(COIN_DISPATCHER_DECLARATIONS_END))?; + entries_region.push_line_before(format!("{blockchain_type}::Entry {blockchain_type}DP;")); + + Ok(()) + } + + fn generate_blockchain_dispatcher_case( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut entries_region = + file_content.rfind_line(|line| line.contains(COIN_DISPATCHER_SWITCH_END))?; + entries_region.push_line_before(format!( + " case TWBlockchain{blockchain_type}: entry = &{blockchain_type}DP; break;" + )); + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/entry_generator.rs b/codegen-v2/src/codegen/cpp/entry_generator.rs new file mode 100644 index 00000000000..49f341ab35f --- /dev/null +++ b/codegen-v2/src/codegen/cpp/entry_generator.rs @@ -0,0 +1,42 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::cpp_source_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const ENTRY_HEADER_TEMPLATE: &str = include_str!("templates/Entry.h"); + +pub fn coin_source_directory(coin: &CoinItem) -> PathBuf { + cpp_source_directory().join(coin.blockchain_type()) +} + +pub struct EntryGenerator; + +impl EntryGenerator { + pub fn generate(coin: &CoinItem) -> Result { + let blockchain_dir = coin_source_directory(coin); + let entry_header_path = blockchain_dir.join("Entry.h"); + + if blockchain_dir.exists() { + println!("[SKIP] Entry file already exists: {blockchain_dir:?}"); + return Ok(blockchain_dir); + } + + fs::create_dir_all(&blockchain_dir)?; + + println!("[ADD] {entry_header_path:?}"); + TemplateGenerator::new(ENTRY_HEADER_TEMPLATE) + .write_to(entry_header_path.clone()) + .with_default_patterns(coin) + .write()?; + + Ok(entry_header_path) + } +} diff --git a/codegen-v2/src/codegen/cpp/mod.rs b/codegen-v2/src/codegen/cpp/mod.rs new file mode 100644 index 00000000000..87443e4ad6d --- /dev/null +++ b/codegen-v2/src/codegen/cpp/mod.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::registry::CoinItem; +use std::env; +use std::path::PathBuf; + +pub mod blockchain_dispatcher_generator; +pub mod entry_generator; +pub mod new_blockchain; +pub mod new_evmchain; +pub mod tw_any_address_tests_generator; +pub mod tw_any_signer_tests_generator; +pub mod tw_blockchain; +pub mod tw_coin_address_derivation_tests_generator; +pub mod tw_coin_type_generator; +pub mod tw_coin_type_tests_generator; + +pub fn cpp_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("src") +} + +pub fn cpp_include_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("include") + .join("TrustWalletCore") +} + +pub fn integration_tests_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("tests") +} + +pub fn coin_integration_tests_directory(coin: &CoinItem) -> PathBuf { + integration_tests_directory() + .join("chains") + .join(coin.coin_type()) +} diff --git a/codegen-v2/src/codegen/cpp/new_blockchain.rs b/codegen-v2/src/codegen/cpp/new_blockchain.rs new file mode 100644 index 00000000000..75634f481de --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_blockchain.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::blockchain_dispatcher_generator::BlockchainDispatcherGenerator; +use crate::codegen::cpp::entry_generator::EntryGenerator; +use crate::codegen::cpp::tw_any_address_tests_generator::TWAnyAddressTestsGenerator; +use crate::codegen::cpp::tw_any_signer_tests_generator::TWAnySignerTestsGenerator; +use crate::codegen::cpp::tw_blockchain::TWBlockchainGenerator; +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + // Generate C++ files. + EntryGenerator::generate(coin)?; + + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + // Add the new blockchain type to the `TWBlockchain` enum. + TWBlockchainGenerator::generate_blockchain_type_variant(coin)?; + // Add the blockchain entry to the dispatcher `Coin.cpp`. + BlockchainDispatcherGenerator::generate_new_blockchain_type_dispatching(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + TWAnyAddressTestsGenerator::generate(coin)?; + TWAnySignerTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/new_evmchain.rs b/codegen-v2/src/codegen/cpp/new_evmchain.rs new file mode 100644 index 00000000000..36c29e127e2 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_evmchain.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_evmchain(coin: &CoinItem) -> Result<()> { + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_evm_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/templates/Entry.h b/codegen-v2/src/codegen/cpp/templates/Entry.h new file mode 100644 index 00000000000..a07c98e471d --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/Entry.h @@ -0,0 +1,19 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "rust/RustCoinEntry.h" + +namespace TW::{BLOCKCHAIN} { + +/// Entry point for {BLOCKCHAIN} coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry : public Rust::RustCoinEntry { +}; + +} // namespace TW::{BLOCKCHAIN} + diff --git a/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..35516cf2597 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp @@ -0,0 +1,26 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "TestUtilities.h" +#include + +using namespace TW; + +// TODO: Finalize tests + +TEST(TW{COIN_TYPE}, Address) { + // TODO: Finalize test implementation + + auto string = STRING("__ADD_VALID_ADDRESS_HERE__"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinType{COIN_TYPE})); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "__CORRESPONDING_ADDRESS_DATA__"); +} diff --git a/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp new file mode 100644 index 00000000000..38f1493b916 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp @@ -0,0 +1,19 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "TestUtilities.h" +#include + +using namespace TW; + +// TODO: Finalize tests + +TEST(TWAnySigner{COIN_TYPE}, Sign) { + // TODO: Finalize test implementation +} diff --git a/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..83124519c83 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TestUtilities.h" +#include +#include + +TEST(TW{COIN_TYPE}CoinType, TWCoinType) { + const auto coin = TWCoinType{COIN_TYPE}; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("{EXPLORER_SAMPLE_TX}")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("{EXPLORER_SAMPLE_ACCOUNT}")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "{COIN_ID}"); + assertStringsEqual(name, "{COIN_TYPE}"); + assertStringsEqual(symbol, "{SYMBOL}"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), {DECIMALS}); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchain{BLOCKCHAIN}); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), {P2PKH_PREFIX}); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), {P2SH_PREFIX}); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), {STATIC_PREFIX}); + assertStringsEqual(txUrl, "{EXPLORER_URL}{EXPLORER_TX_PATH}{EXPLORER_SAMPLE_TX}"); + assertStringsEqual(accUrl, "{EXPLORER_URL}{EXPLORER_ACCOUNT_PATH}{EXPLORER_SAMPLE_ACCOUNT}"); +} diff --git a/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs new file mode 100644 index 00000000000..f6fa62ac324 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs @@ -0,0 +1,41 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_ANY_ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/TWAnyAddressTests.cpp"); + +pub fn tw_any_address_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWAnyAddressTests.cpp") +} + +pub struct TWAnyAddressTestsGenerator; + +impl TWAnyAddressTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_any_address_tests_path = coin_tests_dir.join("TWAnyAddressTests.cpp"); + + fs::create_dir_all(coin_tests_dir)?; + if tw_any_address_tests_path.exists() { + println!("[SKIP] {tw_any_address_tests_path:?} already exists"); + return Ok(()); + } + + println!("[ADD] {tw_any_address_tests_path:?}"); + TemplateGenerator::new(TW_ANY_ADDRESS_TESTS_TEMPLATE) + .write_to(tw_any_address_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs new file mode 100644 index 00000000000..677162aee84 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs @@ -0,0 +1,41 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_ANY_SIGNER_TESTS_TEMPLATE: &str = include_str!("templates/TWAnySignerTests.cpp"); + +pub fn tw_any_signer_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWAnySignerTests.cpp") +} + +pub struct TWAnySignerTestsGenerator; + +impl TWAnySignerTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_any_signer_tests_path = coin_tests_dir.join("TWAnySignerTests.cpp"); + + fs::create_dir_all(coin_tests_dir)?; + if tw_any_signer_tests_path.exists() { + println!("[SKIP] {tw_any_signer_tests_path:?} already exists"); + return Ok(()); + } + + println!("[ADD] {tw_any_signer_tests_path:?}"); + TemplateGenerator::new(TW_ANY_SIGNER_TESTS_TEMPLATE) + .write_to(tw_any_signer_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_blockchain.rs b/codegen-v2/src/codegen/cpp/tw_blockchain.rs new file mode 100644 index 00000000000..f74c1e20ea9 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_blockchain.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::cpp_include_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +/// An offset because of removed blockchain enum types. +const BLOCKCHAIN_TYPE_OFFSET: usize = 2; + +pub fn tw_blockchain_path() -> PathBuf { + cpp_include_directory().join("TWBlockchain.h") +} + +/// Represents `TWBlockchain.h`. +pub struct TWBlockchainGenerator; + +impl TWBlockchainGenerator { + pub fn generate_blockchain_type_variant(coin: &CoinItem) -> Result<()> { + let coin_type = coin.blockchain_type(); + let tw_blockchain_type_path = tw_blockchain_path(); + + println!("[EDIT] {tw_blockchain_type_path:?}"); + let mut tw_blockchain_type_rs = FileContent::read(tw_blockchain_type_path)?; + + { + let mut enum_region = + tw_blockchain_type_rs.find_region_with_prefix(" TWBlockchain")?; + // Add an offset because of removed blockchain enum types. + let new_blockchain_id = enum_region.count_lines() + BLOCKCHAIN_TYPE_OFFSET; + enum_region.push_line(format!( + " TWBlockchain{coin_type} = {new_blockchain_id}," + )); + } + + tw_blockchain_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs new file mode 100644 index 00000000000..1cdeaf295ac --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::integration_tests_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_ADDRESS_DERIVATION_TESTS_END: &str = + "end_of_coin_address_derivation_tests_marker_do_not_modify"; +const EVM_ADDRESS_DERIVATION_TESTS_END: &str = + "end_of_evm_address_derivation_tests_marker_do_not_modify"; + +pub fn coin_address_derivation_tests_path() -> PathBuf { + integration_tests_directory() + .join("common") + .join("CoinAddressDerivationTests.cpp") +} + +/// Represents `CoinAddressDerivationTests.cpp`. +pub struct CoinAddressDerivationTestsGenerator; + +impl CoinAddressDerivationTestsGenerator { + pub fn generate_new_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let test_path = coin_address_derivation_tests_path(); + println!("[EDIT] {test_path:?}"); + + let mut coin_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut switch_case_region = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(COIN_ADDRESS_DERIVATION_TESTS_END))?; + + #[rustfmt::skip] + let test_case = format!( +r#" case TWCoinType{coin_type}: + EXPECT_EQ(address, "__TODO__"); + break;"# +); + + switch_case_region.push_paragraph_before(test_case); + } + + coin_address_derivation_test_rs.write() + } + + pub fn generate_new_evm_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let test_path = coin_address_derivation_tests_path(); + println!("[EDIT] {test_path:?}"); + + let mut evm_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut switch_case_region = evm_address_derivation_test_rs + .rfind_line(|line| line.contains(EVM_ADDRESS_DERIVATION_TESTS_END))?; + switch_case_region.push_line_before(format!(" case TWCoinType{coin_type}:")); + } + + evm_address_derivation_test_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs new file mode 100644 index 00000000000..d238922ae4d --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::cpp_include_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const TW_COIN_TYPE_END: &str = "end_of_tw_coin_type_marker_do_not_modify"; + +pub fn tw_coin_type_path() -> PathBuf { + cpp_include_directory().join("TWCoinType.h") +} + +/// Represents `TWCoinType.h`. +pub struct TWCoinTypeGenerator; + +impl TWCoinTypeGenerator { + pub fn generate_coin_type_variant(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let coin_id_number = coin.coin_id_number; + let tw_coin_type_file_path = tw_coin_type_path(); + + println!("[EDIT] {tw_coin_type_file_path:?}"); + let mut tw_coin_type_rs = FileContent::read(tw_coin_type_file_path)?; + + { + let mut enum_region = + tw_coin_type_rs.rfind_line(|line| line.contains(TW_COIN_TYPE_END))?; + enum_region.push_line_before(format!(" TWCoinType{coin_type} = {coin_id_number},")); + } + + tw_coin_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs new file mode 100644 index 00000000000..8b85c4e79f4 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs @@ -0,0 +1,41 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_COIN_TYPE_TESTS_TEMPLATE: &str = include_str!("templates/TWCoinTypeTests.cpp"); + +pub fn tw_coin_type_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWCoinTypeTests.cpp") +} + +pub struct TWCoinTypeTestsGenerator; + +impl TWCoinTypeTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_coin_type_tests_path = coin_tests_dir.join("TWCoinTypeTests.cpp"); + + fs::create_dir(coin_tests_dir)?; + if tw_coin_type_tests_path.exists() { + println!("[SKIP] {tw_coin_type_tests_path:?} already exists"); + return Ok(()); + } + + println!("[ADD] {tw_coin_type_tests_path:?}"); + TemplateGenerator::new(TW_COIN_TYPE_TESTS_TEMPLATE) + .write_to(tw_coin_type_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/mod.rs b/codegen-v2/src/codegen/mod.rs index 52487305973..2f1b1a70839 100644 --- a/codegen-v2/src/codegen/mod.rs +++ b/codegen-v2/src/codegen/mod.rs @@ -4,4 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod cpp; +pub mod proto; +pub mod rust; pub mod swift; +pub mod template_generator; diff --git a/codegen-v2/src/codegen/proto/mod.rs b/codegen-v2/src/codegen/proto/mod.rs new file mode 100644 index 00000000000..e760e33c8ea --- /dev/null +++ b/codegen-v2/src/codegen/proto/mod.rs @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::env; +use std::path::PathBuf; + +pub mod new_blockchain; +pub mod proto_generator; + +pub fn proto_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("src") + .join("proto") +} diff --git a/codegen-v2/src/codegen/proto/new_blockchain.rs b/codegen-v2/src/codegen/proto/new_blockchain.rs new file mode 100644 index 00000000000..805e342f124 --- /dev/null +++ b/codegen-v2/src/codegen/proto/new_blockchain.rs @@ -0,0 +1,13 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::proto::proto_generator::ProtoGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + ProtoGenerator::generate(coin) +} diff --git a/codegen-v2/src/codegen/proto/proto_generator.rs b/codegen-v2/src/codegen/proto/proto_generator.rs new file mode 100644 index 00000000000..f3bd016984d --- /dev/null +++ b/codegen-v2/src/codegen/proto/proto_generator.rs @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::proto::proto_source_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::path::PathBuf; + +const PROTO_TEMPLATE: &str = include_str!("templates/Blockchain.proto"); + +pub fn blockchain_proto_path(coin: &CoinItem) -> PathBuf { + let blockchain_type = coin.blockchain_type(); + proto_source_directory().join(format!("{blockchain_type}.proto")) +} + +pub struct ProtoGenerator; + +impl ProtoGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let proto_path = blockchain_proto_path(coin); + + if proto_path.exists() { + println!("[SKIP] Protobuf interface already exists: {proto_path:?}"); + return Ok(()); + } + + println!("[ADD] {proto_path:?}"); + TemplateGenerator::new(PROTO_TEMPLATE) + .write_to(proto_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/proto/templates/Blockchain.proto b/codegen-v2/src/codegen/proto/templates/Blockchain.proto new file mode 100644 index 00000000000..c78772ce0a5 --- /dev/null +++ b/codegen-v2/src/codegen/proto/templates/Blockchain.proto @@ -0,0 +1,39 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust 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.{BLOCKCHAIN}.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// TODO: typical balance transfer, add more fields needed to sign +message TransferMessage { + int64 amount = 1; + int64 fee = 2; + string to = 3; +} + +// TODO: Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + + oneof message_oneof { + TransferMessage transfer = 2; + } +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; + + // A possible error, `OK` if none. + Common.Proto.SigningError error = 2; + + string error_message = 3; +} diff --git a/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs b/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs new file mode 100644 index 00000000000..19c83519e15 --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs @@ -0,0 +1,81 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_registry_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const BLOCKCHAIN_ENTRIES_START: &str = "start_of_blockchain_entries"; +const BLOCKCHAIN_ENTRIES_END: &str = "end_of_blockchain_entries"; +const BLOCKCHAIN_DISPATCHER_START: &str = "start_of_blockchain_dispatcher"; +const BLOCKCHAIN_DISPATCHER_END: &str = "end_of_blockchain_dispatcher"; + +pub fn dispatcher_path() -> PathBuf { + coin_registry_directory().join("src").join("dispatcher.rs") +} + +pub struct BlockchainDispatcherGenerator; + +impl BlockchainDispatcherGenerator { + pub fn generate_new_blockchain_type_dispatching(coin: &CoinItem) -> Result<()> { + let dispatcher_rs_path = dispatcher_path(); + println!("[EDIT] {dispatcher_rs_path:?}"); + let mut dispatcher_rs = FileContent::read(dispatcher_rs_path)?; + + Self::generate_use_of_blockchain_entry(coin, &mut dispatcher_rs)?; + Self::generate_blockchain_entry_constant(coin, &mut dispatcher_rs)?; + Self::generate_blockchain_dispatch(coin, &mut dispatcher_rs)?; + + dispatcher_rs.write() + } + + fn generate_use_of_blockchain_entry( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let import_pattern = "use "; + let blockchain_entry = coin.blockchain_entry(); + let tw_crate_name = coin.id.to_tw_crate_name(); + + let mut last_entry = file_content.rfind_line(|line| line.contains(import_pattern))?; + last_entry.push_line_after(format!("use {tw_crate_name}::entry::{blockchain_entry};")); + + Ok(()) + } + + fn generate_blockchain_entry_constant( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_entry = coin.blockchain_entry(); + let blockchain_entry_const = coin.blockchain_entry_upper_snake(); + + let mut entries_region = file_content + .find_region_with_comments(BLOCKCHAIN_ENTRIES_START, BLOCKCHAIN_ENTRIES_END)?; + entries_region.push_line(format!( + "const {blockchain_entry_const}: {blockchain_entry} = {blockchain_entry};" + )); + entries_region.sort(); + + Ok(()) + } + + fn generate_blockchain_dispatch(coin: &CoinItem, file_content: &mut FileContent) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + let blockchain_entry_const = coin.blockchain_entry_upper_snake(); + + let mut dispatcher_region = file_content + .find_region_with_comments(BLOCKCHAIN_DISPATCHER_START, BLOCKCHAIN_DISPATCHER_END)?; + dispatcher_region.push_line(format!( + " BlockchainType::{blockchain_type} => Ok(&{blockchain_entry_const})," + )); + dispatcher_region.sort(); + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/rust/blockchain_type_generator.rs b/codegen-v2/src/codegen/rust/blockchain_type_generator.rs new file mode 100644 index 00000000000..b902e88f739 --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_type_generator.rs @@ -0,0 +1,42 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_registry_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const BLOCKCHAIN_TYPE_START: &str = "start_of_blockchain_type"; +const BLOCKCHAIN_TYPE_END: &str = "end_of_blockchain_type"; + +pub fn blockchain_type_path() -> PathBuf { + coin_registry_directory() + .join("src") + .join("blockchain_type.rs") +} + +/// Represents `BlockchainType` enum generator. +pub struct BlockchainTypeGenerator; + +impl BlockchainTypeGenerator { + pub fn add_new_blockchain_type(coin: &CoinItem) -> Result<()> { + let blockchain_type_rs_path = blockchain_type_path(); + let blockchain_type = coin.blockchain_type(); + + println!("[EDIT] {blockchain_type_rs_path:?}"); + let mut blockchain_type_rs = FileContent::read(blockchain_type_rs_path)?; + + { + let mut enum_region = blockchain_type_rs + .find_region_with_comments(BLOCKCHAIN_TYPE_START, BLOCKCHAIN_TYPE_END)?; + enum_region.push_line(format!(" {blockchain_type},")); + enum_region.sort(); + } + + blockchain_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs b/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs new file mode 100644 index 00000000000..4a5d3acdcc6 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs @@ -0,0 +1,58 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::tw_any_coin_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_ADDRESS_DERIVATION_TEST_END: &str = + "end_of_coin_address_derivation_tests_marker_do_not_modify"; +const EVM_ADDRESS_DERIVATION_TEST_END: &str = + "end_of_evm_address_derivation_tests_marker_do_not_modify"; + +pub fn coin_address_derivation_test_path() -> PathBuf { + tw_any_coin_directory() + .join("tests") + .join("coin_address_derivation_test.rs") +} + +pub struct CoinAddressDerivationTestGenerator; + +impl CoinAddressDerivationTestGenerator { + pub fn generate_new_coin_type_case(coin: &CoinItem) -> Result<()> { + let test_path = coin_address_derivation_test_path(); + let coin_type = coin.coin_type(); + + println!("[EDIT] {test_path:?}"); + let mut coin_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut end_of_test = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(COIN_ADDRESS_DERIVATION_TEST_END))?; + end_of_test.push_line_before(format!(" CoinType::{coin_type} => todo!(),")); + } + + coin_address_derivation_test_rs.write() + } + + pub fn generate_new_evm_coin_type_case(coin: &CoinItem) -> Result<()> { + let test_path = coin_address_derivation_test_path(); + let coin_type = coin.coin_type(); + + println!("[EDIT] {test_path:?}"); + let mut coin_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut end_of_test = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(EVM_ADDRESS_DERIVATION_TEST_END))?; + end_of_test.push_line_before(format!(" | CoinType::{coin_type}")); + } + + coin_address_derivation_test_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_crate.rs b/codegen-v2/src/codegen/rust/coin_crate.rs new file mode 100644 index 00000000000..4586f66ae3f --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_crate.rs @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::chains_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::coin_id::CoinId; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const BLOCKCHAIN_ADDRESS_TEMPLATE: &str = include_str!("templates/blockchain_crate/address.rs"); +const BLOCKCHAIN_COMPILER_TEMPLATE: &str = include_str!("templates/blockchain_crate/compiler.rs"); +const BLOCKCHAIN_ENTRY_TEMPLATE: &str = include_str!("templates/blockchain_crate/entry.rs"); +const BLOCKCHAIN_MANIFEST_TEMPLATE: &str = include_str!("templates/blockchain_crate/Cargo.toml"); +const BLOCKCHAIN_LIB_TEMPLATE: &str = include_str!("templates/blockchain_crate/lib.rs"); +const BLOCKCHAIN_SIGNER_TEMPLATE: &str = include_str!("templates/blockchain_crate/signer.rs"); + +pub fn coin_source_directory(id: &CoinId) -> PathBuf { + chains_directory().join(id.to_tw_crate_name()) +} + +pub struct CoinCrate { + coin: CoinItem, +} + +impl CoinCrate { + pub fn new(coin: CoinItem) -> CoinCrate { + CoinCrate { coin } + } + + /// Creates a Cargo crate with `entry.rs` file. + /// Returns the path to the create crate. + pub fn create(self) -> Result { + let blockchain_path = coin_source_directory(&self.coin.id); + let blockchain_toml_path = blockchain_path.join("Cargo.toml"); + + let blockchain_src_path = blockchain_path.join("src"); + let blockchain_lib_rs_path = blockchain_src_path.join("lib.rs"); + let blockchain_entry_path = blockchain_src_path.join("entry.rs"); + let blockchain_compiler_path = blockchain_src_path.join("compiler.rs"); + let blockchain_address_rs_path = blockchain_src_path.join("address.rs"); + let blockchain_signer_rs_path = blockchain_src_path.join("signer.rs"); + + if blockchain_path.exists() { + let tw_crate_name = self.coin.id.to_tw_crate_name(); + println!( + "[SKIP] '{tw_crate_name}' blockchain crate already exists: {blockchain_path:?}" + ); + return Ok(blockchain_path); + } + + fs::create_dir_all(&blockchain_path)?; + fs::create_dir_all(&blockchain_src_path)?; + + println!("[ADD] {blockchain_toml_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_MANIFEST_TEMPLATE) + .write_to(blockchain_toml_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_lib_rs_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_LIB_TEMPLATE) + .write_to(blockchain_lib_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_entry_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_ENTRY_TEMPLATE) + .write_to(blockchain_entry_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_compiler_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_COMPILER_TEMPLATE) + .write_to(blockchain_compiler_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_address_rs_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_ADDRESS_TEMPLATE) + .write_to(blockchain_address_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_signer_rs_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_SIGNER_TEMPLATE) + .write_to(blockchain_signer_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + Ok(blockchain_path) + } +} diff --git a/codegen-v2/src/codegen/rust/coin_integration_tests.rs b/codegen-v2/src/codegen/rust/coin_integration_tests.rs new file mode 100644 index 00000000000..541e1b2ae3c --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_integration_tests.rs @@ -0,0 +1,131 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::tw_any_coin_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::coin_id::CoinId; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/address_tests.rs"); +const COMPILE_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/compile_tests.rs"); +const MOD_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod.rs"); +const SIGN_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/sign_tests.rs"); + +pub fn chains_integration_tests_directory() -> PathBuf { + tw_any_coin_directory().join("tests").join("chains") +} + +pub fn coin_integration_tests_directory(id: &CoinId) -> PathBuf { + chains_integration_tests_directory().join(id.as_str()) +} + +pub fn coin_address_derivation_test_path() -> PathBuf { + tw_any_coin_directory() + .join("tests") + .join("coin_address_derivation_test.rs") +} + +pub struct CoinIntegrationTests { + coin: CoinItem, +} + +impl CoinIntegrationTests { + pub fn new(coin: CoinItem) -> CoinIntegrationTests { + CoinIntegrationTests { coin } + } + + pub fn create(self) -> Result { + let blockchain_tests_path = self.coin_tests_directory(); + if blockchain_tests_path.exists() { + println!("[SKIP] integration tests already exists: {blockchain_tests_path:?}"); + return Ok(blockchain_tests_path); + } + + fs::create_dir_all(&blockchain_tests_path)?; + + self.list_blockchain_in_chains_mod()?; + self.create_address_tests()?; + self.create_compile_tests()?; + self.create_sign_tests()?; + self.create_chain_tests_mod_rs()?; + + Ok(blockchain_tests_path) + } + + fn coin_tests_directory(&self) -> PathBuf { + coin_integration_tests_directory(&self.coin.id) + } + + fn create_address_tests(&self) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let address_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_address.rs")); + + println!("[ADD] {address_tests_path:?}"); + TemplateGenerator::new(ADDRESS_TESTS_TEMPLATE) + .write_to(address_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_compile_tests(&self) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let compile_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_compile.rs")); + + println!("[ADD] {compile_tests_path:?}"); + TemplateGenerator::new(COMPILE_TESTS_TEMPLATE) + .write_to(compile_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_sign_tests(&self) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let sign_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_sign.rs")); + + println!("[ADD] {sign_tests_path:?}"); + TemplateGenerator::new(SIGN_TESTS_TEMPLATE) + .write_to(sign_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_chain_tests_mod_rs(&self) -> Result<()> { + let blockchain_tests_mod_path = self.coin_tests_directory().join("mod.rs"); + + println!("[ADD] {blockchain_tests_mod_path:?}"); + TemplateGenerator::new(MOD_TESTS_TEMPLATE) + .write_to(blockchain_tests_mod_path) + .with_default_patterns(&self.coin) + .write() + } + + fn list_blockchain_in_chains_mod(&self) -> Result<()> { + let chains_mod_path = chains_integration_tests_directory().join("mod.rs"); + let chain_id = self.coin.id.as_str(); + + println!("[EDIT] {chains_mod_path:?}"); + let mut chains_mod_rs = FileContent::read(chains_mod_path)?; + + { + let mod_pattern = "mod "; + let mut mod_region = chains_mod_rs.find_region_with_prefix(mod_pattern)?; + mod_region.push_line(format!("mod {chain_id};")); + mod_region.sort(); + } + + chains_mod_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs b/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs new file mode 100644 index 00000000000..f76fed045db --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_registry_directory; +use crate::codegen::rust::toml_editor::Dependencies; +use crate::registry::CoinItem; +use crate::Result; +use std::path::Path; + +pub struct CoinRegistryManifestGenerator; + +impl CoinRegistryManifestGenerator { + pub fn add_dependency(coin: &CoinItem, path_to_new_blockchain_crate: &Path) -> Result<()> { + let path_to_cargo_manifest = coin_registry_directory().join("Cargo.toml"); + println!("[EDIT] {path_to_cargo_manifest:?}"); + Dependencies::new(path_to_cargo_manifest) + .insert_dependency(&coin.id.to_tw_crate_name(), path_to_new_blockchain_crate) + } +} diff --git a/codegen-v2/src/codegen/rust/mod.rs b/codegen-v2/src/codegen/rust/mod.rs new file mode 100644 index 00000000000..7edc77d5062 --- /dev/null +++ b/codegen-v2/src/codegen/rust/mod.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::env; +use std::path::PathBuf; + +pub mod blockchain_dispatcher_generator; +pub mod blockchain_type_generator; +pub mod coin_address_derivation_test_generator; +pub mod coin_crate; +pub mod coin_integration_tests; +pub mod coin_registry_manifest_generator; +pub mod new_blockchain; +pub mod new_evmchain; +pub mod toml_editor; + +pub fn rust_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("rust") +} + +pub fn chains_directory() -> PathBuf { + rust_source_directory().join("chains") +} + +pub fn tw_any_coin_directory() -> PathBuf { + rust_source_directory().join("tw_any_coin") +} + +pub fn workspace_toml_path() -> PathBuf { + rust_source_directory().join("Cargo.toml") +} + +pub fn coin_registry_directory() -> PathBuf { + rust_source_directory().join("tw_coin_registry") +} diff --git a/codegen-v2/src/codegen/rust/new_blockchain.rs b/codegen-v2/src/codegen/rust/new_blockchain.rs new file mode 100644 index 00000000000..2f3a4996dd2 --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_blockchain.rs @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::blockchain_dispatcher_generator::BlockchainDispatcherGenerator; +use crate::codegen::rust::blockchain_type_generator::BlockchainTypeGenerator; +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::codegen::rust::coin_crate::CoinCrate; +use crate::codegen::rust::coin_integration_tests::CoinIntegrationTests; +use crate::codegen::rust::coin_registry_manifest_generator::CoinRegistryManifestGenerator; +use crate::codegen::rust::toml_editor::Workspace; +use crate::codegen::rust::workspace_toml_path; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + // Create blockchain's crate. + let blockchain_crate_path = CoinCrate::new(coin.clone()).create()?; + + // Insert the created crate to the workspace. + Workspace::new(workspace_toml_path()).insert_crate(&blockchain_crate_path)?; + + // Create integration tests. + CoinIntegrationTests::new(coin.clone()).create()?; + CoinAddressDerivationTestGenerator::generate_new_coin_type_case(coin)?; + + // Add the new blockchain to the `tw_coin_registry`. + BlockchainTypeGenerator::add_new_blockchain_type(coin)?; + CoinRegistryManifestGenerator::add_dependency(coin, &blockchain_crate_path)?; + BlockchainDispatcherGenerator::generate_new_blockchain_type_dispatching(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/rust/new_evmchain.rs b/codegen-v2/src/codegen/rust/new_evmchain.rs new file mode 100644 index 00000000000..2222306a4de --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_evmchain.rs @@ -0,0 +1,14 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_evmchain(coin: &CoinItem) -> Result<()> { + // Modify integration tests. + CoinAddressDerivationTestGenerator::generate_new_evm_coin_type_case(coin) +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml new file mode 100644 index 00000000000..9d5a7a7d04a --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "{TW_CRATE_NAME}" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs new file mode 100644 index 00000000000..96d862a63cd --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::AddressError; +use tw_memory::Data; + +pub struct {BLOCKCHAIN}Address { + // TODO add necessary fields. +} + +impl CoinAddress for {BLOCKCHAIN}Address { + #[inline] + fn data(&self) -> Data { + todo!() + } +} + +impl FromStr for {BLOCKCHAIN}Address { + type Err = AddressError; + + fn from_str(_s: &str) -> Result { + todo!() + } +} + +impl fmt::Display for {BLOCKCHAIN}Address { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs new file mode 100644 index 00000000000..2e1831cf5b6 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs @@ -0,0 +1,52 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_proto::{BLOCKCHAIN}::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct {BLOCKCHAIN}Compiler; + +impl {BLOCKCHAIN}Compiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> SigningResult> { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs new file mode 100644 index 00000000000..afbada1d43e --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs @@ -0,0 +1,91 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::{BLOCKCHAIN}Address; +use crate::compiler::{BLOCKCHAIN}Compiler; +use crate::signer::{BLOCKCHAIN}Signer; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::{BLOCKCHAIN}::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct {BLOCKCHAIN}Entry; + +impl CoinEntry for {BLOCKCHAIN}Entry { + type AddressPrefix = NoPrefix; + type Address = {BLOCKCHAIN}Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + _address: &str, + _prefix: Option, + ) -> AddressResult { + todo!() + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + {BLOCKCHAIN}Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + _public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + todo!() + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + {BLOCKCHAIN}Signer::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + {BLOCKCHAIN}Compiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + {BLOCKCHAIN}Compiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs new file mode 100644 index 00000000000..c5ac4e098e5 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod compiler; +pub mod entry; +pub mod signer; diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs new file mode 100644 index 00000000000..17cb69f66e9 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_proto::{BLOCKCHAIN}::Proto; + +pub struct {BLOCKCHAIN}Signer; + +impl {BLOCKCHAIN}Signer { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs new file mode 100644 index 00000000000..6a3a7e6a330 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs @@ -0,0 +1,30 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_{COIN_ID}_address_normalization() { + test_address_normalization(CoinType::{COIN_TYPE}, "DENORMALIZED", "EXPECTED"); +} + +#[test] +fn test_{COIN_ID}_address_is_valid() { + test_address_valid(CoinType::{COIN_TYPE}, "VALID ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_invalid() { + test_address_invalid(CoinType::{COIN_TYPE}, "INVALID ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_get_data() { + test_address_get_data(CoinType::{COIN_TYPE}, "ADDRESS", "HEX(DATA)"); +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs new file mode 100644 index 00000000000..d403c5e154b --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#[test] +fn test_{COIN_ID}_compile() { + todo!() +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs new file mode 100644 index 00000000000..96f7f590af1 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod {COIN_ID}_address; +mod {COIN_ID}_compile; +mod {COIN_ID}_sign; diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs new file mode 100644 index 00000000000..9a24b45d74b --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-{YEAR} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#[test] +fn test_{COIN_ID}_sign() { + todo!() +} diff --git a/codegen-v2/src/codegen/rust/toml_editor.rs b/codegen-v2/src/codegen/rust/toml_editor.rs new file mode 100644 index 00000000000..0ac35b3c53d --- /dev/null +++ b/codegen-v2/src/codegen/rust/toml_editor.rs @@ -0,0 +1,112 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{Error, Result}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use toml_edit::{Document, InlineTable, Item, Value}; + +const NEW_LINE_TAB_DECORATOR: &str = "\n "; +const NO_DECORATOR: &str = ""; + +pub struct Workspace { + path_to_toml: PathBuf, +} + +impl Workspace { + pub fn new(path_to_toml: PathBuf) -> Workspace { + Workspace { path_to_toml } + } + + pub fn insert_crate(self, path_to_crate: &Path) -> Result<()> { + let manifest = fs::read_to_string(&self.path_to_toml)?; + let mut manifest = Document::from_str(&manifest)?; + + let members = manifest["workspace"]["members"] + .as_array_mut() + .ok_or(Error::TomlFormat( + "Invalid 'workspace' TOML format".to_string(), + ))?; + + // Try to get a path to the crate relative to the `Cargo.toml`. + let relative_path_to_crate = relative_path_to_crate(&self.path_to_toml, path_to_crate)?; + + // Push the new member, sort and save the manifest. + + let relative_path_to_crate_decorated = + Value::from(relative_path_to_crate).decorated(NEW_LINE_TAB_DECORATOR, NO_DECORATOR); + + members.push_formatted(relative_path_to_crate_decorated); + members.sort_by(|x, y| x.as_str().cmp(&y.as_str())); + + fs::write(self.path_to_toml, manifest.to_string())?; + Ok(()) + } +} + +pub struct Dependencies { + path_to_toml: PathBuf, +} + +impl Dependencies { + pub fn new(path_to_toml: PathBuf) -> Dependencies { + Dependencies { path_to_toml } + } + + pub fn insert_dependency(self, dep_name: &str, path_to_dep_crate: &Path) -> Result<()> { + let manifest = fs::read_to_string(&self.path_to_toml)?; + let mut manifest = Document::from_str(&manifest)?; + + let dependencies = manifest["dependencies"] + .as_table_like_mut() + .ok_or(Error::TomlFormat("Invalid 'Cargo.toml' format".to_string()))?; + + // Try to get a path to the crate relative to the `Cargo.toml`. + let relative_path_to_crate = relative_path_to_crate(&self.path_to_toml, path_to_dep_crate)?; + + // Create the new dependency member (aka a TOML inline table with `path` key-value). + let mut new_member = InlineTable::new(); + new_member.insert("path", relative_path_to_crate.into()); + + // Push the new member, sort and save the manifest. + dependencies.insert(dep_name, Item::Value(Value::InlineTable(new_member))); + dependencies.sort_values(); + + fs::write(self.path_to_toml, manifest.to_string())?; + + Ok(()) + } +} + +/// Returns a path to the dependency accordingly to the Cargo manifest file. +/// The result string can be put to `Cargo.toml` as: +/// ```toml +/// tw_foo = { path = "" } +/// ``` +fn relative_path_to_crate( + path_to_cargo_manifest: &Path, + path_to_dependency: &Path, +) -> Result { + let absolute_path_to_crate_directory = path_to_cargo_manifest + .parent() + .ok_or_else(|| Error::io_error_other("Cannot get a parent directory".to_string()))? + .canonicalize()?; + let absolute_path_to_dependency = path_to_dependency.canonicalize()?; + + let relative_path_to_dependency = pathdiff::diff_paths( + absolute_path_to_dependency, + absolute_path_to_crate_directory, + ) + .ok_or_else(|| { + Error::io_error_other("Cannot get a relative path to the dependency".to_string()) + })? + .to_str() + .ok_or_else(|| Error::io_error_other("Invalid path to the crate".to_string()))? + .to_string(); + + Ok(relative_path_to_dependency) +} diff --git a/codegen-v2/src/codegen/swift/templates/WalletCore.h b/codegen-v2/src/codegen/swift/templates/WalletCore.h index 8b5657a9eb4..98e432e9066 100644 --- a/codegen-v2/src/codegen/swift/templates/WalletCore.h +++ b/codegen-v2/src/codegen/swift/templates/WalletCore.h @@ -45,6 +45,7 @@ FOUNDATION_EXPORT const unsigned char WalletCoreVersionString[]; #include "TWEthereumAbiFunction.h" #include "TWEthereumAbiValue.h" #include "TWEthereumChainID.h" +#include "TWEthereumRlp.h" #include "TWEthereumMessageSigner.h" #include "TWFIOAccount.h" #include "TWFilecoinAddressConverter.h" diff --git a/codegen-v2/src/codegen/template_generator.rs b/codegen-v2/src/codegen/template_generator.rs new file mode 100644 index 00000000000..39f1780f1f1 --- /dev/null +++ b/codegen-v2/src/codegen/template_generator.rs @@ -0,0 +1,80 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::registry::CoinItem; +use crate::{current_year, Error, Result}; +use std::fs; +use std::path::PathBuf; + +const PATTERNS_CAPACITY: usize = 20; + +pub struct TemplateGenerator { + template_content: &'static str, + write_to: Option, + to_replace: Vec, + replace_with: Vec, +} + +impl TemplateGenerator { + pub fn new(template_content: &'static str) -> TemplateGenerator { + TemplateGenerator { + template_content, + write_to: None, + to_replace: Vec::with_capacity(PATTERNS_CAPACITY), + replace_with: Vec::with_capacity(PATTERNS_CAPACITY), + } + } + + pub fn write_to(mut self, write_to: PathBuf) -> TemplateGenerator { + self.write_to = Some(write_to); + self + } + + /// Use default patterns. + pub fn with_default_patterns(self, coin: &CoinItem) -> TemplateGenerator { + self.add_pattern("{YEAR}", current_year()) + .add_pattern("{BLOCKCHAIN}", coin.blockchain_type()) + .add_pattern("{TW_CRATE_NAME}", coin.id.to_tw_crate_name()) + .add_pattern("{COIN_ID}", coin.id.as_str()) + .add_pattern("{COIN_TYPE}", coin.coin_type()) + .add_pattern("{SYMBOL}", &coin.symbol) + .add_pattern("{DECIMALS}", coin.decimals) + .add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix) + .add_pattern("{P2SH_PREFIX}", coin.p2sh_prefix) + .add_pattern("{STATIC_PREFIX}", coin.static_prefix) + .add_pattern("{EXPLORER_URL}", &coin.explorer.url) + .add_pattern("{EXPLORER_TX_PATH}", &coin.explorer.tx_path) + .add_pattern("{EXPLORER_ACCOUNT_PATH}", &coin.explorer.account_path) + .add_pattern("{EXPLORER_SAMPLE_TX}", &coin.explorer.sample_tx) + .add_pattern("{EXPLORER_SAMPLE_ACCOUNT}", &coin.explorer.sample_account) + } + + pub fn add_pattern( + mut self, + to_replace: K, + replace_with: V, + ) -> TemplateGenerator { + self.to_replace.push(to_replace.to_string()); + self.replace_with.push(replace_with.to_string()); + self + } + + pub fn write(self) -> Result<()> { + let write_to_path = self.write_to.ok_or_else(|| { + Error::io_error_other("Incorrect use of 'TemplateGenerator'".to_string()) + })?; + let file_to_write = fs::File::create(write_to_path)?; + + aho_corasick::AhoCorasick::new(self.to_replace) + .map_err(|e| Error::io_error_other(format!("Invalid patterns: {e}")))? + .try_stream_replace_all( + self.template_content.as_bytes(), + file_to_write, + &self.replace_with, + ) + .map_err(Error::from) + } +} diff --git a/codegen-v2/src/coin_id.rs b/codegen-v2/src/coin_id.rs new file mode 100644 index 00000000000..6f0eaed7c36 --- /dev/null +++ b/codegen-v2/src/coin_id.rs @@ -0,0 +1,46 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{Error, Result}; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer}; + +#[derive(Clone, Eq, PartialEq)] +pub struct CoinId(String); + +impl CoinId { + /// Returns `Ok` if only the given `id` is a valid Rust identifier. + pub fn new(id: String) -> Result { + let first_letter = id + .chars() + .next() + .ok_or(Error::RegistryError("Invalid 'id'".to_string()))?; + let valid_chars = id.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_'); + + if first_letter.is_numeric() || !valid_chars { + return Err(Error::RegistryError("Invalid 'id'".to_string())); + } + Ok(CoinId(id)) + } + + pub fn to_tw_crate_name(&self) -> String { + format!("tw_{}", self.0) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl<'de> Deserialize<'de> for CoinId { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let id = String::deserialize(deserializer)?; + CoinId::new(id).map_err(|e| SerdeError::custom(format!("{e:?}"))) + } +} diff --git a/codegen-v2/src/lib.rs b/codegen-v2/src/lib.rs index ca6556bb44d..5227f533701 100644 --- a/codegen-v2/src/lib.rs +++ b/codegen-v2/src/lib.rs @@ -9,12 +9,17 @@ extern crate serde; use handlebars::{RenderError, TemplateError}; use serde_yaml::Error as YamlError; +use std::io; use std::io::Error as IoError; +use toml_edit::TomlError; pub mod codegen; +pub mod coin_id; pub mod manifest; +pub mod registry; #[cfg(test)] mod tests; +pub mod utils; pub type Result = std::result::Result; @@ -25,9 +30,17 @@ pub enum Error { RenderError(RenderError), TemplateError(TemplateError), BadFormat(String), + RegistryError(String), + TomlFormat(String), InvalidCommand, } +impl Error { + pub fn io_error_other(err: String) -> Error { + Error::IoError(IoError::new(io::ErrorKind::Other, err)) + } +} + impl From for Error { fn from(err: IoError) -> Self { Error::IoError(err) @@ -52,6 +65,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: TomlError) -> Self { + Error::TomlFormat(err.to_string()) + } +} + fn current_year() -> u64 { use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/codegen-v2/src/main.rs b/codegen-v2/src/main.rs index 1be255b2f20..d708659a4c0 100644 --- a/codegen-v2/src/main.rs +++ b/codegen-v2/src/main.rs @@ -5,7 +5,10 @@ // file LICENSE at the root of the source code distribution tree. use libparser::codegen::swift::RenderIntput; +use libparser::codegen::{cpp, proto, rust}; +use libparser::coin_id::CoinId; use libparser::manifest::parse_dir; +use libparser::registry::read_coin_from_registry; use libparser::{Error, Result}; use std::fs::read_to_string; @@ -17,11 +20,52 @@ fn main() -> Result<()> { } match args[1].as_str() { + "new-blockchain-rust" => new_blockchain_rust(&args[2..]), + "new-blockchain" => new_blockchain(&args[2..]), + "new-evmchain" => new_evmchain(&args[2..]), "swift" => generate_swift_bindings(), _ => Err(Error::InvalidCommand), } } +fn new_blockchain_rust(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New Rust blockchain template for coin '{coin_str}' requested"); + rust::new_blockchain::new_blockchain(&coin_item)?; + + Ok(()) +} + +fn new_blockchain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New '{coin_str}' blockchain template requested"); + + proto::new_blockchain::new_blockchain(&coin_item)?; + rust::new_blockchain::new_blockchain(&coin_item)?; + cpp::new_blockchain::new_blockchain(&coin_item)?; + + Ok(()) +} + +fn new_evmchain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New '{coin_str}' EVM chain template requested"); + + rust::new_evmchain::new_evmchain(&coin_item)?; + cpp::new_evmchain::new_evmchain(&coin_item)?; + + Ok(()) +} + fn generate_swift_bindings() -> Result<()> { // NOTE: The paths will be configurable, eventually. const OUT_DIR: &str = "bindings/"; diff --git a/codegen-v2/src/registry.rs b/codegen-v2/src/registry.rs new file mode 100644 index 00000000000..aaad3ff0330 --- /dev/null +++ b/codegen-v2/src/registry.rs @@ -0,0 +1,89 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_id::CoinId; +use crate::{Error, Result}; +use convert_case::{Case, Casing}; +use std::path::PathBuf; +use std::{env, fs}; + +pub fn registry_json_path() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("registry.json") +} + +#[derive(Clone, Deserialize)] +pub struct CoinExplorer { + pub url: String, + #[serde(rename = "txPath")] + pub tx_path: String, + #[serde(rename = "accountPath")] + pub account_path: String, + #[serde(rename = "sampleTx")] + #[serde(default)] + pub sample_tx: String, + #[serde(rename = "sampleAccount")] + #[serde(default)] + pub sample_account: String, +} + +#[derive(Clone, Deserialize)] +pub struct CoinItem { + pub id: CoinId, + pub name: String, + #[serde(rename = "coinId")] + pub coin_id_number: u32, + pub symbol: String, + pub decimals: u8, + pub blockchain: String, + #[serde(rename = "p2pkhPrefix")] + #[serde(default)] + pub p2pkh_prefix: u8, + #[serde(rename = "p2shPrefix")] + #[serde(default)] + pub p2sh_prefix: u8, + #[serde(rename = "staticPrefix")] + #[serde(default)] + pub static_prefix: u8, + pub explorer: CoinExplorer, +} + +impl CoinItem { + /// Transforms a coin name to a Rust name. + /// https://github.com/trustwallet/wallet-core/blob/3769f31b7d0c75126b2f426bb065364429aaa379/codegen/lib/coin_skeleton_gen.rb#L15-L22 + pub fn coin_type(&self) -> String { + self.name.replace([' ', '.', '-'], "") + } + + /// Returns the blockchain type in `UpperCamel` case. + pub fn blockchain_type(&self) -> String { + self.blockchain.to_case(Case::UpperCamel) + } + + /// Returns the blockchain type in `UPPER_SNAKE` case. + pub fn blockchain_entry_upper_snake(&self) -> String { + self.blockchain.to_case(Case::UpperSnake) + } + + /// Returns a Rust blockchain entry of the blockchain. + pub fn blockchain_entry(&self) -> String { + format!("{}Entry", self.blockchain_type()) + } +} + +pub fn read_coin_from_registry(coin: &CoinId) -> Result { + let registry_path = registry_json_path(); + + let registry_bytes = fs::read(registry_path)?; + let coins: Vec = + serde_json::from_slice(®istry_bytes).map_err(|e| Error::RegistryError(e.to_string()))?; + + coins + .into_iter() + .find(|item| item.id == *coin) + .ok_or(Error::InvalidCommand) +} diff --git a/codegen-v2/src/utils.rs b/codegen-v2/src/utils.rs new file mode 100644 index 00000000000..43e1bd84f81 --- /dev/null +++ b/codegen-v2/src/utils.rs @@ -0,0 +1,159 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{Error, Result}; +use std::fs; +use std::path::{Path, PathBuf}; + +pub fn read_lines>(path: P) -> Result> { + let lines = fs::read_to_string(path)? + .split('\n') + .map(|line| line.to_string()) + .collect(); + Ok(lines) +} + +pub fn write_lines>(path: P, lines: Vec) -> Result<()> { + let content = lines.join("\n"); + fs::write(path, content).map_err(Error::from) +} + +pub struct FileContent { + path: PathBuf, + lines: Vec, +} + +impl FileContent { + pub fn read(path: PathBuf) -> Result { + read_lines(&path).map(|lines| FileContent { path, lines }) + } + + pub fn find_region_with_prefix(&mut self, prefix: &str) -> Result> { + // Find the first line that starts with the `prefix`. + let region_starts_at = self + .lines + .iter() + .position(|line| line.starts_with(prefix)) + .ok_or_else(|| Error::io_error_other(format!("Cannot find the `{prefix}` region")))?; + + // Find the last line that starts with the `prefix`. + let region_ends_at = self + .lines + .iter() + .rposition(|line| line.starts_with(prefix)) + .ok_or_else(|| Error::io_error_other(format!("Cannot find the `{prefix}` region")))?; + + Ok(FileRegion { + lines: &mut self.lines, + region_starts_at, + region_ends_at, + }) + } + + pub fn find_region_with_comments( + &mut self, + start_comment: &str, + end_comment: &str, + ) -> Result> { + // Find the position of the `start_comment`. + let start_comment_at = self + .lines + .iter() + .position(|line| line.contains(start_comment)) + .ok_or_else(|| { + Error::io_error_other(format!("Cannot find the `{start_comment}` line")) + })?; + let end_comment_at = self + .lines + .iter() + .skip(start_comment_at) + .position(|line| line.contains(end_comment)) + .ok_or_else(|| { + Error::io_error_other(format!("Cannot find the `{end_comment}` line")) + })? + + start_comment_at; + + let region_starts_at = start_comment_at + 1; + let region_ends_at = end_comment_at - 1; + + if region_starts_at > region_ends_at { + return Err(Error::io_error_other(format!( + "There must be the content between {start_comment} and {end_comment}" + ))); + } + + Ok(FileRegion { + lines: &mut self.lines, + region_starts_at, + region_ends_at, + }) + } + + pub fn rfind_line(&mut self, f: F) -> Result> + where + F: Fn(&str) -> bool, + { + let line_idx = self.lines.iter().rposition(|line| f(line)).ok_or_else(|| { + Error::io_error_other(format!( + "{:?} file does not contain a required pattern", + self.path + )) + })?; + Ok(LinePointer { + lines: &mut self.lines, + line_idx, + }) + } + + pub fn write(self) -> Result<()> { + write_lines(self.path, self.lines) + } +} + +pub struct FileRegion<'a> { + lines: &'a mut Vec, + region_starts_at: usize, + region_ends_at: usize, +} + +impl<'a> FileRegion<'a> { + pub fn push_line(&mut self, line: String) { + self.lines.insert(self.region_ends_at + 1, line); + self.region_ends_at += 1; + } + + pub fn sort(&mut self) { + self.lines[self.region_starts_at..=self.region_ends_at].sort() + } + + pub fn count_lines(&self) -> usize { + self.region_ends_at - self.region_starts_at + } +} + +pub struct LinePointer<'a> { + lines: &'a mut Vec, + line_idx: usize, +} + +impl<'a> LinePointer<'a> { + /// Please note that the line pointer will be shifted to the same line on which it pointed before. + pub fn push_line_before(&mut self, line: String) { + self.lines.insert(self.line_idx, line); + self.line_idx += 1; + } + + pub fn push_paragraph_before(&mut self, paragraph: String) { + for line in paragraph.split("\n") { + self.push_line_before(line.to_string()); + } + } + + /// Please note that the line pointer will not be shifted to the pushed element. + pub fn push_line_after(&mut self, line: String) { + self.lines.insert(self.line_idx + 1, line); + } +} diff --git a/codegen/bin/newcoin b/codegen/bin/newcoin index aeeec609d73..ee14b241f7a 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -18,4 +18,4 @@ end coin_id = command_line_args[0] -generate_skeleton(coin_id, "") +generate_skeleton(coin_id, "full") diff --git a/codegen/bin/newcoin-mobile-tests b/codegen/bin/newcoin-mobile-tests new file mode 100755 index 00000000000..d6ea29a84c9 --- /dev/null +++ b/codegen/bin/newcoin-mobile-tests @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +# Sript for creating new skeleton files for new coin mobile tests. See also `newcoin` or `newevmchain`. +# It is considered to be used by codegen-v2 tool until Swift and Android tests generating supported. +# 1. Add relevant entry to registry.json (in order to minimize merge conflict, don't add at the very end) +# 2. Invoke this script with the id of the coin, e.g.: codegen/bin/newcoin-mobile-tests ethereum + +require 'fileutils' + +CurrentDir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) +require 'coin_skeleton_gen' + +command_line_args = ARGV +if command_line_args.length < 1 + puts "Usage: newcoin-mobile-tests " + return +end + +coin_id = command_line_args[0] + +generate_skeleton(coin_id, "mobile-tests") diff --git a/codegen/lib/coin_skeleton_gen.rb b/codegen/lib/coin_skeleton_gen.rb index 29335e4fdb0..b0bcbe85e80 100755 --- a/codegen/lib/coin_skeleton_gen.rb +++ b/codegen/lib/coin_skeleton_gen.rb @@ -90,6 +90,37 @@ def self.insert_target_line(target_file, target_line, original_line) return true end +def generate_blockchain_files(coin) + name = format_name(coin) + + generate_file("newcoin/Address.h.erb", "src/#{name}", "Address.h", coin) + generate_file("newcoin/Address.cpp.erb", "src/#{name}", "Address.cpp", coin) + generate_file("newcoin/Entry.h.erb", "src/#{name}", "Entry.h", coin) + generate_file("newcoin/Entry.cpp.erb", "src/#{name}", "Entry.cpp", coin) + generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) + generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) + generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) + + generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) + generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) + generate_file("newcoin/TransactionCompilerTests.cpp.erb", "tests/chains/#{name}", "TransactionCompilerTests.cpp", coin) + generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) + generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) +end + +def generate_mobile_tests(coin) + name = format_name(coin) + + generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) + generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) + generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) +end + +def generate_coin_type_tests(coin) + coin_test_gen = CoinTestGen.new() + coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb', true) +end + def generate_skeleton(coin_id, mode) puts "New coin template for coin '#{coin_id}' #{mode} requested" @@ -103,8 +134,6 @@ def generate_skeleton(coin_id, mode) @coins = coins - coin_test_gen = CoinTestGen.new() - # Find coin in list of coins, by Id coinSelect = coins.select {|c| c['id'] == coin_id} if coinSelect.length() == 0 @@ -112,32 +141,19 @@ def generate_skeleton(coin_id, mode) return end coin = coinSelect.first - name = format_name(coin) - - insert_coin_type(coin, mode) - if (mode != "evm") + if (mode == "full") + insert_coin_type(coin, mode) insert_coin_entry(coin) - - generate_file("newcoin/Address.h.erb", "src/#{name}", "Address.h", coin) - generate_file("newcoin/Address.cpp.erb", "src/#{name}", "Address.cpp", coin) - generate_file("newcoin/Entry.h.erb", "src/#{name}", "Entry.h", coin) - generate_file("newcoin/Entry.cpp.erb", "src/#{name}", "Entry.cpp", coin) - generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) - generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) - generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) - - generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) - generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) - generate_file("newcoin/TransactionCompilerTests.cpp.erb", "tests/chains/#{name}", "TransactionCompilerTests.cpp", coin) - generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) - generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) - generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) - generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) - generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) + generate_blockchain_files(coin) + generate_mobile_tests(coin) + generate_coin_type_tests(coin) + elsif (mode == "evm") + insert_coin_type(coin, mode) + generate_coin_type_tests(coin) + elsif (mode == "mobile-tests") + generate_mobile_tests(coin) end - coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb', true) - puts "please tools/generate-files to generate Swift/Java/Protobuf files" end diff --git a/docs/registry-fields.md b/docs/registry-fields.md index f4662d510cd..ff6b0e539e6 100644 --- a/docs/registry-fields.md +++ b/docs/registry-fields.md @@ -31,7 +31,7 @@ Ex.: `10000118` for Osmosis, `118` for Cosmos; `20000714` for BNB Smart Chain. See also: `slip44` and `chainId`. **`slip44`** -Optionally, SLIP-44 (BIP-44) coin ID can be specified here, in case it differs from `coinId`. Most of the case the two are the same, so this can be omitted. +Optionally, SLIP-44 (BIP-44) coin ID can be specified here, in case it differs from `coinId`. In most cases the two are the same, so this can be omitted. Ex.: `60` for Optimism (coinID is `10000070`). **`symbol`** @@ -67,7 +67,7 @@ Note that the second number, the BIP-44 ID, usually matches the coinId. Some blockchains may support additional alternative derivations. These have: - a name -- a alternative derivation path (optional) +- an alternative derivation path (optional) Derivation may differ in the derivation path, or by address generation method (based on the derivation name). The first derivation is considered the default. @@ -126,7 +126,7 @@ Defines the prefix byte used in P2PKH and P2SH addresses, Bitcoin style. Ex. `0` and `5` for Bitcoin. **`hrp`** -Human Readable Prefix used to prefix an address, used to indicate type of address, to minimalize risk of accidental address mix-up across chains. +Human Readable Prefix used to prefix an address, used to indicate type of address, to minimize risk of accidental address mix-up across chains. Ex. `'bc'` for Bitcoin, `'cosmos'` for Cosmos. **`chainId`** @@ -150,7 +150,7 @@ Ex.: `'sha256d'` for Bitcoin, `'blake256d'` for Decred. **`addressHasher`** Hash method used in the publicKey -> address generation. -Only some chain implementation use this setting, in most implementation this is fixed (and value here is only informative). +Only some chain implementations use this setting, in most implementations this is fixed (and value here is only informative). Default is `sha256ripemd`. Ex.: missing ('sha256ripemd') for Bitcoin, `'keccak256'` for Ethereum, `'sha256ripemd'` for Cosmos, `'keccak256'` for Native Evmos, despite being a Cosmos fork. @@ -189,7 +189,7 @@ Link to the default implementation of the node or RPC gateway that can be used b Optional URL to an available public RPC service. **`info/documentation`** -Main porject documentation site/subsite. +Main project documentation site/subsite. **`deprecated`** If set to `true`, the project is considered deprecated: its info is kept here, but it will not be supported. diff --git a/docs/registry.md b/docs/registry.md index c32440788a3..b49763ff960 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -36,6 +36,7 @@ This list is generated from [./registry.json](../registry.json) | 194 | EOS | EOS | | | | 195 | Tron | TRX | | | | 204 | OpBNB | BNB | | | +| 223 | Internet Computer | ICP | | | | 235 | FIO | FIO | | | | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | @@ -70,7 +71,7 @@ This list is generated from [./registry.json](../registry.json) | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | | 888 | NEO | NEO | | | -| 889 | TomoChain | TOMO | | | +| 889 | Viction | VIC | | | | 899 | eCash | XEC | | | | 931 | THORChain | RUNE | | | | 966 | Polygon | MATIC | | | @@ -85,8 +86,10 @@ This list is generated from [./registry.json](../registry.json) | 2301 | Qtum | QTUM | | | | 2718 | Nebulas | NAS | | | | 3030 | Hedera | HBAR | | | +| 5000 | Mantle | MNT | | | | 5600 | BNB Greenfield | BNB | | | | 6060 | GoChain | GO | | | +| 7332 | Zen EON | ZEN | | | | 8453 | Base | ETH | | | | 8964 | NULS | NULS | | | | 14001 | WAX | WAXP | | | @@ -95,7 +98,7 @@ This list is generated from [./registry.json](../registry.json) | 52752 | Celo | CELO | | | | 59144 | Linea | ETH | | | | 105105 | Stratis | STRAX | | | -| 534353 | Scroll | ETH | | | +| 534352 | Scroll | ETH | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | | 10000025 | Cronos Chain | CRO | | | @@ -137,6 +140,7 @@ This list is generated from [./registry.json](../registry.json) | 20000118 | Stargaze | STARS | | | | 20000714 | BNB Smart Chain | BNB | | | | 20009001 | Native Evmos | EVMOS | | | +| 21000118 | Celestia | TIA | | | | 30000118 | Juno | JUNO | | | | 30000714 | TBNB | BNB | | | | 40000118 | Stride | STRD | | | diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index d715176ed7b..94187ca6899 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -64,6 +64,9 @@ enum TWBlockchain { TWBlockchainTheOpenNetwork = 49, TWBlockchainSui = 50, TWBlockchainGreenfield = 51, + TWBlockchainInternetComputer = 52, + TWBlockchainNativeEvmos = 53, // Cosmos + TWBlockchainNativeInjective = 54, // Cosmos }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 6fbf4df4cb8..6a09ac2f74f 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -72,7 +72,7 @@ enum TWCoinType { TWCoinTypeTheta = 500, TWCoinTypeThunderCore = 1001, TWCoinTypeNEO = 888, - TWCoinTypeTomoChain = 889, + TWCoinTypeViction = 889, TWCoinTypeTron = 195, TWCoinTypeVeChain = 818, TWCoinTypeViacoin = 14, @@ -120,7 +120,7 @@ enum TWCoinType { TWCoinTypeSyscoin = 57, TWCoinTypeVerge = 77, TWCoinTypeZen = 121, - TWCoinTypeMetis = 1001088, + TWCoinTypeMetis = 10001088, TWCoinTypeAurora = 1323161554, TWCoinTypeEvmos = 10009001, TWCoinTypeNativeEvmos = 20009001, @@ -162,7 +162,7 @@ enum TWCoinType { TWCoinTypePersistence = 16000118, TWCoinTypeAkash = 17000118, TWCoinTypeNoble = 18000118, - TWCoinTypeScroll = 534353, + TWCoinTypeScroll = 534352, TWCoinTypeRootstock = 137, TWCoinTypeThetaFuel = 361, TWCoinTypeConfluxeSpace = 1030, @@ -175,6 +175,11 @@ enum TWCoinType { TWCoinTypeArbitrumNova = 10042170, TWCoinTypeLinea = 59144, TWCoinTypeGreenfield = 5600, + TWCoinTypeMantle = 5000, + TWCoinTypeZenEON = 7332, + TWCoinTypeInternetComputer = 223, + TWCoinTypeTia = 21000118, + // end_of_tw_coin_type_marker_do_not_modify }; /// Returns the blockchain for a coin type. diff --git a/include/TrustWalletCore/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h index 18408f3ebff..33b976dc61a 100644 --- a/include/TrustWalletCore/TWEthereumAbi.h +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -7,8 +7,9 @@ #pragma once #include "TWBase.h" -#include "TWString.h" +#include "TWCoinType.h" #include "TWData.h" +#include "TWString.h" TW_EXTERN_C_BEGIN @@ -18,6 +19,38 @@ struct TWEthereumAbiFunction; TW_EXPORT_STRUCT struct TWEthereumAbi; +/// Decode a contract call (function input) according to an ABI json. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ContractCallDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ContractCallDecodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiDecodeContractCall(enum TWCoinType coin, TWData* _Nonnull input); + +/// Decode a function input or output data according to a given ABI. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ParamsDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ParamsDecodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiDecodeParams(enum TWCoinType coin, TWData* _Nonnull input); + +/// /// Decodes an Eth ABI value according to a given type. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ValueDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ValueDecodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiDecodeValue(enum TWCoinType coin, TWData* _Nonnull input); + +/// Encode function to Eth ABI binary. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.FunctionEncodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.FunctionEncodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiEncodeFunction(enum TWCoinType coin, TWData* _Nonnull input); + /// Encode function to Eth ABI binary /// /// \param fn Non-null Eth abi function diff --git a/include/TrustWalletCore/TWEthereumRlp.h b/include/TrustWalletCore/TWEthereumRlp.h new file mode 100644 index 00000000000..1644c86e1e2 --- /dev/null +++ b/include/TrustWalletCore/TWEthereumRlp.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "TWCoinType.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWEthereumRlp; + +/// Encode an item or a list of items as Eth RLP binary format. +/// +/// \param coin EVM-compatible coin type. +/// \param input Non-null serialized `EthereumRlp::Proto::EncodingInput`. +/// \return serialized `EthereumRlp::Proto::EncodingOutput`. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumRlpEncode(enum TWCoinType coin, TWData* _Nonnull input); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index 020fb2fcc9f..2e32b19f532 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -123,16 +123,6 @@ struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPri TW_EXPORT_METHOD struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey* _Nonnull pk); -/// Computes an EC Diffie-Hellman secret in constant time -/// Supported curves: secp256k1 -/// -/// \param pk Non-null pointer to a Private key -/// \param publicKey Non-null pointer to the corresponding public key -/// \param curve Eliptic curve -/// \return The corresponding shared key as a non-null block of data -TW_EXPORT_METHOD -TWData* _Nullable TWPrivateKeyGetSharedKey(const struct TWPrivateKey* _Nonnull pk, const struct TWPublicKey* _Nonnull publicKey, enum TWCurve curve); - /// Signs a digest using ECDSA and given curve. /// /// \param pk Non-null pointer to a Private key diff --git a/include/TrustWalletCore/TWTransactionCompiler.h b/include/TrustWalletCore/TWTransactionCompiler.h index 529bc12e302..5ec06883c40 100644 --- a/include/TrustWalletCore/TWTransactionCompiler.h +++ b/include/TrustWalletCore/TWTransactionCompiler.h @@ -20,6 +20,7 @@ struct TWTransactionCompiler; /// Builds a coin-specific SigningInput (proto object) from a simple transaction. /// +/// \deprecated `TWTransactionCompilerBuildInput` will be removed soon. /// \param coin coin type. /// \param from sender of the transaction. /// \param to receiver of the transaction. diff --git a/kotlin/.editorconfig b/kotlin/.editorconfig index aff6d71fbe4..87dad77100a 100644 --- a/kotlin/.editorconfig +++ b/kotlin/.editorconfig @@ -15,5 +15,7 @@ ij_kotlin_imports_layout = * ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma_on_call_site = true ij_kotlin_enum_constants_wrap = split_into_lines +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 ij_kotlin_packages_to_use_import_on_demand = * ij_kotlin_wrap_first_method_in_call_chain = true diff --git a/kotlin/wallet-core-kotlin/build.gradle.kts b/kotlin/wallet-core-kotlin/build.gradle.kts index ef9c1aca20c..3bea97954ca 100644 --- a/kotlin/wallet-core-kotlin/build.gradle.kts +++ b/kotlin/wallet-core-kotlin/build.gradle.kts @@ -1,4 +1,4 @@ -@file:Suppress("UnstableApiUsage") +@file:Suppress("UnstableApiUsage", "OPT_IN_USAGE") import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput @@ -10,11 +10,19 @@ plugins { } kotlin { + targetHierarchy.default() + android { publishLibraryVariants = listOf("release") } - jvm() + jvm { + testRuns.named("test") { + executionTask.configure { + useJUnitPlatform() + } + } + } val nativeTargets = listOf( @@ -50,6 +58,12 @@ kotlin { } } + getByName("commonTest") { + dependencies { + implementation(kotlin("test")) + } + } + val androidMain by getting val jvmMain by getting create("commonAndroidJvmMain") { @@ -60,16 +74,8 @@ kotlin { jvmMain.dependsOn(this) } - val iosArm64Main by getting - val iosSimulatorArm64Main by getting - val iosX64Main by getting - create("iosMain") { + getByName("iosMain") { kotlin.srcDir(projectDir.resolve("src/iosMain/generated")) - - dependsOn(commonMain) - iosArm64Main.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - iosX64Main.dependsOn(this) } getByName("jsMain") { diff --git a/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..1d4f9311712 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + throw NotImplementedError() + } +} diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..5d78d0dc298 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,5 @@ +package com.trustwallet.core + +expect object LibLoader { + fun loadLibrary() +} diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt new file mode 100644 index 00000000000..cec9314a3be --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -0,0 +1,148 @@ +package com.trustwallet.core.test + +import com.trustwallet.core.CoinType +import com.trustwallet.core.CoinType.* +import com.trustwallet.core.HDWallet +import com.trustwallet.core.LibLoader +import kotlin.test.Test +import kotlin.test.assertEquals + +class CoinAddressDerivationTests { + + init { + LibLoader.loadLibrary() + } + + @Test + fun testDeriveAddressesFromPhrase() { + val wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", "") + + CoinType.values().forEach { coin -> + val address = wallet.getAddressForCoin(coin) + val expectedAddress = getExpectedAddress(coin) + + assertEquals(expectedAddress, address, "Coin = $coin") + } + } + + private fun getExpectedAddress(coin: CoinType): String = when (coin) { + Binance -> "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw" + TBinance -> "tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl" + Bitcoin -> "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d" + BitcoinDiamond -> "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn" + BitcoinCash -> "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" + BitcoinGold -> "btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg" + Callisto -> "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" + Dash -> "XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT" + DigiByte -> "dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu" + + Ethereum, SmartChain, Polygon, Optimism, Zksync, Arbitrum, ArbitrumNova, ECOChain, AvalancheCChain, XDai, + Fantom, Celo, CronosChain, SmartBitcoinCash, KuCoinCommunityChain, Boba, Metis, + Aurora, Evmos, Moonriver, Moonbeam, KavaEvm, Klaytn, Meter, OKXChain, PolygonzkEVM, Scroll, + ConfluxeSpace, AcalaEVM, OpBNB, Neon, Base, Linea, Greenfield, Mantle, ZenEON, + -> "0x8f348F300873Fd5DA36950B2aC75a26584584feE" + + Ronin -> "ronin:8f348F300873Fd5DA36950B2aC75a26584584feE" + EthereumClassic -> "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c" + GoChain -> "0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2" + Groestlcoin -> "grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j" + ICON -> "hx18b380b53c23dc4ee9f6666bc20d1be02f3fe106" + Litecoin -> "ltc1qhd8fxxp2dx3vsmpac43z6ev0kllm4n53t5sk0u" + Ontology -> "AHKTnybvnWo3TeY8uvNXekvYxMrXogUjeT" + POANetwork -> "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9" + XRP -> "rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3" + Tezos -> "tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX" + ThunderCore -> "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7" + Viction -> "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424" + Tron -> "TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio" + VeChain -> "0x1a553275dF34195eAf23942CB7328AcF9d48c160" + Wanchain -> "0xD5ca90b928279FE5D06144136a25DeD90127aC15" + Komodo -> "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb" + Zcash -> "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" + Zen -> "znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX" + Firo -> "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" + Nimiq -> "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H" + Stellar -> "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" + Aion -> "0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e" + Nano -> "nano_39gsbcishxn3n7wd17ono4otq5wazwzusqgqigztx73wbrh5jwbdbshfnumc" + Nebulas -> "n1ZVgEidtdseYv9ogmGz69Cz4mbqmHYSNqJ" + NEAR -> "0c91f6106ff835c0195d5388565a2d69e25038a7e23d26198f85caf6594117ec" + Theta, ThetaFuel -> "0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4" + Cosmos -> "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn" + Decred -> "DsidJiDGceqHTyqiejABy1ZQ3FX4SiWZkYG" + Dogecoin -> "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f" + Kin -> "GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA" + Viacoin -> "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc" + Verge -> "DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2" + Qtum -> "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" + NULS -> "NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en" + EOS -> "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" + WAX -> "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" + IoTeX -> "io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk" + IoTeXEVM -> "0x038B8C633873Ca0f06961100BE5d37676EADDD23" + Zilliqa -> "zil1mk6pqphhkmaguhalq6n3cq0h38ltcehg0rfmv6" + Zelcash -> "t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf" + Ravencoin -> "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" + Waves -> "3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n" + Aeternity -> "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN" + Terra, TerraV2 -> "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug" + Monacoin -> "M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja" + FIO -> "FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3" + Harmony -> "one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09" + Solana -> "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m" + Algorand -> "JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ" + Acala -> "25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3" + Kusama -> "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY" + Polkadot -> "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" + Pivx -> "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm" + Kava -> "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" + Cardano -> "addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2" + NEO -> "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf" + Filecoin -> "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori" + MultiversX -> "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" + BandChain -> "band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r" + SmartChainLegacy -> "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" + Oasis -> "oasis1qzcpavvmuw280dk0kd4lxjhtpf0u3ll27yf7sqps" + THORChain -> "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65" + IOST -> "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" + Syscoin -> "sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj" + Stratis -> "strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm" + Bluzelle -> "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + CryptoOrg -> "cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8" + Osmosis -> "osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp" + ECash -> "ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377" + NativeEvmos -> "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d" + Nervos -> "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" + Everscale -> "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04" + TON -> "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9" + Aptos -> "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + Nebl -> "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7" + Sui -> "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2" + Hedera -> "0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5" + Secret -> "secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh" + NativeInjective -> "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a" + Agoric -> "agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5" + Stargaze -> "stars142j9u5eaduzd7faumygud6ruhdwme98qycayaz" + Juno -> "juno142j9u5eaduzd7faumygud6ruhdwme98qxkfz30" + Stride -> "stride142j9u5eaduzd7faumygud6ruhdwme98qn029zl" + Axelar -> "axelar142j9u5eaduzd7faumygud6ruhdwme98q52u3aj" + Crescent -> "cre142j9u5eaduzd7faumygud6ruhdwme98q5veur7" + Kujira -> "kujira142j9u5eaduzd7faumygud6ruhdwme98qpvgpme" + NativeCanto -> "canto13u6g7vqgw074mgmf2ze2cadzvkz9snlwqua5pd" + Comdex -> "comdex142j9u5eaduzd7faumygud6ruhdwme98qhtgm0y" + Neutron -> "neutron142j9u5eaduzd7faumygud6ruhdwme98q5mrmv5" + Sommelier -> "somm142j9u5eaduzd7faumygud6ruhdwme98quc948e" + FetchAI -> "fetch142j9u5eaduzd7faumygud6ruhdwme98qrera5y" + Mars -> "mars142j9u5eaduzd7faumygud6ruhdwme98qdenqrg" + Umee -> "umee142j9u5eaduzd7faumygud6ruhdwme98qzjhxjp" + Coreum -> "core1rawf376jz2lnchgc4wzf4h9c77neg3zldc7xa8" + Quasar -> "quasar142j9u5eaduzd7faumygud6ruhdwme98q78symk" + Persistence -> "persistence142j9u5eaduzd7faumygud6ruhdwme98q7gv2ch" + Akash -> "akash142j9u5eaduzd7faumygud6ruhdwme98qal870f" + Noble -> "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa" + Rootstock -> "0xA2D7065F94F838a3aB9C04D67B312056846424Df" + Sei -> "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" + InternetComputer -> "b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87" + Tia -> "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" + } +} diff --git a/kotlin/wallet-core-kotlin/src/iosTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/iosTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..1d4f9311712 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + throw NotImplementedError() + } +} diff --git a/kotlin/wallet-core-kotlin/src/jsTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/jsTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..1d4f9311712 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + throw NotImplementedError() + } +} diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt b/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt new file mode 100644 index 00000000000..23c20a24897 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt @@ -0,0 +1,60 @@ +package com.trustwallet.core + +import com.trustwallet.core.WalletCoreLibLoader.Linux64Path +import com.trustwallet.core.WalletCoreLibLoader.MacArm64Path +import java.io.File +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Simple helper class that extracts proper native library for current OS/arch from .jar to temporary file and loads it. + * You can do the same by yourself, just use path constants: [MacArm64Path], [Linux64Path] + */ +object WalletCoreLibLoader { + + const val MacArm64Path = "/jni/macos-arm64/libTrustWalletCore.dylib" + const val Linux64Path = "/jni/linux-x86_64/libTrustWalletCore.so" + + private val isLoaded = AtomicBoolean(false) + + @JvmStatic + fun loadLibrary() { + if (isLoaded.compareAndSet(false, true)) { + val resLibPath = getLibResourcePath() + val resLibStream = WalletCoreLibLoader::class.java.getResourceAsStream(resLibPath) + ?: error("File not found: $resLibPath") + + val fileOut = File.createTempFile("libTrustWalletCore", null) + fileOut.deleteOnExit() + + resLibStream.copyTo(fileOut.outputStream()) + + @Suppress("UnsafeDynamicallyLoadedCode") + System.load(fileOut.absolutePath) + } + } + + private fun getLibResourcePath(): String { + val osNameOriginal = System.getProperty("os.name").lowercase() + val osName = osNameOriginal.lowercase() + val archOriginal = System.getProperty("os.arch").lowercase() + val arch = archOriginal.lowercase() + + return when { + osName.startsWith("mac") -> { + when (arch) { + "aarch64" -> MacArm64Path + else -> error("Arch is not supported: $archOriginal") + } + } + + osName.startsWith("linux") -> { + when (arch) { + "amd64", "x86_64" -> Linux64Path + else -> error("Arch is not supported: $archOriginal") + } + } + + else -> error("OS is not supported: $osNameOriginal") + } + } +} diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/linux-x86_64/.gitignore b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/linux-x86_64/.gitignore new file mode 100644 index 00000000000..6519c51f274 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/linux-x86_64/.gitignore @@ -0,0 +1 @@ +libTrustWalletCore.so diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/.gitignore b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/.gitignore new file mode 100644 index 00000000000..9162c6ec1f1 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/.gitignore @@ -0,0 +1 @@ +libTrustWalletCore.dylib diff --git a/kotlin/wallet-core-kotlin/src/jvmTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/jvmTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..0c153abd9e7 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + WalletCoreLibLoader.loadLibrary() + } +} diff --git a/registry.json b/registry.json index e7c368edc3d..28031ea8d0e 100644 --- a/registry.json +++ b/registry.json @@ -410,6 +410,66 @@ "documentation": "https://docs.linea.build" } }, + { + "id": "mantle", + "name": "Mantle", + "coinId": 5000, + "symbol": "MNT", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "5000", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.mantle.xyz", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xfae996ea23f1ff9909ac04d26ae6e52ab600a84163fab9e0e893483c685629dd", + "sampleAccount": "0xA295EEFd401C8BE1457F266d3e73cdD015e5CFbb" + }, + "info": { + "url": "https://www.mantle.xyz", + "source": "https://github.com/mantlenetworkio", + "rpc": "https://rpc.mantle.xyz", + "documentation": "https://docs.mantle.xyz/network/introduction/overview" + } + }, + { + "id": "zeneon", + "name": "Zen EON", + "coinId": 7332, + "symbol": "ZEN", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "7332", + "addressHasher": "keccak256", + "explorer": { + "url": "https://eon-explorer.horizenlabs.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb462e3dac8eef21957d3b6cff3c184d083434367a726dd871e98a774f4d037a5", + "sampleAccount": "0x09bCfC348101B1179BCF3837aC996cF09357215f" + }, + "info": { + "url": "https://eon.horizen.io", + "source": "https://github.com/HorizenOfficial/eon", + "rpc": "https://eon-rpc.horizenlabs.io/ethv1", + "documentation": "https://eon.horizen.io/docs" + } + }, { "id": "ethereum", "name": "Ethereum", @@ -1106,6 +1166,36 @@ "documentation": "https://docs.sei.io/" } }, + { + "id": "tia", + "name": "Tia", + "displayName": "Celestia", + "coinId": 21000118, + "symbol": "TIA", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "celestia", + "derivation": [ + { + "path": "m/44'/118'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "celestia", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/celestia", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B", + "sampleAccount": "celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2" + }, + "info": { + "url": "https://celestia.org/", + "documentation": "https://docs.celestia.org/" + } + }, { "id": "coreum", "name": "Coreum", @@ -1703,7 +1793,6 @@ "documentation": "https://developer.algorand.org/docs/algod-rest-paths" } }, - { "id": "iotex", "name": "IoTeX", @@ -2484,10 +2573,10 @@ } }, { - "id": "tomochain", - "name": "TomoChain", + "id": "viction", + "name": "Viction", "coinId": 889, - "symbol": "TOMO", + "symbol": "VIC", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2500,15 +2589,15 @@ "chainId": "88", "addressHasher": "keccak256", "explorer": { - "url": "https://tomoscan.io", + "url": "https://www.vicscan.xyz", "txPath": "/tx/", "accountPath": "/address/", "sampleTx": "0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b", "sampleAccount": "0x86cCbD9bfb371c355202086882bC644A7D0b024B" }, "info": { - "url": "https://tomochain.com", - "source": "https://github.com/tomochain/tomochain", + "url": "https://www.viction.xyz/", + "source": "https://github.com/BuildOnViction/tomochain", "rpc": "https://rpc.tomochain.com", "documentation": "https://eth.wiki/json-rpc/API" } @@ -3088,6 +3177,7 @@ "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "thor", + "addressHasher": "sha256ripemd", "chainId": "thorchain-mainnet-v1", "explorer": { "url": "https://viewblock.io/thorchain", @@ -3199,7 +3289,7 @@ "id": "scroll", "name": "Scroll", "displayName": "Scroll", - "coinId": 534353, + "coinId": 534352, "slip44": 60, "symbol": "ETH", "decimals": 18, @@ -3211,19 +3301,19 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "534353", + "chainId": "534352", "addressHasher": "keccak256", "explorer": { - "url": "https://blockscout.scroll.io", + "url": "https://scrollscan.com", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0xee9196d6840c8d31626324d91c886d20e65711c2026c559133fb23741d3b2f9d", - "sampleAccount": "0xFE993660cd35d68D94b6Eba29F4D928d979cd65B" + "sampleTx": "0xa2062a4530b194a438bb9f9e87cdce59f70775a52e8336892364445847c43ca2", + "sampleAccount": "0xf9062b8a30e0d7722960e305049fa50b86ba6253" }, "info": { "url": "https://scroll.io", "source": "https://github.com/scroll-tech", - "rpc": "https://alpha-rpc.scroll.io/l2", + "rpc": "https://rpc.scroll.io", "documentation": "https://guide.scroll.io" } }, @@ -3884,7 +3974,7 @@ "coinId": 20009001, "symbol": "EVMOS", "decimals": 18, - "blockchain": "Cosmos", + "blockchain": "NativeEvmos", "chainId": "evmos_9001-2", "derivation": [ { @@ -4088,7 +4178,7 @@ "coinId": 5600, "symbol": "BNB", "decimals": 18, - "chainId": "5600", + "chainId": "9000", "blockchain": "Greenfield", "derivation": [ { @@ -4102,8 +4192,8 @@ "url": "https://greenfieldscan.com", "txPath": "/tx/", "accountPath": "/account/", - "sampleTx": "9F895CF2DD64FB1F428CEFCF2A6585A813C3540FC9FE1EF42DB1DA2CB1DF55AB", - "sampleAccount": "0x9d1d97adfcd324bbd603d3872bd78e04098510b1" + "sampleTx": "0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3", + "sampleAccount": "0xcf0f6b88ed72653b00fdebbffc90b98072cb3285" }, "info": { "url": "https://greenfield.bnbchain.org", @@ -4276,7 +4366,7 @@ "coinId": 10000060, "symbol": "INJ", "decimals": 18, - "blockchain": "Cosmos", + "blockchain": "NativeInjective", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -4386,5 +4476,35 @@ "rpc": "https://neon-proxy-mainnet.solana.p2p.org/", "documentation": "https://docs.neonfoundation.io/docs/quick_start" } + }, + { + "id": "internet_computer", + "name": "Internet Computer", + "coinId": 223, + "symbol": "ICP", + "decimals": 8, + "blockchain": "InternetComputer", + "derivation": [ + { + "path": "m/44'/223'/1'/0/0", + "xpub": "xpub", + "xpriv": "xpriv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://dashboard.internetcomputer.org", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85", + "sampleAccount": "529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b" + }, + "info": { + "url": "https://internetcomputer.org", + "source": "https://github.com/dfinity/ic", + "rpc": "", + "documentation": "https://internetcomputer.org/docs" + } } ] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3199454c20e..24a18ba843f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -22,9 +22,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ark-ff" @@ -90,6 +99,12 @@ dependencies = [ "rand", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "atty" version = "0.2.14" @@ -107,11 +122,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bcs" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b06b4c1f053002b70e7084ac944c77d58d5d92b2110dbc5e852735e00ad3ccc" +checksum = "85b6598a2f5d564fb7855dc6b06fd1c38cff5a72bd8b863a4d021938497b440a" dependencies = [ "serde", "thiserror", @@ -125,9 +152,9 @@ checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] name = "bigdecimal" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" dependencies = [ "num-bigint", "num-integer", @@ -137,9 +164,9 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b36f4c848f6bd9ff208128f08751135846cc23ae57d66ab10a22efff1c675f3c" +checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75" dependencies = [ "bech32", "bitcoin-private", @@ -169,6 +196,30 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" +dependencies = [ + "funty 1.1.0", + "radium 0.6.2", + "tap", + "wyz 0.2.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", +] + [[package]] name = "blake-hash" version = "0.4.1" @@ -182,11 +233,13 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.6" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" dependencies = [ - "digest 0.10.6", + "crypto-mac", + "digest 0.9.0", + "opaque-debug", ] [[package]] @@ -225,12 +278,24 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cc" version = "1.0.79" @@ -243,6 +308,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "2.34.0" @@ -258,6 +350,12 @@ dependencies = [ "vec_map", ] +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -267,13 +365,20 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" dependencies = [ "generic-array", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -288,17 +393,44 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "der" -version = "0.7.6" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +checksum = "82b10af9f9f9f2134a42d3f8aa74658660f2e0234b0eb81bd171df8aa32779ed" +dependencies = [ + "const-oid", + "zeroize", +] [[package]] name = "derivative" @@ -311,6 +443,17 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "digest" version = "0.9.0" @@ -327,16 +470,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "ecdsa" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" +dependencies = [ + "der", + "digest 0.10.6", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "elliptic-curve" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.6", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -350,6 +527,65 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + +[[package]] +name = "ethnum" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8ff382b2fa527fb7fb06eeebfc5bbb3f17e3cc6b9d70b006c41daa8824adac" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "arbitrary", + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.6" @@ -358,6 +594,18 @@ checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -369,7 +617,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -382,6 +630,35 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -403,6 +680,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -421,6 +707,54 @@ dependencies = [ "quick-error", ] +[[package]] +name = "impl-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" +dependencies = [ + "parity-scale-codec 2.3.1", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec 3.5.0", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itertools" version = "0.10.5" @@ -445,6 +779,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.6", + "signature", +] + [[package]] name = "keccak" version = "0.1.3" @@ -454,6 +802,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.141" @@ -484,16 +838,20 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "move-core-types" version = "0.0.4" -source = "git+https://github.com/move-language/move?rev=f7137eabc2046f76fdad3ded2c51e03a3b1fbd01#f7137eabc2046f76fdad3ded2c51e03a3b1fbd01" +source = "git+https://github.com/move-language/move?rev=ea70797099baea64f05194a918cebd69ed02b285#ea70797099baea64f05194a918cebd69ed02b285" dependencies = [ "anyhow", "bcs", + "ethnum", "hex", + "num", "once_cell", + "primitive-types 0.10.1", "rand", "ref-cast", "serde", "serde_bytes", + "uint", ] [[package]] @@ -506,6 +864,20 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -517,6 +889,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -527,6 +908,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -543,16 +947,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] -name = "paste" -version = "1.0.11" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "pb-rs" -version = "0.10.0" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.6", +] + +[[package]] +name = "parity-scale-codec" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" +dependencies = [ + "arrayvec", + "bitvec 0.20.4", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive 2.3.1", + "serde", +] + +[[package]] +name = "parity-scale-codec" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +dependencies = [ + "arrayvec", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive 3.1.4", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + +[[package]] +name = "pb-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" dependencies = [ "clap", "env_logger", @@ -560,17 +1034,69 @@ dependencies = [ "nom", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primeorder" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash 0.7.0", + "impl-codec 0.5.1", + "impl-serde", + "uint", +] + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec 0.6.0", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -592,13 +1118,25 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -607,7 +1145,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -617,7 +1155,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -626,7 +1173,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.9", ] [[package]] @@ -676,6 +1223,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -685,6 +1247,22 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.4.0" @@ -694,12 +1272,32 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "sec1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.27.0" @@ -728,38 +1326,38 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -777,6 +1375,19 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.6" @@ -798,6 +1409,32 @@ dependencies = [ "keccak", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.6", + "rand_core 0.6.4", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "starknet-crypto" version = "0.5.1" @@ -811,7 +1448,7 @@ dependencies = [ "num-integer", "num-traits", "rfc6979", - "sha2", + "sha2 0.10.6", "starknet-crypto-codegen", "starknet-curve", "starknet-ff", @@ -826,7 +1463,7 @@ checksum = "e6dc88f1f470d9de1001ffbb90d2344c9dd1a615f5467daf0574e2975dfd9ebd" dependencies = [ "starknet-curve", "starknet-ff", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -847,17 +1484,42 @@ dependencies = [ "ark-ff", "bigdecimal", "crypto-bigint", - "getrandom", + "getrandom 0.2.9", "hex", "serde", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.37", +] + [[package]] name = "subtle" version = "2.4.1" @@ -877,9 +1539,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -898,6 +1560,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "termcolor" version = "1.2.0" @@ -936,6 +1604,71 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tw_any_coin" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tw_any_coin", + "tw_coin_entry", + "tw_coin_registry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_aptos" +version = "0.1.0" +dependencies = [ + "move-core-types", + "serde", + "serde_bytes", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_bech32_address" +version = "0.1.0" +dependencies = [ + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", +] + [[package]] name = "tw_bitcoin" version = "0.1.0" @@ -944,9 +1677,84 @@ dependencies = [ "secp256k1", "serde", "serde_json", + "tw_coin_entry", "tw_encoding", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", + "tw_utxo", + "wallet-core-rs", +] + +[[package]] +name = "tw_coin_entry" +version = "0.1.0" +dependencies = [ + "serde_json", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_coin_registry" +version = "0.1.0" +dependencies = [ + "itertools", + "lazy_static", + "serde", + "serde_json", + "strum", + "strum_macros", + "tw_aptos", + "tw_bitcoin", + "tw_coin_entry", + "tw_cosmos", + "tw_ethereum", + "tw_evm", + "tw_hash", + "tw_internet_computer", + "tw_keypair", "tw_memory", "tw_misc", + "tw_native_evmos", + "tw_native_injective", + "tw_ronin", + "tw_thorchain", +] + +[[package]] +name = "tw_cosmos" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_proto", +] + +[[package]] +name = "tw_cosmos_sdk" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "serde_json", + "tw_bech32_address", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", "tw_proto", ] @@ -954,37 +1762,111 @@ dependencies = [ name = "tw_encoding" version = "0.1.0" dependencies = [ + "arbitrary", + "bcs", + "bech32", "bs58", + "ciborium", "data-encoding", "hex", + "serde", + "serde_bytes", "tw_memory", ] +[[package]] +name = "tw_ethereum" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_evm", + "tw_keypair", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_evm" +version = "0.1.0" +dependencies = [ + "itertools", + "lazy_static", + "rlp", + "serde", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + [[package]] name = "tw_hash" version = "0.1.0" dependencies = [ + "arbitrary", "blake-hash", - "blake2", "blake2b-ref", "digest 0.10.6", "groestl", - "hex", "hmac", "ripemd", + "serde", + "serde_json", "sha1", - "sha2", + "sha2 0.10.6", "sha3", + "tw_encoding", + "tw_memory", + "zeroize", +] + +[[package]] +name = "tw_internet_computer" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", "tw_memory", + "tw_proto", ] [[package]] name = "tw_keypair" version = "0.1.0" dependencies = [ + "arbitrary", + "blake2", + "curve25519-dalek", "der", + "digest 0.9.0", + "ecdsa", + "k256", + "lazy_static", + "p256", + "pkcs8", + "rfc6979", + "ring", + "serde", + "serde_json", + "sha2 0.9.9", + "starknet-crypto", + "starknet-ff", "tw_encoding", + "tw_hash", "tw_memory", + "tw_misc", + "zeroize", ] [[package]] @@ -995,16 +1877,43 @@ version = "0.1.0" name = "tw_misc" version = "0.1.0" dependencies = [ + "serde", + "serde_json", "zeroize", ] [[package]] -name = "tw_move_parser" +name = "tw_native_evmos" version = "0.1.0" dependencies = [ - "bcs", - "hex", - "move-core-types", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_native_injective" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_number" +version = "0.1.0" +dependencies = [ + "arbitrary", + "lazy_static", + "primitive-types 0.12.1", + "serde", + "tw_encoding", + "tw_hash", "tw_memory", ] @@ -1012,6 +1921,7 @@ dependencies = [ name = "tw_proto" version = "0.1.0" dependencies = [ + "arbitrary", "pb-rs", "quick-protobuf", "tw_encoding", @@ -1019,14 +1929,40 @@ dependencies = [ ] [[package]] -name = "tw_starknet" +name = "tw_ronin" version = "0.1.0" dependencies = [ - "hex", - "starknet-crypto", - "starknet-ff", + "tw_coin_entry", "tw_encoding", + "tw_evm", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_thorchain" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_utxo" +version = "0.1.0" +dependencies = [ + "bitcoin", + "secp256k1", + "tw_coin_entry", + "tw_encoding", + "tw_keypair", + "tw_memory", + "tw_proto", ] [[package]] @@ -1035,6 +1971,19 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "arbitrary", + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.6" @@ -1053,6 +2002,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "vec_map" version = "0.8.2" @@ -1069,16 +2024,28 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "wallet-core-rs" version = "0.1.0" dependencies = [ + "serde_json", + "tw_any_coin", + "tw_aptos", "tw_bitcoin", + "tw_coin_entry", + "tw_coin_registry", "tw_encoding", + "tw_ethereum", "tw_hash", "tw_keypair", "tw_memory", - "tw_move_parser", + "tw_misc", + "tw_number", "tw_proto", - "tw_starknet", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1139,6 +2106,16 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1170,6 +2147,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winnow" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2a3f3dd5dfc..87d437eff4c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,13 +1,38 @@ [workspace] members = [ + "chains/tw_cosmos", + "chains/tw_native_evmos", + "chains/tw_native_injective", + "chains/tw_thorchain", + "tw_any_coin", + "tw_aptos", + "tw_bech32_address", "tw_bitcoin", + "tw_coin_entry", + "tw_coin_registry", + "tw_cosmos_sdk", "tw_encoding", + "tw_ethereum", + "tw_evm", "tw_hash", + "tw_internet_computer", "tw_keypair", "tw_memory", "tw_misc", - "tw_move_parser", + "tw_number", "tw_proto", - "tw_starknet", + "tw_ronin", + "tw_utxo", "wallet_core_rs", ] + +[profile.wasm-test] +inherits = "release" +# Fixes an incredibly slow compilation of `curve25519-dalek` package. +opt-level = 1 +debug = true +debug-assertions = true +overflow-checks = true + +[profile.release.package.curve25519-dalek] +opt-level = 2 diff --git a/rust/chains/tw_cosmos/Cargo.toml b/rust/chains/tw_cosmos/Cargo.toml new file mode 100644 index 00000000000..cbeae311ec3 --- /dev/null +++ b/rust/chains/tw_cosmos/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tw_cosmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_cosmos/src/entry.rs b/rust/chains/tw_cosmos/src/entry.rs new file mode 100644 index 00000000000..ca2d84efcf7 --- /dev/null +++ b/rust/chains/tw_cosmos/src/entry.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct CosmosEntry; + +impl CoinEntry for CosmosEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/tw_starknet/src/lib.rs b/rust/chains/tw_cosmos/src/lib.rs similarity index 89% rename from rust/tw_starknet/src/lib.rs rename to rust/chains/tw_cosmos/src/lib.rs index fc5cac0c0ae..1afc00798db 100644 --- a/rust/tw_starknet/src/lib.rs +++ b/rust/chains/tw_cosmos/src/lib.rs @@ -4,5 +4,4 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod ffi; -pub mod key_pair; +pub mod entry; diff --git a/rust/chains/tw_native_evmos/Cargo.toml b/rust/chains/tw_native_evmos/Cargo.toml new file mode 100644 index 00000000000..cc4762a7971 --- /dev/null +++ b/rust/chains/tw_native_evmos/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_native_evmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_evmos/src/context.rs b/rust/chains/tw_native_evmos/src/context.rs new file mode 100644 index 00000000000..edb431767ac --- /dev/null +++ b/rust/chains/tw_native_evmos/src/context.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ethermint_public_key::EthermintEthSecp256PublicKey; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; + +pub struct NativeEvmosContext; + +impl CosmosContext for NativeEvmosContext { + type Address = Address; + type TxHasher = Keccak256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = EthermintEthSecp256PublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/chains/tw_native_evmos/src/entry.rs b/rust/chains/tw_native_evmos/src/entry.rs new file mode 100644 index 00000000000..eb25d7ea816 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/entry.rs @@ -0,0 +1,89 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::NativeEvmosContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeEvmosEntry; + +impl CoinEntry for NativeEvmosEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs new file mode 100644 index 00000000000..5f53f261e5b --- /dev/null +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::proto::ethermint; +use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::KeyPairResult; +use tw_keypair::{tw, KeyPairError}; +use tw_memory::Data; +use tw_proto::{google, to_any}; + +pub struct EthermintEthSecp256PublicKey { + public_key: Data, +} + +impl EthermintEthSecp256PublicKey { + pub fn new(public_key: &secp256k1::PublicKey) -> KeyPairResult { + Ok(EthermintEthSecp256PublicKey { + // NativeEvmos chain requires the public key to be compressed. + // This trick is needed because `registry.json` contains extended public key type. + public_key: public_key.compressed().to_vec(), + }) + } +} + +impl CosmosPublicKey for EthermintEthSecp256PublicKey { + fn from_private_key(coin: &dyn CoinContext, private_key: &tw::PrivateKey) -> KeyPairResult + where + Self: Sized, + { + let tw_public_key = private_key.get_public_key_by_type(coin.public_key_type())?; + let secp256k1_key = tw_public_key + .to_secp256k1() + .ok_or(KeyPairError::InvalidPublicKey)?; + EthermintEthSecp256PublicKey::new(secp256k1_key) + } + + fn from_bytes(_coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; + EthermintEthSecp256PublicKey::new(&public_key) + } + + fn to_bytes(&self) -> Data { + self.public_key.clone() + } +} + +impl JsonPublicKey for EthermintEthSecp256PublicKey { + fn public_key_type(&self) -> String { + const ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE: &str = "ethermint/PubKeyEthSecp256k1"; + + ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE.to_string() + } +} + +impl ProtobufPublicKey for EthermintEthSecp256PublicKey { + fn to_proto(&self) -> google::protobuf::Any { + let proto = ethermint::crypto::v1::ethsecp256k1::PubKey { + key: self.public_key.clone(), + }; + to_any(&proto) + } +} diff --git a/rust/chains/tw_native_evmos/src/lib.rs b/rust/chains/tw_native_evmos/src/lib.rs new file mode 100644 index 00000000000..d7aa4534c83 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod context; +pub mod entry; +pub mod ethermint_public_key; diff --git a/rust/chains/tw_native_injective/Cargo.toml b/rust/chains/tw_native_injective/Cargo.toml new file mode 100644 index 00000000000..d793539a241 --- /dev/null +++ b/rust/chains/tw_native_injective/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_native_injective" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_injective/src/context.rs b/rust/chains/tw_native_injective/src/context.rs new file mode 100644 index 00000000000..898c034d656 --- /dev/null +++ b/rust/chains/tw_native_injective/src/context.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::injective_public_key::InjectiveEthSecp256PublicKey; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; + +pub struct NativeInjectiveContext; + +impl CosmosContext for NativeInjectiveContext { + type Address = Address; + type TxHasher = Keccak256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = InjectiveEthSecp256PublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/chains/tw_native_injective/src/entry.rs b/rust/chains/tw_native_injective/src/entry.rs new file mode 100644 index 00000000000..b11d0036c87 --- /dev/null +++ b/rust/chains/tw_native_injective/src/entry.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::NativeInjectiveContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeInjectiveEntry; + +impl CoinEntry for NativeInjectiveEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs new file mode 100644 index 00000000000..084d47e94f0 --- /dev/null +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -0,0 +1,57 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::proto::injective; +use tw_cosmos_sdk::public_key::secp256k1::prepare_secp256k1_public_key; +use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_keypair::tw::PrivateKey; +use tw_keypair::KeyPairResult; +use tw_memory::Data; +use tw_proto::{google, to_any}; + +pub struct InjectiveEthSecp256PublicKey { + public_key: Data, +} + +impl CosmosPublicKey for InjectiveEthSecp256PublicKey { + fn from_private_key(coin: &dyn CoinContext, private_key: &PrivateKey) -> KeyPairResult + where + Self: Sized, + { + let public_key = private_key.get_public_key_by_type(coin.public_key_type())?; + Ok(InjectiveEthSecp256PublicKey { + public_key: public_key.to_bytes(), + }) + } + + fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; + Ok(InjectiveEthSecp256PublicKey { public_key }) + } + + fn to_bytes(&self) -> Data { + self.public_key.clone() + } +} + +impl JsonPublicKey for InjectiveEthSecp256PublicKey { + fn public_key_type(&self) -> String { + /// https://github.com/cosmostation/cosmostation-chrome-extension/blob/e2fd27d71a17993f8eef07ce30f7a04a32e52788/src/constants/cosmos.ts#L4 + const INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE: &str = "injective/PubKeyEthSecp256k1"; + + INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE.to_string() + } +} + +impl ProtobufPublicKey for InjectiveEthSecp256PublicKey { + fn to_proto(&self) -> google::protobuf::Any { + let proto = injective::crypto::v1beta1::ethsecp256k1::PubKey { + key: self.public_key.clone(), + }; + to_any(&proto) + } +} diff --git a/rust/chains/tw_native_injective/src/lib.rs b/rust/chains/tw_native_injective/src/lib.rs new file mode 100644 index 00000000000..a61663bd72c --- /dev/null +++ b/rust/chains/tw_native_injective/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod context; +pub mod entry; +pub mod injective_public_key; diff --git a/rust/chains/tw_thorchain/Cargo.toml b/rust/chains/tw_thorchain/Cargo.toml new file mode 100644 index 00000000000..a39bc4d7c80 --- /dev/null +++ b/rust/chains/tw_thorchain/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_thorchain" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_thorchain/src/compiler.rs b/rust/chains/tw_thorchain/src/compiler.rs new file mode 100644 index 00000000000..a6fd1b8bbc8 --- /dev/null +++ b/rust/chains/tw_thorchain/src/compiler.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signing_input::ThorchainSigningInput; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ThorchainCompiler; + +impl ThorchainCompiler { + pub fn preimage_hashes( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + pub fn compile( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_thorchain/src/entry.rs b/rust/chains/tw_thorchain/src/entry.rs new file mode 100644 index 00000000000..81f8bf562f1 --- /dev/null +++ b/rust/chains/tw_thorchain/src/entry.rs @@ -0,0 +1,88 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::compiler::ThorchainCompiler; +use crate::signer::ThorchainSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ThorchainEntry; + +impl CoinEntry for ThorchainEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + ThorchainSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + ThorchainCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + ThorchainCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_thorchain/src/lib.rs b/rust/chains/tw_thorchain/src/lib.rs new file mode 100644 index 00000000000..be313341897 --- /dev/null +++ b/rust/chains/tw_thorchain/src/lib.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod compiler; +pub mod entry; +pub mod signer; +pub mod signing_input; diff --git a/rust/chains/tw_thorchain/src/signer.rs b/rust/chains/tw_thorchain/src/signer.rs new file mode 100644 index 00000000000..d086eacd1c3 --- /dev/null +++ b/rust/chains/tw_thorchain/src/signer.rs @@ -0,0 +1,23 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signing_input::ThorchainSigningInput; +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_proto::Cosmos::Proto; + +pub struct ThorchainSigner; + +impl ThorchainSigner { + pub fn sign( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWSigner::::sign(coin, input) + } +} diff --git a/rust/chains/tw_thorchain/src/signing_input.rs b/rust/chains/tw_thorchain/src/signing_input.rs new file mode 100644 index 00000000000..66863f7288f --- /dev/null +++ b/rust/chains/tw_thorchain/src/signing_input.rs @@ -0,0 +1,23 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +const THORCHAIN_PREFIX_MSG_SEND: &str = "thorchain/MsgSend"; + +pub struct ThorchainSigningInput; + +impl ThorchainSigningInput { + pub fn prepare_signing_input(input: &mut Proto::SigningInput) { + for message in input.messages.iter_mut() { + if let MessageEnum::send_coins_message(ref mut msg_send) = message.message_oneof { + msg_send.type_prefix = Cow::from(THORCHAIN_PREFIX_MSG_SEND); + } + } + } +} diff --git a/rust/coverage.stats b/rust/coverage.stats index 2d567ce4c07..7d7ab43dc7c 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -86.4 \ No newline at end of file +92.0 \ No newline at end of file diff --git a/rust/tw_any_coin/Cargo.toml b/rust/tw_any_coin/Cargo.toml new file mode 100644 index 00000000000..e2aeea6d021 --- /dev/null +++ b/rust/tw_any_coin/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tw_any_coin" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../tw_coin_entry" } +tw_coin_registry = { path = "../tw_coin_registry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } + +[features] +test-utils = [] + +[dev-dependencies] +serde = { version = "1.0.163", features = ["derive"] } +serde_json = { version = "1.0.96" } +tw_any_coin = { path = "./", features = ["test-utils"] } +tw_cosmos_sdk = { path = "../tw_cosmos_sdk", features = ["test-utils"] } +tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } +tw_memory = { path = "../tw_memory", features = ["test-utils"] } +tw_misc = { path = "../tw_misc", features = ["test-utils"] } +tw_number = { path = "../tw_number" } +tw_proto = { path = "../tw_proto" } diff --git a/rust/tw_any_coin/src/any_address.rs b/rust/tw_any_coin/src/any_address.rs new file mode 100644 index 00000000000..5d5098482a9 --- /dev/null +++ b/rust/tw_any_coin/src/any_address.rs @@ -0,0 +1,80 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::prefix::AddressPrefix; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; +use tw_keypair::tw::PublicKey; +use tw_memory::Data; +use tw_misc::try_or_false; + +/// Represents an address in Rust for almost any blockchain. +#[derive(Debug, PartialEq)] +pub struct AnyAddress { + coin: CoinType, + address: String, +} + +impl AnyAddress { + /// Determines if the string is a valid Any address. + #[inline] + pub fn is_valid(coin: CoinType, address: &str, prefix: Option) -> bool { + let (ctx, entry) = try_or_false!(coin_dispatcher(coin)); + entry.validate_address(&ctx, address, prefix).is_ok() + } + + /// Creates an address from a string representation and a coin type. + #[inline] + pub fn with_string( + coin: CoinType, + address: &str, + prefix: Option, + ) -> AddressResult { + let (ctx, entry) = coin_dispatcher(coin).map_err(|_| AddressError::UnknownCoinType)?; + entry.validate_address(&ctx, address, prefix)?; + let address = entry.normalize_address(&ctx, address)?; + Ok(AnyAddress { coin, address }) + } + + /// Creates an address from a string representation and a coin type. + /// Please note that his function does not validate if the address belongs to the given chain. + pub(crate) fn with_string_unchecked( + coin: CoinType, + address: &str, + ) -> AddressResult { + let (ctx, entry) = coin_dispatcher(coin).map_err(|_| AddressError::UnknownCoinType)?; + let address = entry.normalize_address(&ctx, address)?; + Ok(AnyAddress { coin, address }) + } + + /// Creates an address from a public key, derivation and prefix option. + #[inline] + pub fn with_public_key( + coin: CoinType, + public_key: PublicKey, + derivation: Derivation, + prefix: Option, + ) -> AddressResult { + let (ctx, entry) = coin_dispatcher(coin).map_err(|_| AddressError::UnknownCoinType)?; + let address = entry.derive_address(&ctx, public_key, derivation, prefix)?; + Ok(AnyAddress { coin, address }) + } + + /// Returns underlying data (public key or key hash). + #[inline] + pub fn get_data(&self) -> AddressResult { + let (ctx, entry) = coin_dispatcher(self.coin).map_err(|_| AddressError::UnknownCoinType)?; + entry.address_to_data(&ctx, &self.address) + } + + /// Returns the address string representation. + #[inline] + pub fn description(&self) -> &str { + &self.address + } +} diff --git a/rust/tw_any_coin/src/any_signer.rs b/rust/tw_any_coin/src/any_signer.rs new file mode 100644 index 00000000000..eea6859517b --- /dev/null +++ b/rust/tw_any_coin/src/any_signer.rs @@ -0,0 +1,30 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::{SigningError, SigningResult}; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; +use tw_memory::Data; + +/// Represents a signer to sign transactions for any blockchain. +pub struct AnySigner; + +impl AnySigner { + /// Signs a transaction specified by the signing input and coin type. + #[inline] + pub fn sign(input: &[u8], coin: CoinType) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry.sign(&ctx, input).map_err(SigningError::from) + } + + /// Planning, for UTXO chains, in preparation for signing + /// It is optional, only UTXO chains need it, default impl. leaves empty result. + #[inline] + pub fn plan(input: &[u8], coin: CoinType) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry.plan(&ctx, input) + } +} diff --git a/rust/tw_any_coin/src/ffi/mod.rs b/rust/tw_any_coin/src/ffi/mod.rs new file mode 100644 index 00000000000..62c144645d8 --- /dev/null +++ b/rust/tw_any_coin/src/ffi/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod tw_any_address; +pub mod tw_any_signer; +pub mod tw_message_signer; +pub mod tw_transaction_compiler; diff --git a/rust/tw_any_coin/src/ffi/tw_any_address.rs b/rust/tw_any_coin/src/ffi/tw_any_address.rs new file mode 100644 index 00000000000..ba8d39163c5 --- /dev/null +++ b/rust/tw_any_coin/src/ffi/tw_any_address.rs @@ -0,0 +1,188 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::any_address::AnyAddress; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::prefix::AddressPrefix; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::ffi::pubkey::TWPublicKey; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; + +/// Represents an address in Rust for almost any blockchain. +pub struct TWAnyAddress(AnyAddress); + +impl RawPtrTrait for TWAnyAddress {} + +/// Determines if the string is a valid Any address. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \return bool indicating if the address is valid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_is_valid(string: *const TWString, coin: u32) -> bool { + let string = try_or_false!(TWString::from_ptr_as_ref(string)); + let string = try_or_false!(string.as_str()); + let coin = try_or_false!(CoinType::try_from(coin)); + + AnyAddress::is_valid(coin, string, None) +} + +/// Determines if the string is a valid Any address with the given hrp. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \param hrp explicit given hrp of the given address. +/// \return bool indicating if the address is valid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_is_valid_bech32( + string: *const TWString, + coin: u32, + hrp: *const TWString, +) -> bool { + let string = try_or_false!(TWString::from_ptr_as_ref(string)); + let string = try_or_false!(string.as_str()); + + let hrp = try_or_false!(TWString::from_ptr_as_ref(hrp)); + let hrp = try_or_false!(hrp.as_str()); + + let coin = try_or_false!(CoinType::try_from(coin)); + + let prefix = AddressPrefix::Hrp(hrp.to_string()); + AnyAddress::is_valid(coin, string, Some(prefix)) +} + +/// Creates an address from a string representation and a coin type. Must be deleted with `TWAnyAddressDelete` after use. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \return `TWAnyAddress` pointer or nullptr if address and coin are invalid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_with_string( + string: *const TWString, + coin: u32, +) -> *mut TWAnyAddress { + let string = try_or_else!(TWString::from_ptr_as_ref(string), std::ptr::null_mut); + let string = try_or_else!(string.as_str(), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + AnyAddress::with_string(coin, string, None) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Creates an address from a public key and derivation option. +/// +/// \param public_key derivates the address from the public key. +/// \param coin coin type of the address. +/// \param derivation the custom derivation to use. +/// \return `TWAnyAddress` pointer or nullptr if public key is invalid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_with_public_key_derivation( + public_key: *mut TWPublicKey, + coin: u32, + derivation: u32, +) -> *mut TWAnyAddress { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let derivation = try_or_else!(Derivation::from_raw(derivation), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + AnyAddress::with_public_key(coin, public_key.as_ref().clone(), derivation, None) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Creates an bech32 address from a public key and a given hrp. +/// +/// \param public_key derivates the address from the public key. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_bech32_with_public_key( + public_key: *mut TWPublicKey, + coin: u32, + hrp: *const TWString, +) -> *mut TWAnyAddress { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + let hrp = try_or_else!(TWString::from_ptr_as_ref(hrp), std::ptr::null_mut); + let hrp = try_or_else!(hrp.as_str(), std::ptr::null_mut); + + let prefix = AddressPrefix::Hrp(hrp.to_string()); + AnyAddress::with_public_key( + coin, + public_key.as_ref().clone(), + Derivation::default(), + Some(prefix), + ) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Deletes an address. +/// +/// \param address address to delete. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_delete(address: *mut TWAnyAddress) { + // Take the ownership back to rust and drop the owner. + let _ = TWAnyAddress::from_ptr(address); +} + +/// Returns the address string representation. +/// +/// \param address address to get the string representation of. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_description(address: *const TWAnyAddress) -> *mut TWString { + // Take the ownership back to rust and drop the owner. + let address = try_or_else!(TWAnyAddress::from_ptr_as_ref(address), std::ptr::null_mut); + + let description = address.0.description().to_string(); + TWString::from(description).into_ptr() +} + +/// Returns underlying data (public key or key hash) +/// +/// \param address address to get the data of. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_data(address: *const TWAnyAddress) -> *mut TWData { + // Take the ownership back to rust and drop the owner. + let address = try_or_else!(TWAnyAddress::from_ptr_as_ref(address), std::ptr::null_mut); + + let data = try_or_else!(address.0.get_data(), std::ptr::null_mut); + TWData::from(data).into_ptr() +} + +/// Creates an address from a string representation and a coin type. Must be deleted with `TWAnyAddressDelete` after use. +/// This function does not check if the address belongs to the given chain. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \return `TWAnyAddress` pointer or nullptr if address and coin are invalid. +/// +/// # Warning +/// +/// This function should only be used when address prefix is unavailable to be passed to this function. +/// Consider using `tw_any_address_create__with_string` if the prefix is known. +/// Please note that this function should be removed when all chains are migrated to Rust. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_with_string_unchecked( + string: *const TWString, + coin: u32, +) -> *mut TWAnyAddress { + let string = try_or_else!(TWString::from_ptr_as_ref(string), std::ptr::null_mut); + let string = try_or_else!(string.as_str(), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + AnyAddress::with_string_unchecked(coin, string) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_any_coin/src/ffi/tw_any_signer.rs b/rust/tw_any_coin/src/ffi/tw_any_signer.rs new file mode 100644 index 00000000000..c0bde8e921c --- /dev/null +++ b/rust/tw_any_coin/src/ffi/tw_any_signer.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::any_signer::AnySigner; +use tw_coin_registry::coin_type::CoinType; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; + +/// Signs a transaction specified by the signing input and coin type. +/// +/// \param input The serialized data of a signing input (e.g. TW.Bitcoin.Proto.SigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of a `SigningOutput` proto object. (e.g. TW.Bitcoin.Proto.SigningOutput). +#[no_mangle] +pub unsafe extern "C" fn tw_any_signer_sign(input: *const TWData, coin: u32) -> *mut TWData { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + AnySigner::sign(input.as_slice(), coin) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Plans a transaction (for UTXO chains only). +/// +/// \param input The serialized data of a signing input +/// \param coin The given coin type to plan the transaction for. +/// \return The serialized data of a `TransactionPlan` proto object. +#[no_mangle] +pub unsafe extern "C" fn tw_any_signer_plan(input: *const TWData, coin: u32) -> *mut TWData { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + AnySigner::plan(input.as_slice(), coin) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_any_coin/src/ffi/tw_message_signer.rs b/rust/tw_any_coin/src/ffi/tw_message_signer.rs new file mode 100644 index 00000000000..e1188890bf1 --- /dev/null +++ b/rust/tw_any_coin/src/ffi/tw_message_signer.rs @@ -0,0 +1,58 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::message_signer::MessageSigner; +use tw_coin_registry::coin_type::CoinType; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; + +/// Signs a message for the given blockchain. +/// +/// \param input The serialized data of a signing input (e.g. TW.Ethereum.Proto.MessageSigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of a `SigningOutput` proto object. (e.g. TW.Ethereum.Proto.MessageSigningOutput). +#[no_mangle] +pub unsafe extern "C" fn tw_message_signer_sign(input: *const TWData, coin: u32) -> *mut TWData { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + MessageSigner::sign_message(input.as_slice(), coin) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Verifies a signature for a message. +/// +/// \param input The serialized data of a signing input (e.g. TW.Ethereum.Proto.MessageSigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of a `SigningOutput` proto object. (e.g. TW.Ethereum.Proto.MessageSigningOutput). +#[no_mangle] +pub unsafe extern "C" fn tw_message_signer_verify(input: *const TWData, coin: u32) -> bool { + let input = try_or_false!(TWData::from_ptr_as_ref(input)); + let coin = try_or_false!(CoinType::try_from(coin)); + MessageSigner::verify_message(input.as_slice(), coin).unwrap_or_default() +} + +/// Computes preimage hashes of a message. +/// +/// \param input The serialized data of a signing input (e.g. TW.Ethereum.Proto.MessageSigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of TW.TxCompiler.PreSigningOutput. +#[no_mangle] +pub unsafe extern "C" fn tw_message_signer_pre_image_hashes( + input: *const TWData, + coin: u32, +) -> *mut TWData { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + MessageSigner::message_preimage_hashes(input.as_slice(), coin) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs b/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs new file mode 100644 index 00000000000..bd227f4426f --- /dev/null +++ b/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs @@ -0,0 +1,72 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::transaction_compiler::TransactionCompiler; +use tw_coin_registry::coin_type::CoinType; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_data_vector::TWDataVector; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; + +/// Obtains pre-signing hashes of a transaction. +/// +/// We provide a default `PreSigningOutput` in TransactionCompiler.proto. +/// For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. +/// \param coin coin type. +/// \param input The serialized data of a signing input +/// \return serialized data of a proto object `PreSigningOutput` includes hash. +#[no_mangle] +pub unsafe extern "C" fn tw_transaction_compiler_pre_image_hashes( + coin: u32, + input: *const TWData, +) -> *mut TWData { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + TransactionCompiler::preimage_hashes(coin, input.as_slice()) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Compiles a complete transaction with one or more external signatures. +/// +/// Puts together from transaction input and provided public keys and signatures. The signatures must match the hashes +/// returned by `TWTransactionCompilerPreImageHashes`, in the same order. The publicKeyHash attached +/// to the hashes enable identifying the private key needed for signing the hash. +/// \param coin coin type. +/// \param input The serialized data of a signing input. +/// \param signatures signatures to compile, using `TWDataVector`. +/// \param public_keys public keys for signers to match private keys, using `TWDataVector`. +/// \return serialized data of a proto object `SigningOutput`. +#[no_mangle] +pub unsafe extern "C" fn tw_transaction_compiler_compile( + coin: u32, + input: *const TWData, + signatures: *const TWDataVector, + public_keys: *const TWDataVector, +) -> *mut TWData { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let signatures_ref = try_or_else!( + TWDataVector::from_ptr_as_ref(signatures), + std::ptr::null_mut + ); + let public_keys_ref = try_or_else!( + TWDataVector::from_ptr_as_ref(public_keys), + std::ptr::null_mut + ); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + TransactionCompiler::compile( + coin, + input.as_slice(), + signatures_ref.to_data_vec(), + public_keys_ref.to_data_vec(), + ) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_any_coin/src/lib.rs b/rust/tw_any_coin/src/lib.rs new file mode 100644 index 00000000000..ebe47631a8e --- /dev/null +++ b/rust/tw_any_coin/src/lib.rs @@ -0,0 +1,14 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod any_address; +pub mod any_signer; +pub mod ffi; +pub mod message_signer; +pub mod transaction_compiler; + +#[cfg(feature = "test-utils")] +pub mod test_utils; diff --git a/rust/tw_any_coin/src/message_signer.rs b/rust/tw_any_coin/src/message_signer.rs new file mode 100644 index 00000000000..0266df3eed0 --- /dev/null +++ b/rust/tw_any_coin/src/message_signer.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::SigningResult; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; +use tw_memory::Data; + +/// Represents a message signer to sign regular or typed structured data for any blockchain. +pub struct MessageSigner; + +impl MessageSigner { + /// Signs a message. + #[inline] + pub fn sign_message(input: &[u8], coin: CoinType) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry.sign_message(&ctx, input) + } + + /// Computes preimage hashes of a message. + #[inline] + pub fn message_preimage_hashes(input: &[u8], coin: CoinType) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry.message_preimage_hashes(&ctx, input) + } + + /// Verifies a signature for a message. + #[inline] + pub fn verify_message(input: &[u8], coin: CoinType) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry.verify_message(&ctx, input) + } +} diff --git a/rust/tw_any_coin/src/test_utils/address_utils.rs b/rust/tw_any_coin/src/test_utils/address_utils.rs new file mode 100644 index 00000000000..8efd7de120e --- /dev/null +++ b/rust/tw_any_coin/src/test_utils/address_utils.rs @@ -0,0 +1,105 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_any_address::{ + tw_any_address_create_bech32_with_public_key, tw_any_address_create_with_string, + tw_any_address_data, tw_any_address_delete, tw_any_address_description, + tw_any_address_is_valid, tw_any_address_is_valid_bech32, TWAnyAddress, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; +use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_keypair::tw::PublicKeyType; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; + +pub type TWAnyAddressHelper = TWWrapper; + +impl WithDestructor for TWAnyAddress { + fn destructor() -> unsafe extern "C" fn(*mut Self) { + tw_any_address_delete + } +} + +pub fn test_address_normalization(coin: CoinType, denormalized: &str, normalized: &str) { + let expected = normalized; + let denormalized = TWStringHelper::create(denormalized); + + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_with_string(denormalized.ptr(), coin as u32) + }); + + let normalized = TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + + assert_eq!(normalized.to_string(), Some(expected.to_string())); +} + +pub fn test_address_valid(coin: CoinType, address: &str) { + let address = TWStringHelper::create(address); + assert!(unsafe { tw_any_address_is_valid(address.ptr(), coin as u32) }); +} + +pub fn test_address_invalid(coin: CoinType, address: &str) { + let address = TWStringHelper::create(address); + assert!(!unsafe { tw_any_address_is_valid(address.ptr(), coin as u32) }); +} + +pub fn test_address_get_data(coin: CoinType, address: &str, data_hex: &str) { + let address_str = TWStringHelper::create(address); + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_with_string(address_str.ptr(), coin as u32) + }); + + let actual_data = TWDataHelper::wrap(unsafe { tw_any_address_data(any_address.ptr()) }) + .to_vec() + .unwrap_or_else(|| panic!("!tw_any_address_data")); + assert_eq!( + actual_data.to_hex(), + // Decode and encode again to allow to use `0x` and non-prefixed data hexes. + data_hex.decode_hex().unwrap().to_hex() + ); +} + +pub struct AddressCreateBech32WithPublicKey<'a> { + pub coin: CoinType, + pub private_key: &'a str, + pub public_key_type: PublicKeyType, + pub hrp: &'a str, + pub expected: &'a str, +} + +pub fn test_address_create_bech32_with_public_key(input: AddressCreateBech32WithPublicKey<'_>) { + let private_key = TWPrivateKeyHelper::with_hex(input.private_key); + let public_key = TWPublicKeyHelper::wrap(unsafe { + tw_private_key_get_public_key_by_type(private_key.ptr(), input.public_key_type as u32) + }); + let hrp = TWStringHelper::create(input.hrp); + + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_bech32_with_public_key(public_key.ptr(), input.coin as u32, hrp.ptr()) + }); + + let actual = TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + assert_eq!(actual.to_string(), Some(input.expected.to_string())); +} + +pub struct AddressBech32IsValid<'a> { + pub coin: CoinType, + pub address: &'a str, + pub hrp: &'a str, +} + +pub fn test_address_bech32_is_valid(input: AddressBech32IsValid<'_>) { + let address_str = TWStringHelper::create(input.address); + let hrp = TWStringHelper::create(input.hrp); + // Should be valid even though Osmosis chain has `osmo` default hrp. + let result = + unsafe { tw_any_address_is_valid_bech32(address_str.ptr(), input.coin as u32, hrp.ptr()) }; + assert!(result); +} diff --git a/rust/tw_any_coin/src/test_utils/mod.rs b/rust/tw_any_coin/src/test_utils/mod.rs new file mode 100644 index 00000000000..7bdd5ac4e99 --- /dev/null +++ b/rust/tw_any_coin/src/test_utils/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address_utils; diff --git a/rust/tw_any_coin/src/transaction_compiler.rs b/rust/tw_any_coin/src/transaction_compiler.rs new file mode 100644 index 00000000000..85e5013bdb9 --- /dev/null +++ b/rust/tw_any_coin/src/transaction_compiler.rs @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::{SigningError, SigningResult}; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; +use tw_memory::Data; + +/// Non-core transaction utility methods, like building a transaction using an external signature. +pub struct TransactionCompiler; + +impl TransactionCompiler { + /// Obtains pre-signing hashes of a transaction. + #[inline] + pub fn preimage_hashes(coin: CoinType, input: &[u8]) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry + .preimage_hashes(&ctx, input) + .map_err(SigningError::from) + } + + /// Compiles a complete transaction with one or more external signatures. + #[inline] + pub fn compile( + coin: CoinType, + input: &[u8], + signatures: Vec, + public_keys: Vec, + ) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry + .compile(&ctx, input, signatures, public_keys) + .map_err(SigningError::from) + } +} diff --git a/rust/tw_move_parser/src/lib.rs b/rust/tw_any_coin/tests/chain_tests.rs similarity index 95% rename from rust/tw_move_parser/src/lib.rs rename to rust/tw_any_coin/tests/chain_tests.rs index c9f1c100548..c1b24a82062 100644 --- a/rust/tw_move_parser/src/lib.rs +++ b/rust/tw_any_coin/tests/chain_tests.rs @@ -4,4 +4,4 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod ffi; +mod chains; diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_address.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_address.rs new file mode 100644 index 00000000000..788cdc5aeec --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_address.rs @@ -0,0 +1,73 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_aptos_address_normalization() { + test_address_normalization( + CoinType::Aptos, + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + ); +} + +#[test] +fn test_aptos_address_is_valid() { + test_address_valid(CoinType::Aptos, "0x1"); + test_address_valid( + CoinType::Aptos, + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); + test_address_valid( + CoinType::Aptos, + "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); + test_address_valid( + CoinType::Aptos, + "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", + ); + test_address_valid( + CoinType::Aptos, + "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", + ); + test_address_valid( + CoinType::Aptos, + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", + ); +} + +#[test] +fn test_aptos_address_invalid() { + // Empty + test_address_invalid(CoinType::Aptos, ""); + // Invalid Hex + test_address_invalid( + CoinType::Aptos, + "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); + // Too long + test_address_invalid( + CoinType::Aptos, + "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", + ); + test_address_invalid( + CoinType::Aptos, + "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); +} + +#[test] +fn test_aptos_address_get_data() { + test_address_get_data( + CoinType::Aptos, + "0x1", + "0000000000000000000000000000000000000000000000000000000000000001", + ); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs new file mode 100644 index 00000000000..114d7e9a46e --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs @@ -0,0 +1,86 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::aptos::test_cases::transfer_b4d62afd::{ + aptos_sign_transfer_input, expected_json, DATA_TO_SIGN, ENCODED, PRIVATE_KEY, RAW_TXN, + SIGNATURE, +}; +use crate::chains::aptos::APTOS_COIN_TYPE; +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::ToHex; +use tw_keypair::ed25519; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_misc::assert_eq_json; +use tw_misc::traits::ToBytesVec; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_compile_aptos() { + let input = aptos_sign_transfer_input(); + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(APTOS_COIN_TYPE, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!(preimage.data.to_hex(), DATA_TO_SIGN); + + // Step 3: Sign the data "externally" + + let private_key = ed25519::sha512::KeyPair::try_from(PRIVATE_KEY).unwrap(); + let public_key = private_key.public().to_vec(); + + let signature = private_key + .sign(preimage.data.to_vec()) + .expect("Error signing data") + .to_vec(); + assert_eq!(signature.to_hex(), SIGNATURE); + + // Step 4: Compile transaction info + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + APTOS_COIN_TYPE, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let authenticator = output.authenticator.unwrap(); + assert_eq!(authenticator.signature.to_hex(), SIGNATURE); + assert_eq!(output.raw_txn.to_hex(), RAW_TXN); + assert_eq!(output.encoded.to_hex(), ENCODED); + + assert_eq_json!(output.json, expected_json()); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_sign.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_sign.rs new file mode 100644 index 00000000000..6021ce8bb92 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_sign.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::aptos::test_cases::transfer_b4d62afd::{ + aptos_sign_transfer_input, expected_json, ENCODED, PRIVATE_KEY, RAW_TXN, SIGNATURE, +}; +use crate::chains::aptos::APTOS_COIN_TYPE; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_misc::assert_eq_json; +use tw_proto::Aptos::Proto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_aptos() { + let input = Proto::SigningInput { + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + ..aptos_sign_transfer_input() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), APTOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let authenticator = output.authenticator.unwrap(); + assert_eq!(authenticator.signature.to_hex(), SIGNATURE); + assert_eq!(output.raw_txn.to_hex(), RAW_TXN); + assert_eq!(output.encoded.to_hex(), ENCODED); + + assert_eq_json!(output.json, expected_json()); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/mod.rs b/rust/tw_any_coin/tests/chains/aptos/mod.rs new file mode 100644 index 00000000000..ecea2c75bfd --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/mod.rs @@ -0,0 +1,12 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod aptos_address; +mod aptos_compile; +mod aptos_sign; +mod test_cases; + +const APTOS_COIN_TYPE: u32 = 637; diff --git a/rust/tw_any_coin/tests/chains/aptos/test_cases.rs b/rust/tw_any_coin/tests/chains/aptos/test_cases.rs new file mode 100644 index 00000000000..81b166f7247 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/test_cases.rs @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::{json, Value as Json}; +use tw_proto::Aptos::Proto; +use tw_proto::Aptos::Proto::mod_SigningInput::OneOftransaction_payload as TransactionPayloadEnum; + +pub(super) mod transfer_b4d62afd { + use super::*; + + /// Expected private key. + pub const PRIVATE_KEY: &str = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"; + pub const ENCODED: &str = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"; + /// Expected `raw_txn`. + pub const RAW_TXN: &str = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"; + /// Expected signature. + pub const SIGNATURE: &str = "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"; + /// Expected preimage data to be signed. + pub const DATA_TO_SIGN: &str = "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"; + + pub fn aptos_sign_transfer_input() -> Proto::SigningInput<'static> { + let transfer = Proto::TransferMessage { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".into(), + amount: 1000, + }; + + Proto::SigningInput { + sender: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".into(), + sequence_number: 99, + max_gas_amount: 3296766, + gas_unit_price: 100, + expiration_timestamp_secs: 3664390082, + chain_id: 33, + any_encoded: Default::default(), + transaction_payload: TransactionPayloadEnum::transfer(transfer), + ..Proto::SigningInput::default() + } + } + + pub fn expected_json() -> Json { + json!({ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + }) + } +} diff --git a/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs b/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs new file mode 100644 index 00000000000..0397c6c7768 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs @@ -0,0 +1,49 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_bitcoin_address_normalization() { + test_address_normalization( + CoinType::Bitcoin, + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + ); +} + +#[test] +fn test_bitcoin_address_is_valid() { + test_address_valid(CoinType::Bitcoin, "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx"); + test_address_valid( + CoinType::Bitcoin, + "bc1qunq74p3h8425hr6wllevlvqqr6sezfxj262rff", + ); + test_address_valid( + CoinType::Bitcoin, + "bc1pwse34zfpvt344rvlt7tw0ngjtfh9xasc4q03avf0lk74jzjpzjuqaz7ks5", + ); +} + +#[test] +fn test_bitcoin_address_invalid() { + test_address_invalid( + CoinType::Bitcoin, + "0xb16db98b365b1f89191996942612b14f1da4bd5f", + ); +} + +#[test] +fn test_bitcoin_address_get_data() { + test_address_get_data( + CoinType::Bitcoin, + "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx", + "314d725a4e474e376d66575a695a4e517474727a486a667737326a6e4a43324a4e78", + ); +} diff --git a/rust/tw_any_coin/tests/chains/bitcoin/mod.rs b/rust/tw_any_coin/tests/chains/bitcoin/mod.rs new file mode 100644 index 00000000000..00682e69022 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/bitcoin/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod bitcoin_address; diff --git a/rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs b/rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs new file mode 100644 index 00000000000..964414dff44 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs @@ -0,0 +1,83 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_cosmos_address_normalization() { + test_address_normalization( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + ); +} + +#[test] +fn test_cosmos_address_is_valid() { + test_address_valid( + CoinType::Cosmos, + "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + ); + test_address_valid( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + ); + test_address_valid( + CoinType::Cosmos, + "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + ); +} + +#[test] +fn test_cosmos_address_invalid() { + test_address_invalid( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax6", + ); + test_address_invalid( + CoinType::Cosmos, + "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + ); + test_address_invalid( + CoinType::Cosmos, + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", + ); +} + +#[test] +fn test_cosmos_address_get_data() { + test_address_get_data( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "818c5dc04ccbd1dcb454fb7c06da564b0e22955d", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf", + hrp: "juno", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::Cosmos, + private_key: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + public_key_type: PublicKeyType::Secp256k1, + hrp: "juno", + expected: "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86", + }); +} diff --git a/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs b/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs new file mode 100644 index 00000000000..b9140fb9731 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs @@ -0,0 +1,65 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::DecodeHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_cosmos() { + use tw_proto::Cosmos::Proto; + use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + let private_key = "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af" + .decode_hex() + .unwrap(); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), + amounts: vec![Proto::Amount { + denom: "uatom".into(), + amount: "400000".into(), + }], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(Proto::Fee { + gas: 200000, + amounts: vec![Proto::Amount { + denom: "uatom".into(), + amount: "1000".into(), + }], + }), + private_key: private_key.into(), + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_msg), + }], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::Cosmos as u32) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#; + assert_eq!(output.serialized, expected); +} diff --git a/rust/tw_any_coin/tests/chains/cosmos/mod.rs b/rust/tw_any_coin/tests/chains/cosmos/mod.rs new file mode 100644 index 00000000000..47b109ad2c9 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod cosmos_address; +mod cosmos_sign; diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs new file mode 100644 index 00000000000..f0a03ec2cde --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::blockchain_type::BlockchainType; +use tw_coin_registry::registry::coin_items_by_blockchain; + +#[test] +fn test_ethereum_address_normalization() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_normalization( + coin.coin_id, + "0xb16db98b365b1f89191996942612b14f1da4bd5f", + "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); + } +} + +#[test] +fn test_ethereum_address_is_valid() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_valid(coin.coin_id, "0xb16db98b365b1f89191996942612b14f1da4bd5f"); + test_address_valid(coin.coin_id, "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f"); + } +} + +#[test] +fn test_ethereum_address_invalid() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_invalid(coin.coin_id, "b16Db98B365B1f89191996942612B14F1Da4Bd5f"); + } +} + +#[test] +fn test_ethereum_address_get_data() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_get_data( + coin.coin_id, + "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", + "b16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); + } +} diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_compile.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_compile.rs new file mode 100644 index 00000000000..f31a380d456 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_compile.rs @@ -0,0 +1,110 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_plan; +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_transaction_compiler_eth() { + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + let input = Proto::SigningInput { + nonce: U256::encode_be_compact(11), + chain_id: U256::encode_be_compact(1), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + preimage.data_hash.to_hex(), + "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature = "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900".decode_hex().unwrap(); + let public_key = "044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a".decode_hex().unwrap(); + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + CoinType::Ethereum as u32, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_transaction_compiler_plan_not_supported() { + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1), + data: Cow::default(), + }; + let input = Proto::SigningInput { + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + ..Proto::SigningInput::default() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let plan = TWDataHelper::wrap(unsafe { + tw_any_signer_plan(input_data.ptr(), CoinType::Ethereum as u32) + }); + assert!( + plan.is_null(), + "Transaction plan is expected to be not supported by the {:?} coin", + CoinType::Ethereum + ); +} diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_message_sign.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_message_sign.rs new file mode 100644 index 00000000000..1b348169f49 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_message_sign.rs @@ -0,0 +1,93 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_message_signer::{ + tw_message_signer_pre_image_hashes, tw_message_signer_sign, tw_message_signer_verify, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::{deserialize, serialize, Ethereum, TxCompiler}; + +#[test] +fn test_tw_message_signer_sign() { + let input = Ethereum::Proto::MessageSigningInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d" + .decode_hex() + .unwrap() + .into(), + message: "Foo".into(), + chain_id: None, + message_type: Ethereum::Proto::MessageType::MessageType_legacy, + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output = TWDataHelper::wrap(unsafe { + tw_message_signer_sign(input_data.ptr(), CoinType::Ethereum as u32) + }) + .to_vec() + .expect("!tw_message_signer_sign returned nullptr"); + + let output: Ethereum::Proto::MessageSigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b"); +} + +#[test] +fn test_tw_message_signer_verify() { + let input = Ethereum::Proto::MessageVerifyingInput { + message: "Foo".into(), + public_key: "0349d0134ef2c798c02879379a1760baa49c4e25e2324cd128f11e559f073bcc6f".decode_hex().unwrap().into(), + signature: "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b".into(), + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let verified = unsafe { tw_message_signer_verify(input_data.ptr(), CoinType::Ethereum as u32) }; + assert!(verified); +} + +#[test] +fn test_tw_message_signer_verify_invalid() { + let input = Ethereum::Proto::MessageVerifyingInput { + message: "Foo".into(), + public_key: "0349d0134ef2c798c02879379a1760baa49c4e25e2324cd128f11e559f073bcc6f".decode_hex().unwrap().into(), + signature: "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101c".into(), + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let verified = unsafe { tw_message_signer_verify(input_data.ptr(), CoinType::Ethereum as u32) }; + assert!(!verified); +} + +#[test] +fn test_tw_message_signer_pre_image_hashes() { + let input = Ethereum::Proto::MessageSigningInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d" + .decode_hex() + .unwrap() + .into(), + message: "Foo".into(), + chain_id: None, + message_type: Ethereum::Proto::MessageType::MessageType_legacy, + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output = TWDataHelper::wrap(unsafe { + tw_message_signer_pre_image_hashes(input_data.ptr(), CoinType::Ethereum as u32) + }) + .to_vec() + .expect("!tw_message_signer_sign returned nullptr"); + + let output: TxCompiler::Proto::PreSigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!( + output.data_hash.to_hex(), + "0af844076e792f9685560b2e597967da7403b00a5339b5801ea251ddde375f8a" + ); +} diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs new file mode 100644 index 00000000000..47a78da82e7 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs @@ -0,0 +1,56 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_number::U256; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_eth() { + use tw_proto::Ethereum::Proto; + + let private = "0x4646464646464646464646464646464646464646464646464646464646464646" + .decode_hex() + .unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(9), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::Ethereum as u32) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; + assert_eq!(output.encoded.to_hex(), expected); +} diff --git a/rust/tw_any_coin/tests/chains/ethereum/mod.rs b/rust/tw_any_coin/tests/chains/ethereum/mod.rs new file mode 100644 index 00000000000..153b12547b5 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod ethereum_address; +mod ethereum_compile; +mod ethereum_message_sign; +mod ethereum_sign; diff --git a/rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs b/rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs new file mode 100644 index 00000000000..ef44d0e022f --- /dev/null +++ b/rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_thorchain_address_normalization() { + test_address_normalization( + CoinType::InternetComputer, + "290CC7C359F44C8516FC169C5ED4F0F3AE2E24BF5DE0D4C51F5E7545B5474FAA", + "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + ); +} + +#[test] +fn test_thorchain_address_is_valid() { + test_address_valid( + CoinType::InternetComputer, + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + ); + test_address_valid( + CoinType::InternetComputer, + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + ); + test_address_valid( + CoinType::InternetComputer, + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + ); + test_address_valid( + CoinType::InternetComputer, + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + ); +} + +#[test] +fn test_thorchain_address_invalid() { + test_address_invalid( + CoinType::InternetComputer, + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b", + ); + test_address_invalid( + CoinType::InternetComputer, + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce", + ); + test_address_invalid( + CoinType::InternetComputer, + "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", + ); +} + +#[test] +fn test_thorchain_address_get_data() { + test_address_get_data( + CoinType::InternetComputer, + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + ); +} diff --git a/rust/tw_any_coin/tests/chains/internet_computer/mod.rs b/rust/tw_any_coin/tests/chains/internet_computer/mod.rs new file mode 100644 index 00000000000..e305a45c6be --- /dev/null +++ b/rust/tw_any_coin/tests/chains/internet_computer/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod internet_computer_address; diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs new file mode 100644 index 00000000000..ddd04b53632 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -0,0 +1,14 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod aptos; +mod bitcoin; +mod cosmos; +mod ethereum; +mod internet_computer; +mod native_evmos; +mod native_injective; +mod thorchain; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/mod.rs b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs new file mode 100644 index 00000000000..2a92924fc00 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod native_evmos_address; +mod native_evmos_sign; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs new file mode 100644 index 00000000000..d779aabc230 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_native_evmos_address_normalization() { + test_address_normalization( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ); +} + +#[test] +fn test_native_evmos_address_is_valid() { + test_address_valid( + CoinType::NativeEvmos, + "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + ); + test_address_valid( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ); +} + +#[test] +fn test_native_evmos_address_invalid() { + test_address_invalid( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw", + ); +} + +#[test] +fn test_native_evmos_address_get_data() { + test_address_get_data( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + "f1829676db577682e944fc3493d451b67ff3e29f", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + hrp: "evmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::NativeEvmos, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1Extended, + hrp: "evmos", + expected: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + }); +} diff --git a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs new file mode 100644 index 00000000000..3615cfa3548 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs @@ -0,0 +1,122 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::DecodeHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::{deserialize, serialize}; + +const NATIVE_EVMOS_COIN_TYPE: u32 = 20009001; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_2139877_private_key() -> Cow<'static, [u8]> { + "79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_sign_native_evmos_tx_json() { + let send_msg = Proto::mod_Message::Send { + from_address: "evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z".into(), + to_address: "evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye".into(), + amounts: vec![make_amount("muon", "1")], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + account_number: 1037, + chain_id: "evmos_9001-2".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + output.json, + r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z","to_address":"evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye"}}],"signatures":[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]}}"# + ); + assert_eq!( + output.signature_json, + r#"[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]"# + ); +} + +/// CompoundingAuthz +#[test] +fn test_sign_native_evmos_tx_protobuf() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["evmosvaloper1umk407eed7af6anvut6llg2zevnf0dn0feqqny".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::DELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "evmos12m9grgas60yk0kult076vxnsrqz8xpjy9rpf3e".into(), + grantee: "evmos18fzq4nac28gfma6gqfvkpwrgpm5ctar2z9mxf3".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1692309600, + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 2139877, + chain_id: "evmos_9001-2".into(), + sequence: 3, + fee: Some(make_fee(180859, make_amount("aevmos", "4521475000000000"))), + private_key: account_2139877_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // Original test: https://github.com/trustwallet/wallet-core/blob/a60033f797e33628e557af7c66be539c8d78bc61/tests/chains/Evmos/SignerTests.cpp#L91-L124 + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM + assert_eq!( + output.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAESNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkBXaTo3nk5EMFW9Euheez5ADx2bWo7XisNJ5vuGj1fKXh6CGNJGfJj/q1XUkBzaCvPNg+EcFHgtJdVSyF4cJZTg"}"# + ); +} diff --git a/rust/tw_any_coin/tests/chains/native_injective/mod.rs b/rust/tw_any_coin/tests/chains/native_injective/mod.rs new file mode 100644 index 00000000000..a421b328188 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/mod.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod native_injective_address; +mod native_injective_compile; +mod native_injective_sign; diff --git a/rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs b/rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs new file mode 100644 index 00000000000..1bd9c4a8f8e --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_native_injective_address_normalization() { + test_address_normalization( + CoinType::NativeInjective, + "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + ); +} + +#[test] +fn test_native_injective_address_is_valid() { + test_address_valid( + CoinType::NativeInjective, + "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", + ); + test_address_valid( + CoinType::NativeInjective, + "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd", + ); +} + +#[test] +fn test_native_injective_address_invalid() { + test_address_invalid( + CoinType::NativeInjective, + "ini13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", + ); +} + +#[test] +fn test_native_injective_address_get_data() { + test_address_get_data( + CoinType::NativeInjective, + "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd", + "36c36d9875ec1efced515e14246f8422903ade2e", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + hrp: "evmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::NativeInjective, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1Extended, + hrp: "inj", + expected: "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + }); +} diff --git a/rust/tw_any_coin/tests/chains/native_injective/native_injective_compile.rs b/rust/tw_any_coin/tests/chains/native_injective/native_injective_compile.rs new file mode 100644 index 00000000000..fbffdddfb91 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/native_injective_compile.rs @@ -0,0 +1,96 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +const NATIVE_INJECTIVE_COIN_TYPE: u32 = 10000060; +const ACCOUNT_88701_PUBLIC_KEY: &str = "04088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f728"; + +fn send_tx_input() -> Proto::SigningInput<'static> { + let send_msg = Proto::mod_Message::Send { + from_address: "inj1d0jkrsd09c7pule43y3ylrul43lwwcqaky8w8c".into(), + to_address: "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd".into(), + amounts: vec![make_amount("inj", "10000000000")], + ..Proto::mod_Message::Send::default() + }; + Proto::SigningInput { + account_number: 88701, + chain_id: "injective-1".into(), + sequence: 0, + fee: Some(make_fee(110000, make_amount("inj", "100000000000000"))), + public_key: ACCOUNT_88701_PUBLIC_KEY.decode_hex().unwrap().into(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + } +} + +#[test] +fn test_compile_native_injective_tx_protobuf() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + ..send_tx_input() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + // Step 1: Obtain preimage hash + + let preimage_output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(NATIVE_INJECTIVE_COIN_TYPE, input_data.ptr()) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let preimage_output: CompilerProto::PreSigningOutput = + deserialize(&preimage_output_data).unwrap(); + assert_eq!(preimage_output.error, SigningErrorType::OK); + assert!(preimage_output.error_message.is_empty()); + + let expected_preimage = "0a8f010a8c010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126c0a2a696e6a3164306a6b7273643039633770756c6534337933796c72756c34336c77776371616b7938773863122a696e6a31786d706b6d78723461733030656d32337463327a676d7579793267723468337767636c3676641a120a03696e6a120b3130303030303030303030129c010a7c0a740a2d2f696e6a6563746976652e63727970746f2e763162657461312e657468736563703235366b312e5075624b657912430a4104088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f72812040a020801121c0a160a03696e6a120f31303030303030303030303030303010b0db061a0b696e6a6563746976652d3120fdb405"; + assert_eq!(preimage_output.data.to_hex(), expected_preimage); + let expected_prehash = "57dc10c3d1893ff16e1f5a47fa4b2e96f37b9c57d98a42d88c971debb4947ec9"; + assert_eq!(preimage_output.data_hash.to_hex(), expected_prehash); + + // Step 2: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature = "f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421".decode_hex().unwrap(); + let public_key = ACCOUNT_88701_PUBLIC_KEY.decode_hex().unwrap(); + + let signatures = TWDataVectorHelper::create([signature.clone()]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + NATIVE_INJECTIVE_COIN_TYPE, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajFkMGprcnNkMDljN3B1bGU0M3kzeWxydWw0M2x3d2NxYWt5OHc4YxIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASnAEKfAp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBAiKwpGZh9knNoyyvireRM0O02FnRalpnK4mSz/Fp8NgfZn0Qbg0CZDumQyz6vVg8fC6/mAMfpSkvoOSFmmE9ygSBAoCCAESHAoWCgNpbmoSDzEwMDAwMDAwMDAwMDAwMBCw2wYaQPep7ApSEXC7VWbKlz08c6G2mxYtmc4CIFkYmZHsRAY3MzOU/xyedfrYTrEUOTlp8gmJsDbx3+0olJ6QbcAHdCE="}"#; + assert_eq!(output.serialized, expected_encoded); + assert_eq!(output.signature.to_hex(), signature.to_hex()); +} diff --git a/rust/tw_any_coin/tests/chains/native_injective/native_injective_sign.rs b/rust/tw_any_coin/tests/chains/native_injective/native_injective_sign.rs new file mode 100644 index 00000000000..f5d7e448a27 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/native_injective_sign.rs @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::{deserialize, serialize}; + +const NATIVE_INJECTIVE_COIN_TYPE: u32 = 10000060; + +fn account_17396_private_key() -> Cow<'static, [u8]> { + "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1" + .decode_hex() + .unwrap() + .into() +} + +fn send_tx_input() -> Proto::SigningInput<'static> { + let send_msg = Proto::mod_Message::Send { + from_address: "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a".into(), + to_address: "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd".into(), + amounts: vec![make_amount("inj", "10000000000")], + ..Proto::mod_Message::Send::default() + }; + Proto::SigningInput { + account_number: 17396, + chain_id: "injective-1".into(), + sequence: 1, + fee: Some(make_fee(110000, make_amount("inj", "100000000000000"))), + private_key: account_17396_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + } +} + +#[test] +fn test_sign_native_injective_tx_protobuf() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + ..send_tx_input() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), NATIVE_INJECTIVE_COIN_TYPE) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // https://www.mintscan.io/injective/txs/135DD2C4A1910E4334A9C0F15125DA992E724EBF23FEB9638FCB71218BB064A5 + assert_eq!( + output.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw=="}"# + ); + assert_eq!(output.signature.to_hex(), "c76be4a659b378aee7de9b821463d684b77497f642fc26241a5facb47fb74b78620af21eeeec3032952535a4b0bd3f07c05d64354a7e4b1da6d14a35c79c5c17"); +} + +#[test] +fn test_sign_native_injective_tx_json() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + ..send_tx_input() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), NATIVE_INJECTIVE_COIN_TYPE) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // This transaction hasn't been broadcasted. + assert_eq!( + output.json, + r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"100000000000000","denom":"inj"}],"gas":"110000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"10000000000","denom":"inj"}],"from_address":"inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a","to_address":"inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd"}}],"signatures":[{"pub_key":{"type":"injective/PubKeyEthSecp256k1","value":"BFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXM="},"signature":"7wwedceebL95DwbClz5AzEp2Z74itHC7raiV976DcacfjrJ58oDfjbAO5UOZQAlihiYBP7PpyISFQ72FPRhdZA=="}]}}"# + ); + assert_eq!( + output.signature_json, + r#"[{"pub_key":{"type":"injective/PubKeyEthSecp256k1","value":"BFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXM="},"signature":"7wwedceebL95DwbClz5AzEp2Z74itHC7raiV976DcacfjrJ58oDfjbAO5UOZQAlihiYBP7PpyISFQ72FPRhdZA=="}]"# + ); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/mod.rs b/rust/tw_any_coin/tests/chains/thorchain/mod.rs new file mode 100644 index 00000000000..20d258d5105 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod test_cases; +mod thorchain_address; +mod thorchain_compile; +mod thorchain_sign; diff --git a/rust/tw_any_coin/tests/chains/thorchain/test_cases.rs b/rust/tw_any_coin/tests/chains/thorchain/test_cases.rs new file mode 100644 index 00000000000..e626e3466c7 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/test_cases.rs @@ -0,0 +1,41 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +/// https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF +pub(super) mod send_fd0445af { + use super::*; + + pub const PRIVATE_KEY: &str = + "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"; + pub const JSON_TX_PREIMAGE: &str = r#"{"account_number":"593","chain_id":"thorchain","fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msgs":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"10000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"sequence":"3"}"#; + /// Expected `json` value. + pub const JSON_TX: &str = r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"10000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"qgpMX3WNq4DsNBnYtdmBD4ejiailK4uI/m3/YVqCSNF8AtkUOTmP48ztqCbpkWTFvw1/9S8/ivsFxOcK6AI0jA=="}]}}"#; + /// Expected `signature` for JSON signing mode. + pub const JSON_SIGNING_SIGNATURE: &str = "aa0a4c5f758dab80ec3419d8b5d9810f87a389a8a52b8b88fe6dff615a8248d17c02d91439398fe3cceda826e99164c5bf0d7ff52f3f8afb05c4e70ae802348c"; + /// Expected `signature_json` for JSON signing mode. + pub const JSON_SIGNING_SIGNATURE_JSON: &str = r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"qgpMX3WNq4DsNBnYtdmBD4ejiailK4uI/m3/YVqCSNF8AtkUOTmP48ztqCbpkWTFvw1/9S8/ivsFxOcK6AI0jA=="}]"#; + + pub fn signing_input() -> Proto::SigningInput<'static> { + let send_msg = Proto::mod_Message::Send { + from_address: "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r".into(), + to_address: "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn".into(), + amounts: vec![make_amount("rune", "10000000")], + ..Proto::mod_Message::Send::default() + }; + Proto::SigningInput { + account_number: 593, + chain_id: "thorchain".into(), + sequence: 3, + fee: Some(make_fee(200000, make_amount("rune", "2000000"))), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + } + } +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs new file mode 100644 index 00000000000..03ea3d0c188 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs @@ -0,0 +1,75 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_thorchain_address_normalization() { + test_address_normalization( + CoinType::THORChain, + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + ); +} + +#[test] +fn test_thorchain_address_is_valid() { + test_address_valid( + CoinType::THORChain, + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + ); + test_address_valid( + CoinType::THORChain, + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + ); +} + +#[test] +fn test_thorchain_address_invalid() { + test_address_invalid( + CoinType::THORChain, + "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + ); + test_address_invalid( + CoinType::THORChain, + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0e", + ); +} + +#[test] +fn test_thorchain_address_get_data() { + test_address_get_data( + CoinType::THORChain, + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + "c1e4df75a50e1d5b45d6e03f6157bc6169bfb46a", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + hrp: "thor", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::Cosmos, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1, + hrp: "thor", + expected: "thor1p05ufmhfpkjzqmc2u8humvgcqatq0esjqzrcut", + }); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs new file mode 100644 index 00000000000..ca2fda25eab --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::thorchain::test_cases::send_fd0445af::{ + signing_input, JSON_SIGNING_SIGNATURE, JSON_SIGNING_SIGNATURE_JSON, JSON_TX, JSON_TX_PREIMAGE, + PRIVATE_KEY, +}; +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::ToHex; +use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_misc::traits::ToBytesVec; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_compile_thorchain() { + let private_key = secp256k1::KeyPair::try_from(PRIVATE_KEY).unwrap(); + let public_key = private_key.public().to_vec(); + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + public_key: public_key.clone().into(), + ..signing_input() + }; + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(CoinType::THORChain as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + let tx_preimage = String::from_utf8(preimage.data.to_vec()) + .expect("Invalid transaction preimage. Expected a JSON object"); + assert_eq!(tx_preimage, JSON_TX_PREIMAGE); + + // Step 3: Sign the data "externally" + + let tx_hash = H256::try_from(preimage.data_hash.as_ref()).expect("Invalid Transaction Hash"); + + let signature = private_key + .sign(tx_hash) + .expect("Error signing data") + .to_vec(); + assert!( + signature.to_hex().contains(JSON_SIGNING_SIGNATURE), + "{} must contain {}", + signature.to_hex(), + JSON_SIGNING_SIGNATURE + ); + + // Step 4: Compile transaction info + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + CoinType::THORChain as u32, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF + assert_eq!(output.json, JSON_TX); + assert_eq!(output.signature.to_hex(), JSON_SIGNING_SIGNATURE); + assert_eq!(output.signature_json, JSON_SIGNING_SIGNATURE_JSON); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs new file mode 100644 index 00000000000..c45c196b026 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs @@ -0,0 +1,42 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::thorchain::test_cases::send_fd0445af::{ + signing_input, JSON_SIGNING_SIGNATURE, JSON_SIGNING_SIGNATURE_JSON, JSON_TX, PRIVATE_KEY, +}; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_thorchain() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + ..signing_input() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::THORChain as u32) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF + assert_eq!(output.json, JSON_TX); + assert_eq!(output.signature.to_hex(), JSON_SIGNING_SIGNATURE); + assert_eq!(output.signature_json, JSON_SIGNING_SIGNATURE_JSON); +} diff --git a/rust/tw_any_coin/tests/coin_address_derivation_test.rs b/rust/tw_any_coin/tests/coin_address_derivation_test.rs new file mode 100644 index 00000000000..ef3eabd910b --- /dev/null +++ b/rust/tw_any_coin/tests/coin_address_derivation_test.rs @@ -0,0 +1,168 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_any_address::{ + tw_any_address_create_with_public_key_derivation, tw_any_address_description, +}; +use tw_any_coin::test_utils::address_utils::TWAnyAddressHelper; +use tw_coin_entry::derivation::Derivation; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::registry::get_coin_item; +use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; +use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; + +#[test] +fn test_coin_address_derivation() { + let private_key = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + + for coin in CoinType::iter() { + let coin_item = get_coin_item(coin).unwrap(); + + // Skip unsupported blockchains. + if !coin_item.blockchain.is_supported() { + continue; + } + + let public_key = TWPublicKeyHelper::wrap(unsafe { + tw_private_key_get_public_key_by_type( + private_key.ptr(), + coin_item.public_key_type as u32, + ) + }); + + let expected_address = match coin { + CoinType::Ethereum + | CoinType::AcalaEVM + | CoinType::Arbitrum + | CoinType::ArbitrumNova + | CoinType::Aurora + | CoinType::AvalancheCChain + | CoinType::Boba + | CoinType::Callisto + | CoinType::Celo + | CoinType::ConfluxeSpace + | CoinType::CronosChain + | CoinType::ECOChain + | CoinType::EthereumClassic + | CoinType::Evmos + | CoinType::Fantom + | CoinType::GoChain + | CoinType::KavaEvm + | CoinType::Klaytn + | CoinType::KuCoinCommunityChain + | CoinType::Meter + | CoinType::Metis + | CoinType::Moonbeam + | CoinType::Moonriver + | CoinType::Optimism + | CoinType::Zksync + | CoinType::PolygonzkEVM + | CoinType::OKXChain + | CoinType::POANetwork + | CoinType::Polygon + | CoinType::SmartBitcoinCash + | CoinType::SmartChain + | CoinType::SmartChainLegacy + | CoinType::Theta + | CoinType::ThetaFuel + | CoinType::ThunderCore + | CoinType::Viction + | CoinType::VeChain + | CoinType::Wanchain + | CoinType::xDai + | CoinType::IoTeXEVM + | CoinType::Scroll + | CoinType::OpBNB + | CoinType::Neon + | CoinType::Base + | CoinType::Linea + | CoinType::Greenfield + | CoinType::Mantle + | CoinType::ZenEON + // end_of_evm_address_derivation_tests_marker_do_not_modify + => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", + CoinType::Bitcoin + // TODO all Bitcoin-based blockchains should have different addresses. + // It should be fixed when Bitcoin is finalized. + | CoinType::Litecoin + | CoinType::Dogecoin + | CoinType::Dash + | CoinType::Viacoin + | CoinType::DigiByte + | CoinType::Monacoin + | CoinType::Syscoin + | CoinType::Pivx + | CoinType::Firo + | CoinType::BitcoinCash + | CoinType::BitcoinGold + | CoinType::Ravencoin + | CoinType::Qtum + | CoinType::eCash + | CoinType::Stratis + => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + CoinType::Aptos => "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4", + CoinType::Cosmos => "cosmos1ten42eesehw0ktddcp0fws7d3ycsqez3lynlqx", + CoinType::Stargaze => "stars1ten42eesehw0ktddcp0fws7d3ycsqez3tcyzth", + CoinType::Juno => "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86", + CoinType::Stride => "stride1ten42eesehw0ktddcp0fws7d3ycsqez3u0nr52", + CoinType::Axelar => "axelar1ten42eesehw0ktddcp0fws7d3ycsqez3m29ht8", + CoinType::Crescent => "cre1ten42eesehw0ktddcp0fws7d3ycsqez3mvq64t", + CoinType::Kujira => "kujira1ten42eesehw0ktddcp0fws7d3ycsqez3wv38dv", + CoinType::Comdex => "comdex1ten42eesehw0ktddcp0fws7d3ycsqez3ct3ae3", + CoinType::Neutron => "neutron1ten42eesehw0ktddcp0fws7d3ycsqez3mm6a6p", + CoinType::Sommelier => "somm1ten42eesehw0ktddcp0fws7d3ycsqez3ncun3v", + CoinType::FetchAI => "fetch1ten42eesehw0ktddcp0fws7d3ycsqez3ve6mz3", + CoinType::Mars => "mars1ten42eesehw0ktddcp0fws7d3ycsqez3ze2x4a", + CoinType::Umee => "umee1ten42eesehw0ktddcp0fws7d3ycsqez3djwqy5", + CoinType::Noble => "noble1ten42eesehw0ktddcp0fws7d3ycsqez3h8xhcg", + CoinType::Sei => "sei1ten42eesehw0ktddcp0fws7d3ycsqez3jgzfx8", + CoinType::Tia => "celestia1ten42eesehw0ktddcp0fws7d3ycsqez3wwz06t", + CoinType::Coreum => "core1ten42eesehw0ktddcp0fws7d3ycsqez3v2ty8a", + CoinType::Quasar => "quasar1ten42eesehw0ktddcp0fws7d3ycsqez338fzdr", + CoinType::Persistence => "persistence1ten42eesehw0ktddcp0fws7d3ycsqez33g4vwz", + CoinType::Akash => "akash1ten42eesehw0ktddcp0fws7d3ycsqez3jl7ceu", + CoinType::Terra => "terra1ten42eesehw0ktddcp0fws7d3ycsqez3eqflzx", + CoinType::TerraV2 => "terra1ten42eesehw0ktddcp0fws7d3ycsqez3eqflzx", + CoinType::Kava => "kava1ten42eesehw0ktddcp0fws7d3ycsqez3r38zkp", + CoinType::Bluzelle => "bluzelle1ten42eesehw0ktddcp0fws7d3ycsqez32usaxh", + CoinType::BandChain => "band1ten42eesehw0ktddcp0fws7d3ycsqez3xtnacw", + CoinType::Rootstock => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", + CoinType::THORChain => "thor1ten42eesehw0ktddcp0fws7d3ycsqez3er2y4e", + CoinType::CryptoOrg => "cro1ten42eesehw0ktddcp0fws7d3ycsqez38lmxuh", + CoinType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", + CoinType::Secret => "secret1ten42eesehw0ktddcp0fws7d3ycsqez3ap8ka6", + CoinType::Osmosis => "osmo1ten42eesehw0ktddcp0fws7d3ycsqez3hlq0k5", + CoinType::NativeEvmos => "evmos14s0vgnj0pjnazu4hsqlksdk7slah9vcfvt8ssm", + CoinType::Agoric => "agoric1ten42eesehw0ktddcp0fws7d3ycsqez3de3qss", + CoinType::NativeInjective => "inj14s0vgnj0pjnazu4hsqlksdk7slah9vcfyrp6ct", + CoinType::NativeCanto => "canto14s0vgnj0pjnazu4hsqlksdk7slah9vcfuuhw7m", + CoinType::InternetComputer => "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + // end_of_coin_address_derivation_tests_marker_do_not_modify + _ => panic!("{:?} must be covered", coin), + }; + + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_with_public_key_derivation( + public_key.ptr(), + coin as u32, + Derivation::Default as u32, + ) + }); + + let description = + TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + assert_eq!( + description.to_string(), + Some(expected_address.to_string()), + "Invalid {:?} address", + coin + ); + } +} diff --git a/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs new file mode 100644 index 00000000000..74cd4e04d4b --- /dev/null +++ b/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; + +#[test] +fn test_any_signer_sign_unknown_coin() { + let unsupported_coin = u32::MAX; + + let input_data = TWDataHelper::create(vec![]); + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), unsupported_coin) }); + assert!(output.is_null()); +} diff --git a/rust/tw_aptos/Cargo.toml b/rust/tw_aptos/Cargo.toml new file mode 100644 index 00000000000..49e4d120138 --- /dev/null +++ b/rust/tw_aptos/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_aptos" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde_json = "1.0.108" +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_keypair = { path = "../tw_keypair" } +tw_proto = { path = "../tw_proto" } +tw_number = { path = "../tw_number" } +tw_hash = { path = "../tw_hash" } +tw_memory = { path = "../tw_memory" } +move-core-types = { git = "https://github.com/move-language/move", rev = "ea70797099baea64f05194a918cebd69ed02b285", features = ["address32"] } +serde = { version = "1.0.189", features = ["derive"] } +serde_bytes = "0.11.12" + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../tw_encoding" } +tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/rust/tw_aptos/fuzz/.gitignore b/rust/tw_aptos/fuzz/.gitignore new file mode 100644 index 00000000000..1a45eee7760 --- /dev/null +++ b/rust/tw_aptos/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/rust/tw_aptos/fuzz/Cargo.toml b/rust/tw_aptos/fuzz/Cargo.toml new file mode 100644 index 00000000000..721a84b3ab9 --- /dev/null +++ b/rust/tw_aptos/fuzz/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "tw_aptos-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_proto = { path = "../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_aptos] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/tw_aptos/fuzz/fuzz_targets/sign.rs b/rust/tw_aptos/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..5d55e467d14 --- /dev/null +++ b/rust/tw_aptos/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_aptos::signer::Signer; +use tw_proto::Aptos::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let _ = Signer::sign_proto(input); +}); diff --git a/rust/tw_aptos/src/address.rs b/rust/tw_aptos/src/address.rs new file mode 100644 index 00000000000..3f5cf1d3217 --- /dev/null +++ b/rust/tw_aptos/src/address.rs @@ -0,0 +1,139 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use move_core_types::account_address::{AccountAddress, AccountAddressParseError}; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_hash::sha3::sha3_256; +use tw_keypair::ed25519; +use tw_memory::Data; + +pub trait AptosAddress: FromStr + Into
{ + /// Tries to parse an address from the string representation. + /// Returns `Ok(None)` if the given `s` string is empty. + #[inline] + fn from_str_optional(s: &str) -> AddressResult> { + if s.is_empty() { + return Ok(None); + } + + Self::from_str(s).map(Some) + } +} + +impl AptosAddress for Address {} + +#[repr(u8)] +pub enum Scheme { + Ed25519 = 0, +} + +#[derive(Clone)] +pub struct Address { + addr: AccountAddress, +} + +impl Address { + pub const LENGTH: usize = AccountAddress::LENGTH; + /// Initializes an address with a `ed25519` public key. + + pub fn with_ed25519_pubkey( + pubkey: &ed25519::sha512::PublicKey, + ) -> Result { + let mut to_hash = pubkey.as_slice().to_vec(); + to_hash.push(Scheme::Ed25519 as u8); + let hashed = sha3_256(to_hash.as_slice()); + let addr = AccountAddress::from_bytes(hashed).map_err(from_account_error)?; + Ok(Address { addr }) + } + + pub fn inner(&self) -> AccountAddress { + self.addr + } +} + +impl Display for Address { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.addr.to_hex_literal()) + } +} + +impl CoinAddress for Address { + #[inline] + fn data(&self) -> Data { + self.addr.to_vec() + } +} + +#[inline] +pub fn from_account_error(_err: AccountAddressParseError) -> AddressError { + AddressError::InvalidInput +} + +impl FromStr for Address { + type Err = AddressError; + + // https://github.com/aptos-labs/aptos-core/blob/261019cbdafe1c514c60c2b74357ea2c77d25e67/types/src/account_address.rs#L44 + fn from_str(s: &str) -> Result { + const NUM_CHARS: usize = AccountAddress::LENGTH * 2; + let mut has_0x = false; + let mut working = s.trim(); + + // Checks if it has a 0x at the beginning, which is okay + if working.starts_with("0x") { + has_0x = true; + working = &working[2..]; + } + + if working.len() > NUM_CHARS || (!has_0x && working.len() < NUM_CHARS) { + return Err(AddressError::InvalidInput); + } + + if !working.chars().all(|c| char::is_ascii_hexdigit(&c)) { + return Err(AddressError::InvalidInput); + } + + let addr = if has_0x { + AccountAddress::from_hex_literal(s.trim()) + } else { + AccountAddress::from_str(s.trim()) + } + .map_err(from_account_error)?; + + Ok(Address { addr }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_keypair::ed25519::sha512::PrivateKey; + + #[test] + fn test_from_public_key() { + let private = PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + let addr = Address::with_ed25519_pubkey(&public); + assert_eq!( + addr.as_ref().unwrap().to_string(), + "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4" + ); + assert_eq!(addr.unwrap().data().len(), Address::LENGTH); + } + + #[test] + fn test_from_account_error() { + assert_eq!( + from_account_error(AccountAddressParseError {}), + AddressError::InvalidInput + ); + } +} diff --git a/rust/tw_aptos/src/aptos_move_packages.rs b/rust/tw_aptos/src/aptos_move_packages.rs new file mode 100644 index 00000000000..2e8f7aff378 --- /dev/null +++ b/rust/tw_aptos/src/aptos_move_packages.rs @@ -0,0 +1,209 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::transaction_payload::{EntryFunction, TransactionPayload}; +use move_core_types::account_address::AccountAddress; +use move_core_types::ident_str; +use move_core_types::language_storage::{ModuleId, TypeTag}; +use serde_json::json; +use tw_coin_entry::error::SigningResult; +use tw_encoding::bcs; + +pub fn aptos_account_transfer( + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn aptos_account_create_account(auth_key: AccountAddress) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("create_account").to_owned(), + vec![], + vec![bcs::encode(&auth_key)?], + json!([auth_key.to_hex_literal()]), + ))) +} + +pub fn coin_transfer( + coin_type: TypeTag, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("coin").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![coin_type], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn aptos_account_transfer_coins( + coin_type: TypeTag, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("transfer_coins").to_owned(), + vec![coin_type], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn token_transfers_offer_script( + receiver: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("offer_script").to_owned(), + vec![], + vec![ + bcs::encode(&receiver)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + bcs::encode(&amount)?, + ], + json!([ + receiver.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string(), + amount.to_string() + ]), + ))) +} + +pub fn token_transfers_cancel_offer_script( + receiver: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("cancel_offer_script").to_owned(), + vec![], + vec![ + bcs::encode(&receiver)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + ], + json!([ + receiver.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string() + ]), + ))) +} + +pub fn token_transfers_claim_script( + sender: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("claim_script").to_owned(), + vec![], + vec![ + bcs::encode(&sender)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + ], + json!([ + sender.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string() + ]), + ))) +} + +pub fn managed_coin_register(coin_type: TypeTag) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("managed_coin").to_owned(), + ), + ident_str!("register").to_owned(), + vec![coin_type], + vec![], + json!([]), + )) +} diff --git a/rust/tw_aptos/src/bcs_encoding.rs b/rust/tw_aptos/src/bcs_encoding.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/rust/tw_aptos/src/compiler.rs b/rust/tw_aptos/src/compiler.rs new file mode 100644 index 00000000000..73954261f19 --- /dev/null +++ b/rust/tw_aptos/src/compiler.rs @@ -0,0 +1,74 @@ +use crate::address::Address; +use crate::transaction_builder; +use std::str::FromStr; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct Compiler; + +impl Compiler { + #[inline] + pub fn preimage_hashes( + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .pre_image()?; + Ok(CompilerProto::PreSigningOutput { + data: signed_tx.into(), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signature = signatures + .first() + .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; + let public_key = public_keys + .first() + .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; + + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .compile(signature.to_vec(), public_key.to_vec())?; + Ok(Proto::SigningOutput { + raw_txn: signed_tx.raw_txn_bytes().clone().into(), + encoded: signed_tx.encoded().clone().into(), + authenticator: Some((*signed_tx.authenticator()).clone().into()), + json: signed_tx.to_json().to_string().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_aptos/src/constants.rs b/rust/tw_aptos/src/constants.rs new file mode 100644 index 00000000000..6c30d3b68bc --- /dev/null +++ b/rust/tw_aptos/src/constants.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub const GAS_UNIT_PRICE: u64 = 100; +pub const MAX_GAS_AMOUNT: u64 = 100_000_000; +pub const APTOS_SALT: &[u8] = b"APTOS::RawTransaction"; diff --git a/rust/tw_aptos/src/entry.rs b/rust/tw_aptos/src/entry.rs new file mode 100644 index 00000000000..e2a16cd0ac8 --- /dev/null +++ b/rust/tw_aptos/src/entry.rs @@ -0,0 +1,103 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::compiler::Compiler; +use crate::signer::Signer; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct AptosEntry; + +impl CoinEntry for AptosEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Address::with_ed25519_pubkey(public_key) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::compile(input, signatures, public_keys) + } + + #[inline] + fn json_signer(&self) -> Option { + None + } + + #[inline] + fn message_signer(&self) -> Option { + None + } +} diff --git a/rust/tw_aptos/src/lib.rs b/rust/tw_aptos/src/lib.rs new file mode 100644 index 00000000000..b85498f42c2 --- /dev/null +++ b/rust/tw_aptos/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod aptos_move_packages; +pub mod constants; +pub mod entry; +mod serde_helper; + +pub mod nft; + +pub mod compiler; +pub mod liquid_staking; +pub mod signer; +pub mod transaction; +pub mod transaction_builder; +pub mod transaction_payload; diff --git a/rust/tw_aptos/src/liquid_staking.rs b/rust/tw_aptos/src/liquid_staking.rs new file mode 100644 index 00000000000..2ac80692050 --- /dev/null +++ b/rust/tw_aptos/src/liquid_staking.rs @@ -0,0 +1,154 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::from_account_error; +use crate::transaction_payload::{EntryFunction, TransactionPayload}; +use move_core_types::{account_address::AccountAddress, ident_str, language_storage::ModuleId}; +use serde_json::json; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_encoding::bcs; +use tw_proto::{ + Aptos::Proto::mod_LiquidStaking::OneOfliquid_stake_transaction_payload, + Aptos::Proto::{LiquidStaking, TortugaClaim, TortugaStake, TortugaUnstake}, +}; + +pub fn tortuga_stake( + smart_contract_address: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("stake").to_owned(), + vec![], + vec![bcs::encode(&amount)?], + json!([amount.to_string()]), + ))) +} + +pub fn tortuga_unstake( + smart_contract_address: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("unstake").to_owned(), + vec![], + vec![bcs::encode(&amount)?], + json!([amount.to_string()]), + ))) +} + +pub fn tortuga_claim( + smart_contract_address: AccountAddress, + idx: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("claim").to_owned(), + vec![], + vec![bcs::encode(&idx)?], + json!([idx.to_string()]), + ))) +} + +pub struct Stake { + pub amount: u64, + pub smart_contract_address: AccountAddress, +} + +pub struct Unstake { + pub amount: u64, + pub smart_contract_address: AccountAddress, +} + +pub struct Claim { + pub idx: u64, + pub smart_contract_address: AccountAddress, +} + +pub enum LiquidStakingOperation { + Stake(Stake), + Unstake(Unstake), + Claim(Claim), +} + +impl TryFrom> for LiquidStakingOperation { + type Error = SigningError; + + fn try_from(value: LiquidStaking) -> SigningResult { + match value.liquid_stake_transaction_payload { + OneOfliquid_stake_transaction_payload::stake(stake_msg) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error)?; + Ok(LiquidStakingOperation::Stake(Stake { + amount: stake_msg.amount, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::unstake(unstake_msg) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error)?; + Ok(LiquidStakingOperation::Unstake(Unstake { + amount: unstake_msg.amount, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::claim(claim) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error)?; + Ok(LiquidStakingOperation::Claim(Claim { + idx: claim.idx, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::None => { + Err(SigningError(SigningErrorType::Error_invalid_params)) + }, + } + } +} + +impl From for LiquidStaking<'_> { + fn from(value: LiquidStakingOperation) -> Self { + match value { + LiquidStakingOperation::Stake(stake) => LiquidStaking { + smart_contract_address: stake.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::stake( + TortugaStake { + amount: stake.amount, + }, + ), + }, + LiquidStakingOperation::Unstake(unstake) => LiquidStaking { + smart_contract_address: unstake.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::unstake( + TortugaUnstake { + amount: unstake.amount, + }, + ), + }, + LiquidStakingOperation::Claim(claim) => LiquidStaking { + smart_contract_address: claim.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::claim( + TortugaClaim { idx: claim.idx }, + ), + }, + } + } +} diff --git a/rust/tw_aptos/src/nft.rs b/rust/tw_aptos/src/nft.rs new file mode 100644 index 00000000000..fe6f0375146 --- /dev/null +++ b/rust/tw_aptos/src/nft.rs @@ -0,0 +1,165 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::from_account_error; +use move_core_types::account_address::AccountAddress; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_proto::Aptos::Proto::mod_NftMessage::OneOfnft_transaction_payload; +use tw_proto::Aptos::Proto::{CancelOfferNftMessage, ClaimNftMessage, NftMessage, OfferNftMessage}; + +pub struct Offer { + pub receiver: AccountAddress, + pub creator: AccountAddress, + pub collection: Vec, + pub name: Vec, + pub property_version: u64, + pub amount: u64, +} + +pub struct Claim { + pub sender: AccountAddress, + pub creator: AccountAddress, + pub collection: Vec, + pub name: Vec, + pub property_version: u64, +} + +pub enum NftOperation { + Claim(Claim), + Offer(Offer), + Cancel(Offer), +} + +impl TryFrom> for NftOperation { + type Error = SigningError; + + fn try_from(value: NftMessage) -> SigningResult { + match value.nft_transaction_payload { + OneOfnft_transaction_payload::offer_nft(msg) => { + Ok(NftOperation::Offer(Offer::try_from(msg)?)) + }, + OneOfnft_transaction_payload::cancel_offer_nft(msg) => { + Ok(NftOperation::Cancel(Offer::try_from(msg)?)) + }, + OneOfnft_transaction_payload::claim_nft(msg) => { + Ok(NftOperation::Claim(Claim::try_from(msg)?)) + }, + OneOfnft_transaction_payload::None => { + Err(SigningError(SigningErrorType::Error_invalid_params)) + }, + } + } +} + +impl From for NftMessage<'_> { + fn from(value: NftOperation) -> Self { + match value { + NftOperation::Claim(claim) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::claim_nft(claim.into()), + }, + NftOperation::Offer(offer) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::offer_nft(offer.into()), + }, + NftOperation::Cancel(cancel) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::cancel_offer_nft( + cancel.into(), + ), + }, + } + } +} + +impl TryFrom> for Offer { + type Error = SigningError; + + fn try_from(value: OfferNftMessage) -> SigningResult { + Ok(Offer { + receiver: AccountAddress::from_str(&value.receiver).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + amount: value.amount, + }) + } +} + +impl From for OfferNftMessage<'_> { + fn from(value: Offer) -> Self { + OfferNftMessage { + receiver: value.receiver.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(&value.name).to_string().into(), + property_version: value.property_version, + amount: value.amount, + } + } +} + +impl TryFrom> for Offer { + type Error = SigningError; + + fn try_from(value: CancelOfferNftMessage) -> SigningResult { + Ok(Offer { + receiver: AccountAddress::from_str(&value.receiver).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + amount: 0, + }) + } +} + +impl From for CancelOfferNftMessage<'_> { + fn from(value: Offer) -> Self { + CancelOfferNftMessage { + receiver: value.receiver.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(value.name.as_slice()) + .to_string() + .into(), + property_version: value.property_version, + } + } +} + +impl TryFrom> for Claim { + type Error = SigningError; + + fn try_from(value: ClaimNftMessage) -> SigningResult { + Ok(Claim { + sender: AccountAddress::from_str(&value.sender).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + }) + } +} + +impl From for ClaimNftMessage<'_> { + fn from(value: Claim) -> Self { + ClaimNftMessage { + sender: value.sender.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(value.name.as_slice()) + .to_string() + .into(), + property_version: value.property_version, + } + } +} diff --git a/rust/tw_aptos/src/serde_helper/mod.rs b/rust/tw_aptos/src/serde_helper/mod.rs new file mode 100644 index 00000000000..876f018225e --- /dev/null +++ b/rust/tw_aptos/src/serde_helper/mod.rs @@ -0,0 +1,5 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod vec_bytes; diff --git a/rust/tw_aptos/src/serde_helper/vec_bytes.rs b/rust/tw_aptos/src/serde_helper/vec_bytes.rs new file mode 100644 index 00000000000..f57311d19dd --- /dev/null +++ b/rust/tw_aptos/src/serde_helper/vec_bytes.rs @@ -0,0 +1,30 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use serde::{ + de::Deserializer, + ser::{SerializeSeq, Serializer}, + Deserialize, +}; + +pub fn serialize(data: &[Vec], serializer: S) -> Result +where + S: Serializer, +{ + let mut seq = serializer.serialize_seq(Some(data.len()))?; + for e in data { + seq.serialize_element(serde_bytes::Bytes::new(e.as_slice()))?; + } + seq.end() +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + Ok(>::deserialize(deserializer)? + .into_iter() + .map(serde_bytes::ByteBuf::into_vec) + .collect()) +} diff --git a/rust/tw_aptos/src/signer.rs b/rust/tw_aptos/src/signer.rs new file mode 100644 index 00000000000..91431569c73 --- /dev/null +++ b/rust/tw_aptos/src/signer.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::transaction_builder; +use std::str::FromStr; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519; +use tw_proto::Aptos::Proto; + +pub struct Signer; + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let key_pair = ed25519::sha512::KeyPair::try_from(input.private_key.as_ref())?; + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .sign(key_pair)?; + Ok(Proto::SigningOutput { + raw_txn: signed_tx.raw_txn_bytes().clone().into(), + encoded: signed_tx.encoded().clone().into(), + authenticator: Some((*signed_tx.authenticator()).clone().into()), + json: signed_tx.to_json().to_string().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_aptos/src/transaction.rs b/rust/tw_aptos/src/transaction.rs new file mode 100644 index 00000000000..b48d7950bbc --- /dev/null +++ b/rust/tw_aptos/src/transaction.rs @@ -0,0 +1,220 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::constants::APTOS_SALT; +use crate::transaction_payload::TransactionPayload; +use move_core_types::account_address::AccountAddress; +use serde::Serialize; +use serde_json::{json, Value}; +use std::borrow::Cow; +use tw_coin_entry::error::SigningResult; +use tw_encoding::hex::encode; +use tw_encoding::{bcs, EncodingResult}; +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::Data; +use tw_proto::Aptos::Proto; + +#[derive(Clone, Serialize)] +pub enum TransactionAuthenticator { + /// Single Ed25519 signature + Ed25519 { + public_key: Vec, + signature: Vec, + }, +} + +impl From for Proto::TransactionAuthenticator<'_> { + fn from(from: TransactionAuthenticator) -> Self { + Proto::TransactionAuthenticator { + signature: Cow::from(from.get_signature()), + public_key: Cow::from(from.get_public_key()), + } + } +} + +impl TransactionAuthenticator { + pub fn get_signature(&self) -> Vec { + match self { + TransactionAuthenticator::Ed25519 { + public_key: _public_key, + signature, + } => signature.clone(), + } + } + + pub fn get_public_key(&self) -> Vec { + match self { + TransactionAuthenticator::Ed25519 { + public_key, + signature: _signature, + } => public_key.clone(), + } + } + + pub fn to_json(&self) -> Value { + match self { + TransactionAuthenticator::Ed25519 { + public_key, + signature, + } => { + json!({"public_key": encode(public_key, true), + "signature": encode(signature, true), + "type": "ed25519_signature"}) + }, + } + } +} + +/// RawTransaction is the portion of a transaction that a client signs. +#[derive(Clone, Serialize)] +pub struct RawTransaction { + /// Sender's address. + sender: AccountAddress, + + /// Sequence number of this transaction. This must match the sequence number + /// stored in the sender's account at the time the transaction executes. + sequence_number: u64, + + /// The transaction payload, e.g., a script to execute. + payload: TransactionPayload, + + /// Maximal total gas to spend for this transaction. + max_gas_amount: u64, + + /// Price to be paid per gas unit. + gas_unit_price: u64, + + /// Expiration timestamp for this transaction, represented + /// as seconds from the Unix Epoch. If the current blockchain timestamp + /// is greater than or equal to this time, then the transaction has + /// expired and will be discarded. This can be set to a large value far + /// in the future to indicate that a transaction does not expire. + expiration_timestamp_secs: u64, + + /// Chain ID of the Aptos network this transaction is intended for. + chain_id: u8, +} + +impl RawTransaction { + /// Create a new `RawTransaction` with a payload. + /// + /// It can be either to publish a module, to execute a script, or to issue a writeset + /// transaction. + pub fn new( + sender: AccountAddress, + sequence_number: u64, + payload: TransactionPayload, + max_gas_amount: u64, + gas_unit_price: u64, + expiration_timestamp_secs: u64, + chain_id: u8, + ) -> Self { + RawTransaction { + sender, + sequence_number, + payload, + max_gas_amount, + gas_unit_price, + expiration_timestamp_secs, + chain_id, + } + } + + /// Create a new `RawTransaction` with an entry function + fn serialize(&self) -> EncodingResult { + bcs::encode(&self) + } + + fn msg_to_sign(&self) -> SigningResult { + let serialized = self.serialize()?; + let mut preimage = tw_hash::sha3::sha3_256(APTOS_SALT); + preimage.extend_from_slice(serialized.as_slice()); + Ok(preimage) + } + + pub fn pre_image(&self) -> SigningResult> { + self.msg_to_sign() + } + + pub fn compile( + &self, + signature: Vec, + public_key: Vec, + ) -> SigningResult { + let serialized = self.serialize()?; + let auth = TransactionAuthenticator::Ed25519 { + public_key, + signature, + }; + let mut encoded = serialized.clone(); + encoded.extend_from_slice(bcs::encode(&auth)?.as_slice()); + Ok(SignedTransaction { + raw_txn: self.clone(), + authenticator: auth, + raw_txn_bytes: serialized.to_vec(), + encoded, + }) + } + + pub fn sign(self, key_pair: KeyPair) -> SigningResult { + let to_sign = self.pre_image()?; + let signature = key_pair.private().sign(to_sign)?.to_bytes().into_vec(); + let pubkey = key_pair.public().as_slice().to_vec(); + self.compile(signature, pubkey) + } + + pub fn to_json(&self) -> Value { + json!({ + "expiration_timestamp_secs": self.expiration_timestamp_secs.to_string(), + "gas_unit_price": self.gas_unit_price.to_string(), + "max_gas_amount": self.max_gas_amount.to_string(), + "payload": self.payload.to_json(), + "sender": self.sender.to_hex_literal(), + "sequence_number": self.sequence_number.to_string() + }) + } +} + +/// A transaction that has been signed. +/// +/// A `SignedTransaction` is a single transaction that can be atomically executed. Clients submit +/// these to validator nodes, and the validator and executor submits these to the VM. +/// +#[derive(Clone, Serialize)] +pub struct SignedTransaction { + /// The raw transaction + raw_txn: RawTransaction, + + /// Public key and signature to authenticate + authenticator: TransactionAuthenticator, + + #[serde(skip_serializing)] + /// Raw txs bytes + raw_txn_bytes: Vec, + + #[serde(skip_serializing)] + /// Encoded bytes to be broadcast + encoded: Vec, +} + +impl SignedTransaction { + pub fn authenticator(&self) -> &TransactionAuthenticator { + &self.authenticator + } + pub fn raw_txn_bytes(&self) -> &Vec { + &self.raw_txn_bytes + } + pub fn encoded(&self) -> &Vec { + &self.encoded + } + + pub fn to_json(&self) -> Value { + let mut json_value = self.raw_txn.to_json(); + json_value["signature"] = self.authenticator.to_json(); + json_value + } +} diff --git a/rust/tw_aptos/src/transaction_builder.rs b/rust/tw_aptos/src/transaction_builder.rs new file mode 100644 index 00000000000..53f72d32936 --- /dev/null +++ b/rust/tw_aptos/src/transaction_builder.rs @@ -0,0 +1,263 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::from_account_error; +use crate::aptos_move_packages::{ + aptos_account_create_account, aptos_account_transfer, aptos_account_transfer_coins, + coin_transfer, managed_coin_register, token_transfers_cancel_offer_script, + token_transfers_claim_script, token_transfers_offer_script, +}; +use crate::constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT}; +use crate::liquid_staking::{ + tortuga_claim, tortuga_stake, tortuga_unstake, LiquidStakingOperation, +}; +use crate::nft::NftOperation; +use crate::transaction::RawTransaction; +use crate::transaction_payload::{ + convert_proto_struct_tag_to_type_tag, EntryFunction, TransactionPayload, +}; +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::TypeTag; +use serde_json::Value; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_proto::Aptos::Proto::mod_SigningInput::OneOftransaction_payload; +use tw_proto::Aptos::Proto::SigningInput; + +pub struct TransactionBuilder { + sender: Option, + sequence_number: Option, + payload: TransactionPayload, + max_gas_amount: u64, + gas_unit_price: u64, + expiration_timestamp_secs: u64, + chain_id: u8, +} + +impl TransactionBuilder { + pub fn sender(mut self, sender: AccountAddress) -> Self { + self.sender = Some(sender); + self + } + + pub fn sequence_number(mut self, sequence_number: u64) -> Self { + self.sequence_number = Some(sequence_number); + self + } + + pub fn build(self) -> SigningResult { + let sender = self + .sender + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let sequence_number = self + .sequence_number + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + Ok(RawTransaction::new( + sender, + sequence_number, + self.payload, + self.max_gas_amount, + self.gas_unit_price, + self.expiration_timestamp_secs, + self.chain_id, + )) + } +} + +#[derive(Clone, Debug)] +pub struct TransactionFactory { + max_gas_amount: u64, + gas_unit_price: u64, + transaction_expiration_time: u64, + chain_id: u8, +} + +impl TransactionFactory { + pub fn new(chain_id: u8) -> Self { + Self { + max_gas_amount: MAX_GAS_AMOUNT, + gas_unit_price: GAS_UNIT_PRICE, + transaction_expiration_time: 30, + chain_id, + } + } + + pub fn new_from_protobuf(input: SigningInput) -> SigningResult { + let factory = TransactionFactory::new(input.chain_id as u8) + .with_gas_unit_price(input.gas_unit_price) + .with_max_gas_amount(input.max_gas_amount) + .with_transaction_expiration_time(input.expiration_timestamp_secs); + match input.transaction_payload { + OneOftransaction_payload::transfer(transfer) => factory + .implicitly_create_user_account_and_transfer( + AccountAddress::from_str(&transfer.to).map_err(from_account_error)?, + transfer.amount, + ), + OneOftransaction_payload::token_transfer(token_transfer) => { + let func = token_transfer + .function + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + factory.coins_transfer( + AccountAddress::from_str(&token_transfer.to).map_err(from_account_error)?, + token_transfer.amount, + convert_proto_struct_tag_to_type_tag(func)?, + ) + }, + OneOftransaction_payload::create_account(create_account) => { + let address = AccountAddress::from_str(&create_account.auth_key) + .map_err(from_account_error)?; + factory.create_user_account(address) + }, + OneOftransaction_payload::nft_message(nft_message) => { + factory.nft_ops(NftOperation::try_from(nft_message)?) + }, + OneOftransaction_payload::register_token(register_token) => { + let function = register_token + .function + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + Ok(factory.register_token(convert_proto_struct_tag_to_type_tag(function)?)) + }, + OneOftransaction_payload::liquid_staking_message(msg) => { + factory.liquid_staking_ops(LiquidStakingOperation::try_from(msg)?) + }, + OneOftransaction_payload::token_transfer_coins(token_transfer_coins) => { + let func = token_transfer_coins + .function + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + factory.implicitly_create_user_and_coins_transfer( + AccountAddress::from_str(&token_transfer_coins.to) + .map_err(from_account_error)?, + token_transfer_coins.amount, + convert_proto_struct_tag_to_type_tag(func)?, + ) + }, + OneOftransaction_payload::None => { + let is_blind_sign = !input.any_encoded.is_empty(); + let v = serde_json::from_str::(&input.any_encoded)?; + if is_blind_sign { + let entry_function = EntryFunction::try_from(v)?; + Ok(factory.payload(TransactionPayload::EntryFunction(entry_function))) + } else { + Err(SigningError(SigningErrorType::Error_input_parse)) + } + }, + } + } + + pub fn with_max_gas_amount(mut self, max_gas_amount: u64) -> Self { + self.max_gas_amount = max_gas_amount; + self + } + + pub fn with_gas_unit_price(mut self, gas_unit_price: u64) -> Self { + self.gas_unit_price = gas_unit_price; + self + } + + pub fn with_transaction_expiration_time(mut self, transaction_expiration_time: u64) -> Self { + self.transaction_expiration_time = transaction_expiration_time; + self + } + + pub fn payload(&self, payload: TransactionPayload) -> TransactionBuilder { + self.transaction_builder(payload) + } + + pub fn create_user_account(&self, to: AccountAddress) -> SigningResult { + Ok(self.payload(aptos_account_create_account(to)?)) + } + + pub fn register_token(&self, coin_type: TypeTag) -> TransactionBuilder { + self.payload(managed_coin_register(coin_type)) + } + + pub fn nft_ops(&self, operation: NftOperation) -> SigningResult { + match operation { + NftOperation::Claim(claim) => Ok(self.payload(token_transfers_claim_script( + claim.sender, + claim.creator, + claim.collection, + claim.name, + claim.property_version, + )?)), + NftOperation::Cancel(offer) => Ok(self.payload(token_transfers_cancel_offer_script( + offer.receiver, + offer.creator, + offer.collection, + offer.name, + offer.property_version, + )?)), + NftOperation::Offer(offer) => Ok(self.payload(token_transfers_offer_script( + offer.receiver, + offer.creator, + offer.collection, + offer.name, + offer.property_version, + offer.amount, + )?)), + } + } + + pub fn liquid_staking_ops( + &self, + operation: LiquidStakingOperation, + ) -> SigningResult { + match operation { + LiquidStakingOperation::Stake(stake) => { + Ok(self.payload(tortuga_stake(stake.smart_contract_address, stake.amount)?)) + }, + LiquidStakingOperation::Unstake(unstake) => Ok(self.payload(tortuga_unstake( + unstake.smart_contract_address, + unstake.amount, + )?)), + LiquidStakingOperation::Claim(claim) => { + Ok(self.payload(tortuga_claim(claim.smart_contract_address, claim.idx)?)) + }, + } + } + + pub fn implicitly_create_user_account_and_transfer( + &self, + to: AccountAddress, + amount: u64, + ) -> SigningResult { + Ok(self.payload(aptos_account_transfer(to, amount)?)) + } + + pub fn coins_transfer( + &self, + to: AccountAddress, + amount: u64, + coin_type: TypeTag, + ) -> SigningResult { + Ok(self.payload(coin_transfer(coin_type, to, amount)?)) + } + + pub fn implicitly_create_user_and_coins_transfer( + &self, + to: AccountAddress, + amount: u64, + coin_type: TypeTag, + ) -> SigningResult { + Ok(self.payload(aptos_account_transfer_coins(coin_type, to, amount)?)) + } + + fn transaction_builder(&self, payload: TransactionPayload) -> TransactionBuilder { + TransactionBuilder { + sender: None, + sequence_number: None, + payload, + max_gas_amount: self.max_gas_amount, + gas_unit_price: self.gas_unit_price, + expiration_timestamp_secs: self.expiration_timestamp(), + chain_id: self.chain_id, + } + } + + fn expiration_timestamp(&self) -> u64 { + self.transaction_expiration_time + } +} diff --git a/rust/tw_aptos/src/transaction_payload.rs b/rust/tw_aptos/src/transaction_payload.rs new file mode 100644 index 00000000000..5e33e71d465 --- /dev/null +++ b/rust/tw_aptos/src/transaction_payload.rs @@ -0,0 +1,268 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::serde_helper::vec_bytes; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::{ModuleId, StructTag, TypeTag}; +use move_core_types::parser::parse_transaction_argument; +use move_core_types::transaction_argument::TransactionArgument; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::default::Default; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_encoding::{bcs, EncodingError, EncodingResult}; +use tw_memory::Data; +use tw_proto::Aptos; + +pub type EntryFunctionResult = Result; + +#[derive(Debug)] +pub enum EntryFunctionError { + MissingFunctionName, + InvalidFunctionName, + MissingArguments, + InvalidArguments, + EncodingError, + MissingTypeArguments, + InvalidTypeArguments, +} + +impl From for EntryFunctionError { + fn from(_error: EncodingError) -> Self { + EntryFunctionError::EncodingError + } +} + +impl From for SigningError { + fn from(_: EntryFunctionError) -> Self { + SigningError(SigningErrorType::Error_invalid_params) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct EntryFunction { + module: ModuleId, + function: Identifier, + ty_args: Vec, + #[serde(with = "vec_bytes")] + args: Vec>, + #[serde(skip_serializing)] + json_args: Value, +} + +impl TryFrom for EntryFunction { + type Error = EntryFunctionError; + + fn try_from(value: Value) -> EntryFunctionResult { + let function_str = value["function"] + .as_str() + .ok_or(EntryFunctionError::MissingFunctionName)?; + let tag = StructTag::from_str(function_str) + .map_err(|_| EntryFunctionError::InvalidFunctionName)?; + + let args = value["arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingArguments)? + .iter() + .map(|element| { + let arg_str = element.to_string(); + let arg = parse_transaction_argument( + arg_str.trim_start_matches('"').trim_end_matches('"'), + ) + .map_err(|_| EntryFunctionError::InvalidArguments)?; + serialize_argument(&arg).map_err(EntryFunctionError::from) + }) + .collect::>>()?; + + let ty_args = value["type_arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingTypeArguments)? + .iter() + .map(|element| { + let ty_arg_str = element + .as_str() + .ok_or(EntryFunctionError::InvalidTypeArguments)?; + TypeTag::from_str(ty_arg_str).map_err(|_| EntryFunctionError::InvalidTypeArguments) + }) + .collect::>>()?; + + Ok(EntryFunction { + module: tag.module_id(), + function: tag.name, + ty_args, + args, + json_args: value["arguments"].clone(), + }) + } +} + +fn serialize_argument(arg: &TransactionArgument) -> EncodingResult { + match arg { + TransactionArgument::U8(v) => bcs::encode(v), + TransactionArgument::U16(v) => bcs::encode(v), + TransactionArgument::U32(v) => bcs::encode(v), + TransactionArgument::U64(v) => bcs::encode(v), + TransactionArgument::U128(v) => bcs::encode(v), + TransactionArgument::U256(v) => bcs::encode(v), + TransactionArgument::U8Vector(v) => bcs::encode(v), + TransactionArgument::Bool(v) => bcs::encode(v), + TransactionArgument::Address(v) => { + let serialized_v = bcs::encode(v)?; + bcs::encode(&serialized_v) + }, + } +} + +pub fn convert_proto_struct_tag_to_type_tag( + struct_tag: Aptos::Proto::StructTag, +) -> SigningResult { + TypeTag::from_str(&format!( + "{}::{}::{}", + struct_tag.account_address, struct_tag.module, struct_tag.name + )) + .map_err(|_| SigningError(SigningErrorType::Error_invalid_params)) +} + +pub fn convert_type_tag_to_struct_tag(type_tag: TypeTag) -> Aptos::Proto::StructTag<'static> { + if let TypeTag::Struct(st) = type_tag { + Aptos::Proto::StructTag { + account_address: st.address.to_hex_literal().into(), + module: st.module.to_string().into(), + name: st.name.to_string().into(), + } + } else { + Aptos::Proto::StructTag::default() + } +} + +impl EntryFunction { + fn to_json(&self) -> Value { + // Create a JSON array from the `ty_args` field by filtering and mapping + // the items that match `TypeTag::Struct` to their string representation. + let type_arguments: Value = self + .ty_args + .iter() + .map(|item| Some(json!(item.to_string()))) + .collect(); + + // Construct the final JSON value + json!({ + "type": "entry_function_payload", + "function": format!("{}::{}", self.module.short_str_lossless(), self.function.clone().into_string()), + "arguments": self.json_args, + "type_arguments": type_arguments + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum TransactionPayload { + Script, + ModuleBundle, + /// A transaction that executes an existing entry function published on-chain. + EntryFunction(EntryFunction), +} + +impl TransactionPayload { + pub fn to_json(&self) -> Value { + match self { + TransactionPayload::Script => Value::default(), + TransactionPayload::ModuleBundle => Value::default(), + TransactionPayload::EntryFunction(entry) => entry.to_json(), + } + } +} + +impl EntryFunction { + pub fn new( + module: ModuleId, + function: Identifier, + ty_args: Vec, + args: Vec>, + json_args: Value, + ) -> Self { + EntryFunction { + module, + function, + ty_args, + args, + json_args, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::Address; + use move_core_types::account_address::AccountAddress; + use move_core_types::identifier::Identifier; + use move_core_types::language_storage::{ModuleId, TypeTag}; + use serde_json::{json, Value}; + use std::str::FromStr; + use tw_encoding::hex; + + #[test] + fn test_payload_from_json() { + let payload_value: Value = json!({ + "arguments": ["0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6"], + "function": "0xc23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d50::IFO::release", + "type": "entry_function_payload", + "type_arguments": [ + "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::BUSD", + "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::DAI", + "0x9936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a::uints::U1" + ] + }); + + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + assert_eq!(payload_value, v.to_json()); + } + + #[test] + fn test_payload_from_json_with_arg_non_str() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0xd11107bdf0d6d7040c6c0bfbdecb6545191fdf13e8d8d259952f53e1713f61b5::ditto_staking::stake_aptos", + "type_arguments":[], + "arguments": [1000000] + }); + + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + assert_eq!(payload_value, v.to_json()); + } + + #[test] + fn test_basic_payload() { + let addr = + Address::from_str("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b") + .unwrap() + .inner(); + let amount: i64 = 1000; + let args = vec![bcs::encode(&addr).unwrap(), bcs::encode(&amount).unwrap()]; + let module = ModuleId::new(AccountAddress::ONE, Identifier::from_str("coin").unwrap()); + let function = Identifier::from_str("transfer").unwrap(); + let type_tag = vec![TypeTag::from_str("0x1::aptos_coin::AptosCoin").unwrap()]; + let entry = EntryFunction::new( + module, + function, + type_tag, + args, + json!([addr.to_hex_literal(), amount.to_string()]), + ); + let tp = TransactionPayload::EntryFunction(entry); + let serialized = bcs::encode(&tp).unwrap(); + assert_eq!(hex::encode(serialized, false), "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"); + let payload_value: Value = json!({ + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "arguments": ["0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", "1000"], + "type_arguments": ["0x1::aptos_coin::AptosCoin"] + }); + assert_eq!(tp.to_json(), payload_value); + } +} diff --git a/rust/tw_aptos/tests/signer.rs b/rust/tw_aptos/tests/signer.rs new file mode 100644 index 00000000000..09f80e2e26a --- /dev/null +++ b/rust/tw_aptos/tests/signer.rs @@ -0,0 +1,873 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::TypeTag; +use std::str::FromStr; +use tw_aptos::liquid_staking; +use tw_aptos::liquid_staking::{LiquidStakingOperation, Stake, Unstake}; +use tw_aptos::nft::{Claim, NftOperation, Offer}; +use tw_aptos::signer::Signer; +use tw_aptos::transaction_payload::convert_type_tag_to_struct_tag; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex; +use tw_proto::Aptos::Proto; +use tw_proto::Aptos::Proto::{SigningInput, SigningOutput}; + +pub struct AccountCreation { + to: String, +} + +pub struct Transfer { + to: String, + amount: u64, +} + +pub struct TokenTransfer { + transfer: Transfer, + tag: TypeTag, +} + +pub struct RegisterToken { + coin_type: TypeTag, +} + +pub enum OpsDetails { + RegisterToken(RegisterToken), + LiquidStakingOps(LiquidStakingOperation), + AccountCreation(AccountCreation), + Transfer(Transfer), + TokenTransfer(TokenTransfer), + ImplicitTokenTransfer(TokenTransfer), + NftOps(NftOperation), +} + +fn setup_proto_transaction<'a>( + sender: &'a str, + keypair_str: &'a str, + transaction_type: &'a str, + sequence_number: i64, + chain_id: u32, + max_gas_amount: u64, + timestamp: u64, + gas_unit_price: u64, + any_encoded: &'a str, + ops_details: Option, +) -> SigningInput<'a> { + let private = hex::decode(keypair_str).unwrap(); + + let payload: Proto::mod_SigningInput::OneOftransaction_payload = match transaction_type { + "transfer" => { + if let OpsDetails::Transfer(transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::transfer( + Proto::TransferMessage { + to: transfer.to.into(), + amount: transfer.amount, + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "create_account" => { + if let OpsDetails::AccountCreation(account) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::create_account( + Proto::CreateAccountMessage { + auth_key: account.to.into(), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "coin_transfer" => { + if let OpsDetails::TokenTransfer(token_transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::token_transfer( + Proto::TokenTransferMessage { + to: token_transfer.transfer.to.into(), + amount: token_transfer.transfer.amount, + function: Some(convert_type_tag_to_struct_tag(token_transfer.tag)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "implicit_coin_transfer" => { + if let OpsDetails::ImplicitTokenTransfer(token_transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::token_transfer_coins( + Proto::TokenTransferCoinsMessage { + to: token_transfer.transfer.to.into(), + amount: token_transfer.transfer.amount, + function: Some(convert_type_tag_to_struct_tag(token_transfer.tag)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "nft_ops" => { + if let OpsDetails::NftOps(nft) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::nft_message(nft.into()) + } else { + panic!("Unsupported arguments") + } + }, + "register_token" => { + if let OpsDetails::RegisterToken(register_token) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::register_token( + Proto::ManagedTokensRegisterMessage { + function: Some(convert_type_tag_to_struct_tag(register_token.coin_type)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "liquid_staking_ops" => { + if let OpsDetails::LiquidStakingOps(liquid_staking_ops) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::liquid_staking_message( + liquid_staking_ops.into(), + ) + } else { + panic!("Unsupported arguments") + } + }, + "blind_sign_json" => Proto::mod_SigningInput::OneOftransaction_payload::None, + _ => Proto::mod_SigningInput::OneOftransaction_payload::None, + }; + + let input = SigningInput { + chain_id, + sender: sender.into(), + sequence_number, + max_gas_amount, + gas_unit_price, + expiration_timestamp_secs: timestamp, + private_key: private.into(), + any_encoded: any_encoded.into(), + transaction_payload: payload, + }; + + input +} + +fn test_tx_result( + output: SigningOutput, + expected_raw_txn_bytes_str: &str, + expected_signature_str: &str, + expected_encoded_txn_str: &str, + json_literal: &str, +) { + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.raw_txn.to_vec(), false), + expected_raw_txn_bytes_str + ); + assert_eq!( + hex::encode(output.authenticator.unwrap().signature.to_vec(), false), + expected_signature_str + ); + assert_eq!( + hex::encode(output.encoded.to_vec(), false), + expected_encoded_txn_str + ); + + let json_value_expected: serde_json::Value = serde_json::from_str(json_literal).unwrap(); + let json_value: serde_json::Value = serde_json::from_str(output.json.as_ref()).unwrap(); + assert_eq!(json_value, json_value_expected); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet +#[test] +fn test_aptos_sign_transaction_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", + "transfer", + 99, + 33, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::Transfer(Transfer { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".to_string(), + amount: 1000, + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021", + "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x477141736de6b0936a6c3734e4d6fd018c7d21f1f28f99028ef0bc6881168602?network=Devnet +#[test] +fn test_aptos_sign_create_account() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "create_account", + 0, // Sequence number + 33, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::AccountCreation(AccountCreation { + to: "0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e".to_string(), + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada0000000021", // Expected raw transaction bytes + "fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"], + "function": "0x1::aptos_account::create_account", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "0", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xfcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb5b383a5c7f99b2edb3bed9533f8169a89051b149d65876a82f4c0b9bf78a15b?network=Devnet +#[test] +fn test_aptos_sign_coin_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "coin_transfer", + 24, // Sequence number + 32, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::TokenTransfer(TokenTransfer { + transfer: Transfer { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + .to_string(), + amount: 100000, + }, + tag: TypeTag::from_str( + "0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC", + ) + .unwrap(), + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada0000000020", // Expected raw transaction bytes + "7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada00000000200020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c407643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","100000"], + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "type_arguments": ["0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "24", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet +#[test] +fn test_implicit_aptos_sign_coin_transfer() { + let input = setup_proto_transaction("0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", // Sender's address + "e7f56c77189e03699a75d8ec5c090e41f3d9d4783bc49c33df8a93d915e10de8", // Keypair + "implicit_coin_transfer", + 2, // Sequence number + 1, + 2000, + 3664390082, + 100, + "", + Some(OpsDetails::ImplicitTokenTransfer(TokenTransfer { transfer: Transfer { to: "0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c".to_string(), amount: 10000 }, tag: TypeTag::from_str("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin").unwrap() })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", // Expected signature + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000", + "payload": { + "arguments": ["0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c","10000"], + "function": "0x1::aptos_account::transfer_coins", + "type": "entry_function_payload", + "type_arguments": ["0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin"] + }, + "sender": "0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", + "sequence_number": "2", + "signature": { + "public_key": "0x62e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca8369", + "signature": "0x30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x514e473618bd3cb89a2b110b7c473db9a2e10532f98eb42d02d86fb31c00525d?network=testnet +#[test] +fn test_aptos_nft_offer() { + let input = setup_proto_transaction( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", // Sender's address + "7bebb6d543d17f6fe4e685cfab239fa37896edd594ff859f1df32f244fb707e2", // Keypair + "nft_ops", + 1, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Offer(Offer { + receiver: AccountAddress::from_str( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + amount: 1, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", // Expected signature + "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada00000000020020d1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a411340af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0", "1"], + "function": "0x3::token_transfers::offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "sequence_number": "1", + "signature": { + "public_key": "0xd1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a4113", + "signature": "0xaf5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x0b8c64e6847c368e4c6bd2cce0e9eab378971b0ef2e3bc40cbd292910a80201d?network=testnet +#[test] +fn test_aptos_cancel_nft_offer() { + let input = setup_proto_transaction( + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "nft_ops", + 21, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Cancel(Offer { + receiver: AccountAddress::from_str( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + amount: 0, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::cancel_offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "21", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x60b51e15140ec0b7650334e948fb447ce3cb13ae63492260461ebfa9d02e85c4?network=testnet +#[test] +fn test_aptos_nft_claim() { + let input = setup_proto_transaction( + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "nft_ops", + 19, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Claim(Claim { + sender: AccountAddress::from_str( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::claim_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "19", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet +#[test] +fn test_aptos_register_token() { + let input = setup_proto_transaction("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "register_token", + 23, // Sequence number + 2, + 2000000, + 3664390082, + 100, + "", + Some(OpsDetails::RegisterToken(RegisterToken { coin_type: TypeTag::from_str("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin").unwrap() })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000000", + "payload": { + "arguments": [], + "function": "0x1::managed_coin::register", + "type": "entry_function_payload", + "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "23", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet +#[test] +fn test_aptos_tortuga_stake() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 19, // Sequence number + 1, + 5554, + 1670240203, + 100, + "", + Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Stake( + Stake { + amount: 100000000, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }, + ))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001", // Expected raw transaction bytes + "22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc44022d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "19", + "max_gas_amount": "5554", + "gas_unit_price": "100", + "expiration_timestamp_secs": "1670240203", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968?network=mainnet +#[test] +fn test_aptos_tortuga_unstake() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 20, // Sequence number + 1, + 2371, + 1670304949, + 120, + "", + Some(OpsDetails::LiquidStakingOps( + LiquidStakingOperation::Unstake(Unstake { + amount: 99178100, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }), + )), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001", // Expected raw transaction bytes + "6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4406994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "20", + "max_gas_amount": "2371", + "gas_unit_price": "120", + "expiration_timestamp_secs": "1670304949", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", + "type": "ed25519_signature" + } + }"#); +} + +// // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x9fc874de7a7d3e813d9a1658d896023de270a0096a5e258c196005656ace7d54?network=mainnet +#[test] +fn test_aptos_tortuga_claim() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 28, // Sequence number + 1, + 10, + 1682066783, + 148, + "", + Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Claim( + liquid_staking::Claim { + idx: 0, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }, + ))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001", // Expected raw transaction bytes + "c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc440c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "28", + "max_gas_amount": "10", + "gas_unit_price": "148", + "expiration_timestamp_secs": "1682066783", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::claim", + "type_arguments": [], + "arguments": [ + "0" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0xc936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet +#[test] +fn test_aptos_blind_sign() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 42, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "42", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/payload +#[test] +fn test_aptos_blind_sign_staking() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 43, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "43", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xa41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968/payload +#[test] +fn test_aptos_blind_sign_unstaking() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 44, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "44", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xa58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", + "type": "ed25519_signature" + } + }"#); +} diff --git a/rust/tw_bech32_address/Cargo.toml b/rust/tw_bech32_address/Cargo.toml new file mode 100644 index 00000000000..1ca0cfa493f --- /dev/null +++ b/rust/tw_bech32_address/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tw_bech32_address" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.163", features = [ "derive" ] } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_bech32_address/src/bech32_prefix.rs b/rust/tw_bech32_address/src/bech32_prefix.rs new file mode 100644 index 00000000000..8f6f418ed2c --- /dev/null +++ b/rust/tw_bech32_address/src/bech32_prefix.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::AddressError; +use tw_coin_entry::prefix::AddressPrefix; + +pub struct Bech32Prefix { + pub hrp: String, +} + +impl TryFrom for Bech32Prefix { + type Error = AddressError; + + fn try_from(prefix: AddressPrefix) -> Result { + match prefix { + AddressPrefix::Hrp(hrp) => Ok(Bech32Prefix { hrp }), + } + } +} diff --git a/rust/tw_bech32_address/src/lib.rs b/rust/tw_bech32_address/src/lib.rs new file mode 100644 index 00000000000..67c361f90a7 --- /dev/null +++ b/rust/tw_bech32_address/src/lib.rs @@ -0,0 +1,441 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::bech32_prefix::Bech32Prefix; +use serde::{Serialize, Serializer}; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_encoding::bech32; +use tw_hash::hasher::Hasher; +use tw_hash::H160; +use tw_keypair::tw::{PrivateKey, PublicKey, PublicKeyType}; +use tw_memory::Data; + +pub mod bech32_prefix; + +pub struct Bech32Address { + hrp: String, + key_hash: Data, + /// An address string created from this `hrp` and `key_hash`. + address_str: String, +} + +impl Bech32Address { + pub fn new(hrp: String, key_hash: Data) -> AddressResult { + let address_str = bech32::encode(&hrp, &key_hash).map_err(|_| AddressError::InvalidHrp)?; + Ok(Bech32Address { + hrp, + key_hash, + address_str, + }) + } + + pub fn with_public_key_hasher( + hrp: String, + public_key: &PublicKey, + hasher: Hasher, + ) -> AddressResult { + let public_key_bytes = match public_key.public_key_type() { + // If the public key is extended, skips the first byte (Evmos specific). + // https://github.com/trustwallet/wallet-core/blob/d67078daa580b37063c97be66a625aaee9664882/src/Bech32Address.cpp#L61-L64 + PublicKeyType::Secp256k1Extended => public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)? + .uncompressed_without_prefix() + .to_vec(), + _ => public_key.to_bytes(), + }; + + let public_key_hash = hasher.hash(&public_key_bytes); + if public_key_hash.len() < H160::LEN { + return Err(AddressError::UnexpectedHasher); + } + + let (_skipped_bytes, key_bytes) = + public_key_hash.split_at(public_key_hash.len() - H160::LEN); + Bech32Address::new(hrp, key_bytes.to_vec()) + } + + pub fn with_public_key_coin_context( + coin: &dyn CoinContext, + public_key: &PublicKey, + prefix: Option, + ) -> AddressResult { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidHrp)?, + }; + let address_hasher = coin + .address_hasher() + .ok_or(AddressError::UnexpectedHasher)?; + Self::with_public_key_hasher(hrp, public_key, address_hasher) + } + + pub fn with_private_key_coin_context( + coin: &dyn CoinContext, + private_key: &PrivateKey, + ) -> AddressResult { + let hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; + let public_key_type = coin.public_key_type(); + let public_key = private_key + .get_public_key_by_type(public_key_type) + .map_err(|_| AddressError::PublicKeyTypeMismatch)?; + let address_hasher = coin + .address_hasher() + .ok_or(AddressError::UnexpectedHasher)?; + Bech32Address::with_public_key_hasher(hrp, &public_key, address_hasher) + } + + pub fn from_str_checked( + expected_hrp: &str, + address_str: String, + ) -> AddressResult { + let bech32::Decoded { hrp, bytes } = + bech32::decode(&address_str).map_err(|_| AddressError::InvalidInput)?; + // Copied from the legacy Bech32Address.cpp: + // https://github.com/trustwallet/wallet-core/blob/d67078daa580b37063c97be66a625aaee9664882/src/Bech32Address.cpp#L21 + if !hrp.starts_with(expected_hrp) { + return Err(AddressError::InvalidHrp); + } + Ok(Bech32Address { + hrp, + key_hash: bytes, + address_str, + }) + } + + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + address_str: String, + prefix: Option, + ) -> AddressResult + where + Self: Sized, + { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidHrp)?, + }; + Self::from_str_checked(&hrp, address_str) + } + + pub fn key_hash(&self) -> &[u8] { + &self.key_hash + } + + pub fn hrp(&self) -> &str { + &self.hrp + } +} + +impl CoinAddress for Bech32Address { + fn data(&self) -> Data { + self.key_hash.to_vec() + } +} + +impl FromStr for Bech32Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let bech32::Decoded { hrp, bytes } = + bech32::decode(s).map_err(|_| AddressError::InvalidInput)?; + Ok(Bech32Address { + hrp, + key_hash: bytes, + address_str: s.to_string(), + }) + } +} + +impl fmt::Display for Bech32Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.address_str) + } +} + +impl fmt::Debug for Bech32Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl Serialize for Bech32Address { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex::{DecodeHex, ToHex}; + + struct FromPublicKeyTestInput<'a> { + hrp: &'a str, + private_key: &'a str, + public_key_type: PublicKeyType, + hasher: Hasher, + expected: &'a str, + } + + fn test_from_public_key(input: FromPublicKeyTestInput<'_>) { + let private_key = PrivateKey::new(input.private_key.decode_hex().unwrap()).unwrap(); + let public_key = private_key + .get_public_key_by_type(input.public_key_type) + .unwrap(); + let actual = + Bech32Address::with_public_key_hasher(input.hrp.to_string(), &public_key, input.hasher) + .unwrap(); + assert_eq!(actual.to_string(), input.expected); + } + + #[test] + fn test_address_from_str_checked_valid() { + fn test_impl(addr: &str, hrp: &str) { + Bech32Address::from_str_checked(hrp, addr.to_string()) + .unwrap_or_else(|e| panic!("ERROR={:?}: hrp={} addr={}", e, hrp, addr)); + } + + test_impl("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", "bnb"); + + test_impl("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", "cosmos"); + test_impl( + "cosmospub1addwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq", + "cosmos", + ); + test_impl( + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmos", + ); + test_impl( + "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + "cosmos", + ); + + test_impl("one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", "one"); + test_impl("one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p", "one"); + + test_impl("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", "io"); + + test_impl("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7", "zil"); + + test_impl( + "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + "erd", + ); + test_impl( + "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "erd", + ); + test_impl( + "erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", + "erd", + ); + + // uppercase version + test_impl("BNB1GRPF0955H0YKZQ3AR5NMUM7Y6GDFL6LXFN46H2", "bnb"); + } + + #[test] + fn test_address_from_str_checked_invalid() { + fn test_impl(addr: &str, hrp: &str) { + Bech32Address::from_str_checked(hrp, addr.to_string()) + .expect_err(&format!("hrp={} addr={}", hrp, addr)); + } + + // 1-char diff + test_impl("bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2", "bnb"); + // mixed case + test_impl("bnb1grPF0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", "bnb"); + + test_impl("cosmos1xsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", "cosmos"); + test_impl( + "cosmospub1xddwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq", + "cosmos", + ); + test_impl( + "cosmosvaloper1xxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmos", + ); + test_impl( + "cosmosvalconspub1xcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + "cosmos", + ); + + test_impl("one1a50tun737ulcvwy0yvve0pe", "one"); + test_impl("oe1tp7xdd9ewwnmyvws96au0ee7e7mz6f8hjqr3g3p", "one"); + + test_impl("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8", "io"); + test_impl("io187wzp08vnhjpkydnr97qlh8kh0dpkkytfam8j", "io"); + test_impl("it187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", "io"); + + test_impl("", "erd"); + test_impl( + "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35!", + "erd", + ); + test_impl( + "xerd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", + "erd", + ); + } + + #[test] + fn test_decode() { + fn test_impl(addr: &str, hrp: &str, expected_hash: &str) { + let actual = Bech32Address::from_str_checked(hrp, addr.to_string()).unwrap(); + assert_eq!(actual.key_hash.to_hex(), expected_hash); + } + + test_impl( + "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + "one", + "ed1ebe4fd1f73f86388f231997859ca42c07da5d", + ); + + test_impl( + "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", + "io", + "3f9c20bcec9de520d88d98cbe07ee7b5ded0dac4", + ); + } + + #[test] + fn test_from_hrp_and_hash() { + fn test_impl(hrp: &str, key_hash: &str, expected_addr: &str) { + let actual = + Bech32Address::new(hrp.to_string(), key_hash.decode_hex().unwrap()).unwrap(); + assert_eq!(actual.to_string(), expected_addr); + } + + test_impl( + "bnb", + "b6561dcc104130059a7c08f48c64610c1f6f9064", + "bnb1ketpmnqsgycqtxnupr6gcerpps0klyryuudz05", + ); + test_impl( + "one", + "587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2", + "one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p", + ); + test_impl( + "zil", + "0x91cdDcEBE846ce4d47712287EEe53cF17c2cfB77", + "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", + ); + test_impl( + "zil", + "1d19918a737306218b5cbb3241fcdcbd998c3a72", + "zil1r5verznnwvrzrz6uhveyrlxuhkvccwnju4aehf", + ); + } + + #[test] + fn test_from_hrp_and_public_key_hasher() { + test_from_public_key(FromPublicKeyTestInput { + hrp: "bnb", + private_key: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "cosmos", + private_key: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "one", + private_key: "e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94", + public_key_type: PublicKeyType::Secp256k1Extended, + hasher: Hasher::Keccak256, + expected: "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "io", + private_key: "0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f", + public_key_type: PublicKeyType::Secp256k1Extended, + hasher: Hasher::Keccak256, + expected: "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "zil", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256, + expected: "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", + }); + } + + /// From same public key, but different hashes: different results. + #[test] + fn test_different_hashes() { + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrp", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrp186zwn9h0z9fyvwfqs4jl92cw3kexusm4xw6ptp", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrp", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256, + expected: "hrp1j8xae6lggm8y63m3y2r7aefu797ze7mhgfetvu", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrp", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1Extended, + hasher: Hasher::Keccak256, + expected: "hrp17hff3s97m5uxpjcdq3nzqxxatt8cmumnsf03su", + }); + } + + /// From same public key, but different prefixes: different results (checksum). + #[test] + fn test_different_hrp() { + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrpone", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrpone186zwn9h0z9fyvwfqs4jl92cw3kexusm47das6p", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrptwo", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrptwo186zwn9h0z9fyvwfqs4jl92cw3kexusm4qzr8p7", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrpthree", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrpthree186zwn9h0z9fyvwfqs4jl92cw3kexusm4wuqkvd", + }); + } +} diff --git a/rust/tw_bitcoin/Cargo.toml b/rust/tw_bitcoin/Cargo.toml index 82ef70900ab..451491eb345 100644 --- a/rust/tw_bitcoin/Cargo.toml +++ b/rust/tw_bitcoin/Cargo.toml @@ -3,14 +3,18 @@ name = "tw_bitcoin" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bitcoin = "0.30.0" secp256k1 = { version = "0.27.0", features = [ "global-context", "rand-std" ] } serde = { version = "1.0.163", features = [ "derive" ] } serde_json = "1.0.96" +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_utxo = { path = "../tw_utxo" } tw_encoding = { path = "../tw_encoding" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto" } +tw_keypair = { path = "../tw_keypair" } + +[dev-dependencies] +wallet-core-rs = { path = "../wallet_core_rs" } diff --git a/rust/tw_bitcoin/src/brc20.rs b/rust/tw_bitcoin/src/brc20.rs deleted file mode 100644 index e2070059c2b..00000000000 --- a/rust/tw_bitcoin/src/brc20.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::ordinals::OrdinalsInscription; -use crate::{Error, Recipient, Result}; -use bitcoin::PublicKey; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BRC20Payload { - #[serde(rename = "p")] - protocol: String, - #[serde(rename = "op")] - operation: String, - #[serde(flatten)] - inner: T, -} - -impl BRC20Payload { - const PROTOCOL_ID: &str = "brc-20"; - const MIME: &[u8] = b"text/plain;charset=utf-8"; -} - -// Convenience aliases. -pub type BRC20DeployPayload = BRC20Payload; -pub type BRC20MintPayload = BRC20Payload; -pub type BRC20TransferPayload = BRC20Payload; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Ticker(String); - -impl Ticker { - pub fn new(string: String) -> Result { - // Ticker must be a 4-letter identifier. - if string.len() != 4 { - return Err(Error::Todo); - } - - Ok(Ticker(string)) - } - pub fn to_byte_array(&self) -> [u8; 4] { - self.0 - .as_bytes() - .try_into() - .expect("ticker must be four bytes") - } -} - -impl TryFrom for Ticker { - type Error = Error; - - fn try_from(string: String) -> Result { - Self::new(string) - } -} - -impl BRC20DeployPayload { - const OPERATION: &str = "deploy"; - - pub fn new(ticker: Ticker, max: usize, limit: Option, decimals: Option) -> Self { - BRC20Payload { - protocol: Self::PROTOCOL_ID.to_string(), - operation: Self::OPERATION.to_string(), - inner: DeployPayload { - tick: ticker, - max: max.to_string(), - lim: limit.map(|l| l.to_string()), - dec: decimals.map(|d| d.to_string()), - }, - } - } -} - -impl BRC20TransferPayload { - const OPERATION: &str = "transfer"; - - pub fn new(ticker: Ticker, amount: u64) -> Self { - BRC20Payload { - protocol: Self::PROTOCOL_ID.to_string(), - operation: Self::OPERATION.to_string(), - inner: TransferPayload { - tick: ticker, - amt: amount.to_string(), - }, - } - } -} - -impl BRC20MintPayload { - const OPERATION: &str = "mint"; - - pub fn new(ticker: Ticker, amount: u64) -> Self { - BRC20Payload { - protocol: Self::PROTOCOL_ID.to_string(), - operation: Self::OPERATION.to_string(), - inner: MintPayload { - tick: ticker, - amt: amount.to_string(), - }, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DeployPayload { - pub tick: Ticker, - pub max: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub lim: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub dec: Option, -} - -#[derive(Debug, Clone)] -pub struct BRC20DeployInscription(OrdinalsInscription); - -impl BRC20DeployInscription { - pub fn new( - recipient: Recipient, - ticker: Ticker, - max: usize, - limit: Option, - decimals: Option, - ) -> Result { - let data = BRC20DeployPayload::new(ticker, max, limit, decimals); - - Self::from_payload(data, recipient) - } - pub fn from_payload( - data: BRC20DeployPayload, - recipient: Recipient, - ) -> Result { - let inscription = OrdinalsInscription::new( - BRC20Payload::::MIME, - &serde_json::to_vec(&data).unwrap(), - recipient, - )?; - - Ok(BRC20DeployInscription(inscription)) - } - pub fn inscription(&self) -> &OrdinalsInscription { - &self.0 - } -} - -#[derive(Serialize, Deserialize)] -pub struct TransferPayload { - pub tick: Ticker, - pub amt: String, -} - -pub struct BRC20TransferInscription(OrdinalsInscription); - -impl BRC20TransferInscription { - pub fn new( - recipient: Recipient, - ticker: Ticker, - amount: u64, - ) -> Result { - let data = BRC20TransferPayload::new(ticker, amount); - Self::from_payload(data, recipient) - } - pub fn from_payload( - data: BRC20TransferPayload, - recipient: Recipient, - ) -> Result { - let inscription = OrdinalsInscription::new( - BRC20Payload::::MIME, - &serde_json::to_vec(&data).unwrap(), - recipient, - )?; - - Ok(BRC20TransferInscription(inscription)) - } - pub fn inscription(&self) -> &OrdinalsInscription { - &self.0 - } -} - -/// The structure is the same as `TransferPayload`, but we'll keep it separate -/// for clarity. -#[derive(Serialize, Deserialize)] -pub struct MintPayload { - pub tick: Ticker, - pub amt: String, -} - -pub struct BRC20MintInscription(OrdinalsInscription); - -impl BRC20MintInscription { - pub fn new( - recipient: Recipient, - ticker: Ticker, - amount: u64, - ) -> Result { - let data = BRC20MintPayload::new(ticker, amount); - Self::from_payload(data, recipient) - } - pub fn from_payload( - data: BRC20MintPayload, - recipient: Recipient, - ) -> Result { - let inscription = OrdinalsInscription::new( - BRC20Payload::::MIME, - &serde_json::to_vec(&data).unwrap(), - recipient, - )?; - - Ok(BRC20MintInscription(inscription)) - } - pub fn inscription(&self) -> &OrdinalsInscription { - &self.0 - } -} diff --git a/rust/tw_bitcoin/src/claim.rs b/rust/tw_bitcoin/src/claim.rs deleted file mode 100644 index 02edc331ec5..00000000000 --- a/rust/tw_bitcoin/src/claim.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::{ - Error, Recipient, Result, TaprootScript, TxInputP2PKH, TxInputP2TRKeyPath, - TxInputP2TRScriptPath, TxInputP2WPKH, -}; -use bitcoin::key::{KeyPair, PublicKey, TapTweak, TweakedKeyPair, TweakedPublicKey}; -use bitcoin::secp256k1::Secp256k1; -use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; -use bitcoin::taproot::{LeafVersion, Signature}; -use bitcoin::{ScriptBuf, Witness}; - -#[derive(Debug, Clone)] -pub enum ClaimLocation { - Script(ScriptBuf), - Witness(Witness), -} - -pub trait TransactionSigner { - /// Claiming mechanism for (legacy) P2PKH outputs. - fn claim_p2pkh( - &self, - input: &TxInputP2PKH, - sighash: secp256k1::Message, - sighash_type: EcdsaSighashType, - ) -> Result; - /// Claiming mechanism for SegWit P2WPKH outputs. - fn claim_p2wpkh( - &self, - input: &TxInputP2WPKH, - sighash: secp256k1::Message, - sighash_type: EcdsaSighashType, - ) -> Result; - /// Claiming mechanism for Taproot P2TR key-path outputs. - fn claim_p2tr_key_path( - &self, - input: &TxInputP2TRKeyPath, - sighash: secp256k1::Message, - sighash_type: TapSighashType, - ) -> Result; - /// Claiming mechanism for Taproot P2TR script-path outputs. - fn claim_p2tr_script_path( - &self, - input: &TxInputP2TRScriptPath, - sighash: secp256k1::Message, - sighash_type: TapSighashType, - ) -> Result; -} - -// Contains the `scriptBuf` that must be included in the transaction when -// spending the P2PKH input. -pub struct ClaimP2PKH(pub ScriptBuf); - -// Contains the Witness that must be included in the transaction when spending -// the SegWit P2WPKH input. -pub struct ClaimP2WPKH(pub Witness); - -// Contains the Witness that must be included in the transaction when spending -// the Taproot P2TR key-path input. -pub struct ClaimP2TRKeyPath(pub Witness); - -// Contains the Witness that must be included in the transaction when spending -// the Taproot P2TR script-path input. -pub struct ClaimP2TRScriptPath(pub Witness); - -impl TransactionSigner for KeyPair { - fn claim_p2pkh( - &self, - input: &TxInputP2PKH, - sighash: secp256k1::Message, - sighash_type: EcdsaSighashType, - ) -> Result { - let me = Recipient::::from_keypair(self); - - // Check whether we can actually claim the input. - if input.recipient().pubkey_hash() != &me.pubkey_hash() { - return Err(Error::Todo); - } - - // Construct the ECDSA signature. - let sig = bitcoin::ecdsa::Signature { - sig: self.secret_key().sign_ecdsa(sighash), - hash_ty: sighash_type, - }; - - // Construct the Script for claiming. - let script = ScriptBuf::builder() - .push_slice(sig.serialize()) - .push_key(&me.public_key()) - .into_script(); - - Ok(ClaimP2PKH(script)) - } - fn claim_p2wpkh( - &self, - input: &TxInputP2WPKH, - sighash: secp256k1::Message, - sighash_type: EcdsaSighashType, - ) -> Result { - let me = Recipient::::from_keypair(self); - - if input.recipient().wpubkey_hash() != &me.wpubkey_hash()? { - return Err(Error::Todo); - } - - // Construct the ECDSA signature. - let sig = bitcoin::ecdsa::Signature { - sig: self.secret_key().sign_ecdsa(sighash), - hash_ty: sighash_type, - }; - - // Construct the Witness for claiming. - let mut witness = Witness::new(); - witness.push(sig.serialize()); - // Serialize public key. - witness.push(me.public_key().to_bytes()); - - Ok(ClaimP2WPKH(witness)) - } - fn claim_p2tr_key_path( - &self, - input: &TxInputP2TRKeyPath, - sighash: secp256k1::Message, - sighash_type: TapSighashType, - ) -> Result { - let me = Recipient::::from(self); - - // Check whether we can actually claim the input. - if input.recipient() != &me { - return Err(Error::Todo); - } - - let secp = Secp256k1::new(); - - // Tweak keypair for P2TR key-path (ie. zeroed Merkle root). - let tapped: TweakedKeyPair = self.tap_tweak(&secp, None); - let tweaked = KeyPair::from(tapped); - - // Construct the Schnorr signature. - #[cfg(not(test))] - let schnorr = secp.sign_schnorr(&sighash, &tweaked); - #[cfg(test)] - // For tests, we disable the included randomness in order to create - // reproducible signatures. Randomness should ALWAYS be used in - // production. - let schnorr = secp.sign_schnorr_no_aux_rand(&sighash, &tweaked); - - let sig = bitcoin::taproot::Signature { - sig: schnorr, - hash_ty: sighash_type, - }; - - // Construct the witness for claiming. - let mut witness = Witness::new(); - witness.push(sig.to_vec()); - - Ok(ClaimP2TRKeyPath(witness)) - } - fn claim_p2tr_script_path( - &self, - input: &TxInputP2TRScriptPath, - sighash: secp256k1::Message, - sighash_type: TapSighashType, - ) -> Result { - // Tweak our public key with the Merkle root of the Script to be claimed. - let me = Recipient::::from_keypair(self, input.recipient().merkle_root()); - - // Check whether we can actually claim the input. - if input.recipient() != &me { - return Err(Error::Todo); - } - - // The control block contains information on which script of the - // script-path is being executed. - let control_block = input - .spend_info() - .control_block(&(input.witness().clone(), LeafVersion::TapScript)) - .ok_or(Error::Todo)?; - - // Construct the Schnorr signature. We leave the keypair untweaked, - // unlike for key-path. - let sig = Signature { - sig: Secp256k1::new().sign_schnorr(&sighash, self), - hash_ty: sighash_type, - }; - - // Construct the Witness for claiming. - let mut witness = Witness::new(); - // Serialize signature. - witness.push(&sig.to_vec()); - witness.push(input.witness()); - witness.push(control_block.serialize()); - - Ok(ClaimP2TRScriptPath(witness)) - } -} diff --git a/rust/tw_bitcoin/src/entry.rs b/rust/tw_bitcoin/src/entry.rs new file mode 100644 index 00000000000..a847c4a0690 --- /dev/null +++ b/rust/tw_bitcoin/src/entry.rs @@ -0,0 +1,358 @@ +use crate::modules::plan_builder::BitcoinPlanBuilder; +use crate::modules::signer::Signer; +use crate::{Error, Result}; +use bitcoin::address::NetworkChecked; +use std::borrow::Cow; +use std::fmt::Display; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinAddress, CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::prefix::NoPrefix; +use tw_coin_entry::signing_output_error; +use tw_keypair::tw::PublicKey; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +pub struct Address(pub bitcoin::address::Address); + +impl Display for Address { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl CoinAddress for Address { + fn data(&self) -> tw_memory::Data { + self.0.to_string().into_bytes() + } +} + +pub struct BitcoinEntry; + +impl CoinEntry for BitcoinEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = Proto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = BitcoinPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + self.parse_address_unchecked(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + let address = bitcoin::address::Address::from_str(address) + .map_err(|_| AddressError::FromHexError)? + // At this moment, we support mainnet only. + // This check will be removed in coming PRs. + .require_network(bitcoin::Network::Bitcoin) + .map_err(|_| AddressError::InvalidInput)?; + + Ok(Address(address)) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let pubkey = match public_key { + PublicKey::Secp256k1(pubkey) | PublicKey::Secp256k1Extended(pubkey) => pubkey, + _ => return Err(AddressError::InvalidInput), + }; + + let pubkey = bitcoin::PublicKey::from_slice(pubkey.to_vec().as_ref()) + .map_err(|_| AddressError::InvalidInput)?; + + let address: bitcoin::address::Address = bitcoin::address::Address::new( + bitcoin::Network::Bitcoin, + bitcoin::address::Payload::PubkeyHash(pubkey.pubkey_hash()), + ); + + Ok(Address(address)) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, proto: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::sign_proto(_coin, proto) + .unwrap_or_else(|err| signing_output_error!(Proto::SigningOutput, err)) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + ) -> Self::PreSigningOutput { + self.preimage_hashes_impl(_coin, proto) + .unwrap_or_else(|err| signing_output_error!(Proto::PreSigningOutput, err)) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + signatures: Vec, + _public_keys: Vec, + ) -> Self::SigningOutput { + self.compile_impl(_coin, proto, signatures, _public_keys) + .unwrap_or_else(|err| signing_output_error!(Proto::SigningOutput, err)) + } + + #[inline] + fn plan_builder(&self) -> Option { + Some(BitcoinPlanBuilder) + } +} + +impl BitcoinEntry { + pub(crate) fn preimage_hashes_impl( + &self, + _coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + ) -> Result> { + let proto = pre_processor(proto); + + // Convert input builders into Utxo inputs. + let utxo_inputs = proto + .inputs + .iter() + .map(crate::modules::transactions::InputBuilder::utxo_from_proto) + .collect::>>()?; + + // Convert output builders into Utxo outputs. + let mut utxo_outputs = proto + .outputs + .iter() + .map(crate::modules::transactions::OutputBuilder::utxo_from_proto) + .collect::>>()?; + + // If automatic change output is enabled, a change script must be provided. + let change_script_pubkey = if proto.disable_change_output { + Cow::default() + } else { + // Convert output builder to Utxo output. + let output = crate::modules::transactions::OutputBuilder::utxo_from_proto( + &proto + .change_output + .ok_or_else(|| Error::from(Proto::Error::Error_invalid_change_output))?, + )?; + + output.script_pubkey + }; + + // Prepare SigningInput for Utxo sighash generation. + let utxo_signing = UtxoProto::SigningInput { + version: proto.version, + lock_time: proto.lock_time, + inputs: utxo_inputs.clone(), + outputs: utxo_outputs + .iter() + .map(|output| UtxoProto::TxOut { + value: output.value, + script_pubkey: Cow::Borrowed(&output.script_pubkey), + }) + .collect(), + input_selector: proto.input_selector, + weight_base: proto.fee_per_vb, + change_script_pubkey, + disable_change_output: proto.disable_change_output, + }; + + // Generate the sighashes to be signed. + let utxo_presigning = tw_utxo::compiler::Compiler::preimage_hashes(utxo_signing); + handle_utxo_error(&utxo_presigning.error)?; + + // If a change output was created by the Utxo compiler, we return it here too. + if utxo_presigning.outputs.len() == utxo_outputs.len() + 1 { + let change_output = utxo_presigning + .outputs + .last() + .expect("expected change output"); + + utxo_outputs.push(Proto::mod_PreSigningOutput::TxOut { + value: change_output.value, + script_pubkey: change_output.script_pubkey.to_vec().into(), + control_block: Default::default(), + taproot_payload: Default::default(), + }) + } + + Ok(Proto::PreSigningOutput { + error: Proto::Error::OK, + error_message: Default::default(), + txid: utxo_presigning.txid, + sighashes: utxo_presigning.sighashes, + // Update selected inputs. + utxo_inputs: utxo_presigning.inputs, + utxo_outputs, + weight_estimate: utxo_presigning.weight_estimate, + fee_estimate: utxo_presigning.fee_estimate, + }) + } + + pub(crate) fn compile_impl( + &self, + _coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + signatures: Vec, + _public_keys: Vec, + ) -> Result> { + let proto = pre_processor(proto); + + // There must be a signature for each input. + if proto.inputs.len() != signatures.len() { + return Err(Error::from( + Proto::Error::Error_unmatched_input_signature_count, + )); + } + + // Generate claims for all the inputs. + let mut utxo_input_claims: Vec = vec![]; + for (input, signature) in proto.inputs.iter().zip(signatures.into_iter()) { + let utxo_claim = + crate::modules::transactions::InputClaimBuilder::utxo_claim_from_proto( + input, signature, + )?; + utxo_input_claims.push(utxo_claim); + } + + // Process all the outputs. + let mut utxo_outputs = vec![]; + for output in proto.outputs { + let utxo = crate::modules::transactions::OutputBuilder::utxo_from_proto(&output)?; + + utxo_outputs.push(utxo); + } + + // Prepare PreSerialization input for Utxo compiler. + let utxo_preserializtion = UtxoProto::PreSerialization { + version: proto.version, + lock_time: proto.lock_time.clone(), + inputs: utxo_input_claims.clone(), + outputs: utxo_outputs + .iter() + .map(|out| UtxoProto::TxOut { + value: out.value, + script_pubkey: Cow::Borrowed(&out.script_pubkey), + }) + .collect(), + weight_base: proto.fee_per_vb, + }; + + // Compile the transaction, build the final encoded transaction + // containing the signatures/scriptSigs/witnesses. + let utxo_serialized = tw_utxo::compiler::Compiler::compile(utxo_preserializtion); + handle_utxo_error(&utxo_serialized.error)?; + + // Prepare `Proto::TransactionInput` protobufs for signing output. + let mut proto_inputs = vec![]; + for input in utxo_input_claims { + proto_inputs.push(Proto::TransactionInput { + txid: Cow::Owned(input.txid.to_vec()), + vout: input.vout, + sequence: input.sequence, + script_sig: Cow::Owned(input.script_sig.into_owned()), + witness_items: input + .witness_items + .into_iter() + .map(|item| Cow::Owned(item.into_owned())) + .collect(), + }); + } + + // Prepare `Proto::TransactionOutput` protobufs for output. + let mut proto_outputs = vec![]; + for output in utxo_outputs { + proto_outputs.push(Proto::TransactionOutput { + script_pubkey: output.script_pubkey, + value: output.value, + taproot_payload: output.taproot_payload, + control_block: output.control_block, + }); + } + + // Prepare `Proto::Transaction` protobuf for output. + let transaction = Proto::Transaction { + version: proto.version, + lock_time: proto.lock_time, + inputs: proto_inputs, + outputs: proto_outputs, + }; + + // Return the full protobuf output. + Ok(Proto::SigningOutput { + error: Proto::Error::OK, + error_message: Default::default(), + transaction: Some(transaction), + encoded: utxo_serialized.encoded, + txid: utxo_serialized.txid, + weight: utxo_serialized.weight, + fee: utxo_serialized.fee, + }) + } +} + +// Convenience function for pre-processing of certain fields that must be +// executed on each `CoinEntry` call. +pub(crate) fn pre_processor(mut proto: Proto::SigningInput<'_>) -> Proto::SigningInput<'_> { + // We automatically set the transaction version to 2. + if proto.version == 0 { + proto.version = 2; + } + + // If an input sequence (timelock, replace-by-fee, etc) of zero is not + // expliclity enabled, we interpreted a sequence of zero as the max value + // (default). + proto.inputs.iter_mut().for_each(|txin| { + if !txin.sequence_enable_zero && txin.sequence == 0 { + txin.sequence = u32::MAX + } + }); + + proto +} + +#[rustfmt::skip] +/// Convert `Utxo.proto` error type to `BitcoinV2.proto` error type. +fn handle_utxo_error(utxo_err: &UtxoProto::Error) -> Result<()> { + let bitcoin_err = match utxo_err { + UtxoProto::Error::OK => return Ok(()), + UtxoProto::Error::Error_invalid_leaf_hash => Proto::Error::Error_utxo_invalid_leaf_hash, + UtxoProto::Error::Error_invalid_sighash_type => Proto::Error::Error_utxo_invalid_sighash_type, + UtxoProto::Error::Error_invalid_lock_time => Proto::Error::Error_utxo_invalid_lock_time, + UtxoProto::Error::Error_invalid_txid => Proto::Error::Error_utxo_invalid_txid, + UtxoProto::Error::Error_sighash_failed => Proto::Error::Error_utxo_sighash_failed, + UtxoProto::Error::Error_missing_sighash_method => Proto::Error::Error_utxo_missing_sighash_method, + UtxoProto::Error::Error_failed_encoding => Proto::Error::Error_utxo_failed_encoding, + UtxoProto::Error::Error_insufficient_inputs => Proto::Error::Error_utxo_insufficient_inputs, + UtxoProto::Error::Error_missing_change_script_pubkey => Proto::Error::Error_utxo_missing_change_script_pubkey, + }; + + Err(Error::from(bitcoin_err)) +} diff --git a/rust/tw_bitcoin/src/ffi/address.rs b/rust/tw_bitcoin/src/ffi/address.rs deleted file mode 100644 index 63c34dc31fc..00000000000 --- a/rust/tw_bitcoin/src/ffi/address.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::CTaprootError; -use crate::Recipient; -use bitcoin::PublicKey; -use std::ffi::CString; -use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; -use tw_memory::ffi::c_result::CStrMutResult; - -#[no_mangle] -pub unsafe extern "C" fn tw_legacy_address_string( - pubkey: *const u8, - pubkey_len: usize, - network: Network, -) -> CStrMutResult { - // Convert Recipient. - let Some(slice) = CByteArrayRef::new(pubkey, pubkey_len).as_slice() else { - return CStrMutResult::error(CTaprootError::InvalidSlice); - }; - - let Ok(recipient) = Recipient::::from_slice(slice) else { - return CStrMutResult::error(CTaprootError::InvalidPubkey); - }; - - let address = recipient.legacy_address_string(network.into()); - let c_string = CString::new(address) - .expect("legacy address contains an internal 0 byte") - .into_raw(); - - CStrMutResult::ok(c_string) -} - -#[no_mangle] -pub unsafe extern "C" fn tw_segwit_address_string( - pubkey: *const u8, - pubkey_len: usize, - network: Network, -) -> CStrMutResult { - // Convert Recipient. - let Some(slice) = CByteArrayRef::new(pubkey, pubkey_len).as_slice() else { - return CStrMutResult::error(CTaprootError::InvalidSlice); - }; - - let Ok(recipient) = Recipient::::from_slice(slice) else { - return CStrMutResult::error(CTaprootError::InvalidPubkey); - }; - - let Ok(address) = recipient.segwit_address_string(network.into()) else { - return CStrMutResult::error(CTaprootError::InvalidSegwitPukey); - }; - - let c_string = CString::new(address) - .expect("legacy address contains an internal 0 byte") - .into_raw(); - - CStrMutResult::ok(c_string) -} - -#[no_mangle] -pub unsafe extern "C" fn tw_taproot_address_string( - pubkey: *const u8, - pubkey_len: usize, - network: Network, -) -> CStrMutResult { - // Convert Recipient. - let Some(slice) = CByteArrayRef::new(pubkey, pubkey_len).as_slice() else { - return CStrMutResult::error(CTaprootError::InvalidSlice); - }; - - let Ok(recipient) = Recipient::::from_slice(slice) else { - return CStrMutResult::error(CTaprootError::InvalidPubkey); - }; - - let address = recipient.taproot_address_string(network.into()); - let c_string = CString::new(address) - .expect("legacy address contains an internal 0 byte") - .into_raw(); - - CStrMutResult::ok(c_string) -} - -// A custom reimplementation of of `bitcoin::Network`. -#[repr(C)] -pub enum Network { - Bitcoin = 0, - Testnet = 1, - Signet = 2, - Regtest = 3, -} - -impl From for bitcoin::Network { - fn from(n: Network) -> Self { - match n { - Network::Bitcoin => bitcoin::Network::Bitcoin, - Network::Testnet => bitcoin::Network::Testnet, - Network::Signet => bitcoin::Network::Signet, - Network::Regtest => bitcoin::Network::Regtest, - } - } -} diff --git a/rust/tw_bitcoin/src/ffi/mod.rs b/rust/tw_bitcoin/src/ffi/mod.rs deleted file mode 100644 index 4b6f8988555..00000000000 --- a/rust/tw_bitcoin/src/ffi/mod.rs +++ /dev/null @@ -1,279 +0,0 @@ -#![allow(clippy::missing_safety_doc)] - -use crate::{ - calculate_fee, Error, Result, TXOutputP2TRScriptPath, TaprootScript, TxInputP2TRScriptPath, -}; -use bitcoin::{ - consensus::Decodable, - taproot::{NodeInfo, TapNodeHash, TaprootSpendInfo}, - PublicKey, ScriptBuf, Transaction, Txid, -}; -use secp256k1::hashes::Hash; -use secp256k1::KeyPair; -use std::borrow::Cow; -use tw_memory::ffi::c_byte_array::CByteArray; -use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; -use tw_memory::ffi::c_result::CUInt64Result; -use tw_memory::ffi::c_result::ErrorCode; -use tw_misc::try_or_else; -use tw_proto::Bitcoin::Proto::{ - OutPoint, SigningInput, SigningOutput, Transaction as ProtoTransaction, TransactionInput, - TransactionOutput, TransactionVariant as TrVariant, -}; - -pub mod address; -pub mod scripts; - -// Re-exports -pub use address::*; -pub use scripts::*; - -use crate::{ - Recipient, TransactionBuilder, TxInput, TxInputP2PKH, TxInputP2TRKeyPath, TxInputP2WPKH, - TxOutput, TxOutputP2PKH, TxOutputP2TRKeyPath, TxOutputP2WPKH, -}; - -#[no_mangle] -pub unsafe extern "C" fn tw_bitcoin_calculate_transaction_fee( - input: *const u8, - input_len: usize, - sat_vb: u64, -) -> CUInt64Result { - let Some(mut encoded) = CByteArrayRef::new(input, input_len).as_slice() else { - return CUInt64Result::error(1); - }; - - // Decode transaction. - let Ok(tx) = Transaction::consensus_decode(&mut encoded) else { - return CUInt64Result::error(1); - }; - - // Calculate fee. - let (_weight, fee) = calculate_fee(&tx, sat_vb); - - CUInt64Result::ok(fee) -} - -#[no_mangle] -pub unsafe extern "C" fn tw_taproot_build_and_sign_transaction( - input: *const u8, - input_len: usize, -) -> CByteArray { - let data = CByteArrayRef::new(input, input_len) - .to_vec() - .unwrap_or_default(); - - let proto: SigningInput = try_or_else!(tw_proto::deserialize(&data), CByteArray::null); - let signing = try_or_else!(taproot_build_and_sign_transaction(proto), CByteArray::null); - - let serialized = tw_proto::serialize(&signing).expect("failed to serialize signed transaction"); - - CByteArray::from(serialized) -} - -/// Note: many of the fields used in the `SigningInput` are currently unused. We -/// can later easily replicate the funcationlity and behavior of the C++ -/// implemenation. -/// -/// Additionally, the `SigningInput` supports two ways of operating (which -/// should probably be separated anyway): one way where the `TransactionPlan` is -/// skipped (and hence automatically constructed) and the other way where the -/// `TransactionPlan` is created manually. As of now, it's expected that the -/// `TransactionPlan` is created manually, meaning that the caller must careful -/// construct the outputs, which must include the return/change transaction and -/// how much goes to the miner as fee ( minus -/// ). -pub(crate) fn taproot_build_and_sign_transaction(proto: SigningInput) -> Result { - let privkey = proto.private_key.get(0).ok_or(Error::Todo)?; - - // Prepare keypair and derive corresponding public key. - let keypair = KeyPair::from_seckey_slice(&secp256k1::Secp256k1::new(), privkey.as_ref()) - .map_err(|_| crate::Error::Todo)?; - - let my_pubkey = Recipient::::from(keypair); - - let mut builder = TransactionBuilder::new(); - - // Process inputs. - for input in proto.utxo { - let my_pubkey = my_pubkey.clone(); - - let out_point = input.out_point.ok_or(Error::Todo)?; - let txid = Txid::from_slice(&out_point.hash).map_err(|_| crate::Error::Todo)?; - let vout = out_point.index; - let satoshis = input.amount as u64; - - let script_buf = ScriptBuf::from_bytes(input.script.to_vec()); - - let tx: TxInput = match input.variant { - TrVariant::P2PKH => { - TxInputP2PKH::new_with_script(txid, vout, my_pubkey.into(), satoshis, script_buf) - .into() - }, - TrVariant::P2WPKH => TxInputP2WPKH::new_with_script( - txid, - vout, - my_pubkey.try_into()?, - satoshis, - script_buf, - ) - .into(), - TrVariant::P2TRKEYPATH => TxInputP2TRKeyPath::new_with_script( - txid, - vout, - my_pubkey.into(), - satoshis, - script_buf, - ) - .into(), - TrVariant::BRC20TRANSFER | TrVariant::NFTINSCRIPTION => { - // We construct the merkle root for the given spending script. - let spending_script = ScriptBuf::from_bytes(input.spendingScript.to_vec()); - let merkle_root = TapNodeHash::from_script( - spending_script.as_script(), - bitcoin::taproot::LeafVersion::TapScript, - ); - - // Convert to tapscript recipient with the given merkle root. - let recipient = - Recipient::::from_pubkey_recipient(my_pubkey, merkle_root); - - // Derive the spending information for the taproot recipient. - let spend_info = TaprootSpendInfo::from_node_info( - &secp256k1::Secp256k1::new(), - recipient.untweaked_pubkey(), - NodeInfo::new_leaf_with_ver( - spending_script.clone(), - bitcoin::taproot::LeafVersion::TapScript, - ), - ); - - TxInputP2TRScriptPath::new_with_script( - txid, - vout, - recipient, - satoshis, - script_buf, - spending_script, - spend_info, - ) - .into() - }, - }; - - builder = builder.add_input(tx); - } - - // Process outputs. - for output in proto.plan.ok_or(Error::Todo)?.utxos { - let script_buf = ScriptBuf::from_bytes(output.script.to_vec()); - let satoshis = output.amount as u64; - - #[rustfmt::skip] - let tx: TxOutput = match output.variant { - TrVariant::P2PKH => { - TxOutputP2PKH::new_with_script(satoshis, script_buf).into() - }, - TrVariant::P2WPKH => { - TxOutputP2WPKH::new_with_script(satoshis, script_buf).into() - }, - TrVariant::P2TRKEYPATH => { - TxOutputP2TRKeyPath::new_with_script(satoshis, script_buf).into() - }, - // We're keeping those two variants separate for now, we're planning - // on writing a new interface as part of a larger task anyway. - TrVariant::BRC20TRANSFER => { - TXOutputP2TRScriptPath::new_with_script(satoshis, script_buf).into() - }, - TrVariant::NFTINSCRIPTION => { - TXOutputP2TRScriptPath::new_with_script(satoshis, script_buf).into() - } - }; - - builder = builder.add_output(tx); - } - - // Copy those values before `builder` gets consumed. - let version = builder.version; - let lock_time = builder.lock_time.to_consensus_u32(); - - // Sign transaction and create protobuf structures. - let tx = builder.sign_inputs(keypair)?; - - // Create Protobuf structures of inputs. - let mut proto_inputs = vec![]; - for input in &tx.inner.input { - let txid: Vec = input - .previous_output - .txid - .as_byte_array() - .iter() - .cloned() - .rev() - .collect(); - - proto_inputs.push(TransactionInput { - previousOutput: Some(OutPoint { - hash: Cow::from(txid), - index: input.previous_output.vout, - sequence: input.sequence.to_consensus_u32(), - // Unused. - tree: 0, - }), - sequence: input.sequence.to_consensus_u32(), - script: { - // If `scriptSig` is empty, then the Witness is being used. - if input.script_sig.is_empty() { - let witness: Vec = input.witness.to_vec().into_iter().flatten().collect(); - Cow::from(witness) - } else { - Cow::from(input.script_sig.to_bytes()) - } - }, - }); - } - - // Create Protobuf structures of outputs. - let mut proto_outputs = vec![]; - for output in &tx.inner.output { - proto_outputs.push(TransactionOutput { - value: output.value as i64, - script: Cow::from(output.script_pubkey.to_bytes()), - spendingScript: Cow::default(), - }) - } - - // Create Protobuf structure of the full transaction. - let mut signing = SigningOutput { - transaction: Some(ProtoTransaction { - version, - lockTime: lock_time, - inputs: proto_inputs, - outputs: proto_outputs, - }), - encoded: Cow::default(), - transaction_id: Cow::from(tx.inner.txid().to_string()), - error: tw_proto::Common::Proto::SigningError::OK, - error_message: Cow::default(), - }; - - // Sign transaction and update Protobuf structure. - let signed = tx.serialize()?; - signing.encoded = Cow::from(signed); - - Ok(signing) -} - -#[repr(C)] -pub enum CTaprootError { - Ok = 0, - InvalidSlice = 1, - InvalidPubkey = 2, - InvalidSegwitPukey = 3, -} - -impl From for ErrorCode { - fn from(error: CTaprootError) -> Self { - error as ErrorCode - } -} diff --git a/rust/tw_bitcoin/src/ffi/scripts.rs b/rust/tw_bitcoin/src/ffi/scripts.rs deleted file mode 100644 index f230cbd0cc0..00000000000 --- a/rust/tw_bitcoin/src/ffi/scripts.rs +++ /dev/null @@ -1,197 +0,0 @@ -use crate::brc20::{BRC20TransferInscription, Ticker}; -use crate::nft::OrdinalNftInscription; -use crate::{ - Recipient, TXOutputP2TRScriptPath, TxOutputP2PKH, TxOutputP2TRKeyPath, TxOutputP2WPKH, -}; -use bitcoin::{PublicKey, WPubkeyHash}; -use std::borrow::Cow; -use std::ffi::{c_char, CStr}; -use tw_memory::ffi::c_byte_array::CByteArray; -use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; -use tw_misc::try_or_else; -use tw_proto::Bitcoin::Proto::TransactionOutput; - -#[no_mangle] -// Builds the P2PKH scriptPubkey. -pub unsafe extern "C" fn tw_build_p2pkh_script( - satoshis: i64, - pubkey: *const u8, - pubkey_len: usize, -) -> CByteArray { - // Convert Recipient - let slice = try_or_else!( - CByteArrayRef::new(pubkey, pubkey_len).as_slice(), - CByteArray::null - ); - let recipient = try_or_else!(Recipient::::from_slice(slice), CByteArray::null); - - let tx_out = TxOutputP2PKH::new(satoshis as u64, recipient); - - // Prepare and serialize protobuf structure. - let proto = TransactionOutput { - value: satoshis, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::default(), - }; - - let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); - - CByteArray::from(serialized) -} - -#[no_mangle] -// Builds the P2WPKH scriptPubkey. -pub unsafe extern "C" fn tw_build_p2wpkh_script( - satoshis: i64, - pubkey: *const u8, - pubkey_len: usize, -) -> CByteArray { - // Convert Recipient - let slice = try_or_else!( - CByteArrayRef::new(pubkey, pubkey_len).as_slice(), - CByteArray::null - ); - let recipient = try_or_else!( - Recipient::::from_slice(slice), - CByteArray::null - ); - - let tx_out = TxOutputP2WPKH::new(satoshis as u64, recipient); - - // Prepare and serialize protobuf structure. - let proto = TransactionOutput { - value: satoshis, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::default(), - }; - - let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); - - CByteArray::from(serialized) -} - -#[no_mangle] -// Builds the P2TR key-path scriptPubkey. -pub unsafe extern "C" fn tw_build_p2tr_key_path_script( - satoshis: i64, - pubkey: *const u8, - pubkey_len: usize, -) -> CByteArray { - // Convert Recipient - let slice = try_or_else!( - CByteArrayRef::new(pubkey, pubkey_len).as_slice(), - CByteArray::null - ); - let recipient = try_or_else!(Recipient::::from_slice(slice), CByteArray::null); - - let tx_out = TxOutputP2TRKeyPath::new(satoshis as u64, recipient.into()); - - // Prepare and serialize protobuf structure. - let proto = TransactionOutput { - value: satoshis, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::default(), - }; - - let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); - - CByteArray::from(serialized) -} - -#[no_mangle] -// Builds the Ordinals inscripton for BRC20 transfer. -pub unsafe extern "C" fn tw_build_brc20_transfer_inscription( - // The 4-byte ticker. - ticker: *const c_char, - amount: u64, - satoshis: i64, - pubkey: *const u8, - pubkey_len: usize, -) -> CByteArray { - // Convert ticket. - let ticker = match CStr::from_ptr(ticker).to_str() { - Ok(input) => input, - Err(_) => return CByteArray::null(), - }; - - if ticker.len() != 4 { - return CByteArray::null(); - } - - let ticker = Ticker::new(ticker.to_string()).expect("ticker must be 4 bytes"); - - // Convert Recipient - let slice = try_or_else!( - CByteArrayRef::new(pubkey, pubkey_len).as_slice(), - CByteArray::null - ); - - let recipient = try_or_else!(Recipient::::from_slice(slice), CByteArray::null); - - // Build transfer inscription. - let transfer = BRC20TransferInscription::new(recipient, ticker, amount) - .expect("transfer inscription implemented wrongly"); - - let tx_out = TXOutputP2TRScriptPath::new(satoshis as u64, transfer.inscription().recipient()); - let spending_script = transfer.inscription().taproot_program(); - - // Prepare and serialize protobuf structure. - let proto = TransactionOutput { - value: satoshis, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::from(spending_script.as_bytes()), - }; - - let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); - - CByteArray::from(serialized) -} - -#[no_mangle] -// Builds the Ordinals inscripton for BRC20 transfer. -pub unsafe extern "C" fn tw_bitcoin_build_nft_inscription( - mime_type: *const c_char, - data: *const u8, - data_len: usize, - satoshis: i64, - pubkey: *const u8, - pubkey_len: usize, -) -> CByteArray { - // Convert mimeType. - let mime_type = match CStr::from_ptr(mime_type).to_str() { - Ok(input) => input, - Err(_) => return CByteArray::null(), - }; - - // Convert data to inscribe. - let data = try_or_else!( - CByteArrayRef::new(data, data_len).as_slice(), - CByteArray::null - ); - - // Convert Recipient. - let slice = try_or_else!( - CByteArrayRef::new(pubkey, pubkey_len).as_slice(), - CByteArray::null - ); - - let recipient = try_or_else!(Recipient::::from_slice(slice), CByteArray::null); - - // Inscribe NFT data. - let nft = OrdinalNftInscription::new(mime_type.as_bytes(), data, recipient) - .expect("Ordinal NFT inscription incorrectly constructed"); - - let tx_out = TXOutputP2TRScriptPath::new(satoshis as u64, nft.inscription().recipient()); - let spending_script = nft.inscription().taproot_program(); - - // Prepare and serialize protobuf structure. - let proto = TransactionOutput { - value: satoshis, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::from(spending_script.as_bytes()), - }; - - let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); - - CByteArray::from(serialized) -} diff --git a/rust/tw_bitcoin/src/input/mod.rs b/rust/tw_bitcoin/src/input/mod.rs deleted file mode 100644 index ebe68369c2d..00000000000 --- a/rust/tw_bitcoin/src/input/mod.rs +++ /dev/null @@ -1,92 +0,0 @@ -use bitcoin::{OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Witness}; - -mod p2pkh; -mod p2tr_key_path; -mod p2tr_script_path; -mod p2wpkh; - -pub use p2pkh::*; -pub use p2tr_key_path::*; -pub use p2tr_script_path::*; -pub use p2wpkh::*; - -#[derive(Debug, Clone)] -pub struct InputContext { - pub previous_output: OutPoint, - pub value: u64, - // The condition for claiming the output. - pub script_pubkey: ScriptBuf, - pub sequence: Sequence, - // Witness data for Segwit/Taproot transactions. -} - -impl InputContext { - pub fn new(utxo: TxOut, point: OutPoint) -> Self { - InputContext { - previous_output: point, - value: utxo.value, - script_pubkey: utxo.script_pubkey, - // Default value of `0xFFFFFFFF = 4294967295`. - sequence: Sequence::default(), - } - } -} - -#[derive(Debug, Clone)] -pub enum TxInput { - P2PKH(TxInputP2PKH), - P2WPKH(TxInputP2WPKH), - P2TRKeyPath(TxInputP2TRKeyPath), - P2TRScriptPath(TxInputP2TRScriptPath), -} - -impl From for TxInput { - fn from(input: TxInputP2PKH) -> Self { - TxInput::P2PKH(input) - } -} - -impl From for TxInput { - fn from(input: TxInputP2WPKH) -> Self { - TxInput::P2WPKH(input) - } -} - -impl From for TxInput { - fn from(input: TxInputP2TRKeyPath) -> Self { - TxInput::P2TRKeyPath(input) - } -} - -impl From for TxInput { - fn from(input: TxInputP2TRScriptPath) -> Self { - TxInput::P2TRScriptPath(input) - } -} - -impl From for TxIn { - fn from(input: TxInput) -> Self { - let ctx = input.ctx(); - - TxIn { - previous_output: ctx.previous_output, - script_sig: ScriptBuf::new(), - sequence: ctx.sequence, - witness: Witness::default(), - } - } -} - -impl TxInput { - pub fn ctx(&self) -> &InputContext { - match self { - TxInput::P2PKH(t) => t.ctx(), - TxInput::P2WPKH(t) => t.ctx(), - TxInput::P2TRKeyPath(t) => t.ctx(), - TxInput::P2TRScriptPath(t) => t.ctx(), - } - } - pub fn satoshis(&self) -> u64 { - self.ctx().value - } -} diff --git a/rust/tw_bitcoin/src/input/p2pkh.rs b/rust/tw_bitcoin/src/input/p2pkh.rs deleted file mode 100644 index 66bfe3de73b..00000000000 --- a/rust/tw_bitcoin/src/input/p2pkh.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{Error, InputContext, Recipient, Result}; -use bitcoin::{OutPoint, PubkeyHash, ScriptBuf, Sequence, Txid}; - -#[derive(Debug, Clone)] -pub struct TxInputP2PKH { - ctx: InputContext, - recipient: Recipient, -} - -impl TxInputP2PKH { - pub fn new(txid: Txid, vout: u32, recipient: Recipient, satoshis: u64) -> Self { - let script = ScriptBuf::new_p2pkh(recipient.pubkey_hash()); - Self::new_with_script(txid, vout, recipient, satoshis, script) - } - pub fn new_with_script( - txid: Txid, - vout: u32, - recipient: Recipient, - satoshis: u64, - script: ScriptBuf, - ) -> Self { - TxInputP2PKH { - ctx: InputContext { - previous_output: OutPoint { txid, vout }, - value: satoshis, - script_pubkey: script, - sequence: Sequence::default(), - }, - recipient, - } - } - pub fn builder() -> TxInputP2PKHBuilder { - TxInputP2PKHBuilder::new() - } - /// Read-only exposure to the context. - pub fn ctx(&self) -> &InputContext { - &self.ctx - } - /// Read-only exposure to the recipient. - pub fn recipient(&self) -> &Recipient { - &self.recipient - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxInputP2PKHBuilder { - txid: Option, - vout: Option, - recipient: Option>, - satoshis: Option, -} - -impl TxInputP2PKHBuilder { - pub fn new() -> TxInputP2PKHBuilder { - TxInputP2PKHBuilder { - txid: None, - vout: None, - recipient: None, - satoshis: None, - } - } - pub fn txid(mut self, txid: Txid) -> TxInputP2PKHBuilder { - self.txid = Some(txid); - self - } - pub fn vout(mut self, vout: u32) -> TxInputP2PKHBuilder { - self.vout = Some(vout); - self - } - pub fn recipient(mut self, recipient: impl Into>) -> TxInputP2PKHBuilder { - self.recipient = Some(recipient.into()); - self - } - pub fn satoshis(mut self, satoshis: u64) -> TxInputP2PKHBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn build(self) -> Result { - Ok(TxInputP2PKH::new( - self.txid.ok_or(Error::Todo)?, - self.vout.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - self.satoshis.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/input/p2tr_key_path.rs b/rust/tw_bitcoin/src/input/p2tr_key_path.rs deleted file mode 100644 index 29bf0ff07f5..00000000000 --- a/rust/tw_bitcoin/src/input/p2tr_key_path.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{Error, InputContext, Recipient, Result}; -use bitcoin::key::TweakedPublicKey; -use bitcoin::{OutPoint, ScriptBuf, Sequence, Txid}; - -#[derive(Debug, Clone)] -pub struct TxInputP2TRKeyPath { - ctx: InputContext, - recipient: Recipient, -} - -impl TxInputP2TRKeyPath { - pub fn new( - txid: Txid, - vout: u32, - recipient: Recipient, - satoshis: u64, - ) -> Self { - let script = ScriptBuf::new_v1_p2tr_tweaked(recipient.tweaked_pubkey()); - Self::new_with_script(txid, vout, recipient, satoshis, script) - } - pub fn new_with_script( - txid: Txid, - vout: u32, - recipient: Recipient, - satoshis: u64, - script: ScriptBuf, - ) -> Self { - TxInputP2TRKeyPath { - ctx: InputContext { - previous_output: OutPoint { txid, vout }, - value: satoshis, - script_pubkey: script, - sequence: Sequence::default(), - }, - recipient, - } - } - pub fn builder() -> TxInputP2TRKeyPathBuilder { - TxInputP2TRKeyPathBuilder::new() - } - /// Read-only exposure to the context. - pub fn ctx(&self) -> &InputContext { - &self.ctx - } - /// Read-only exposure to the recipient. - pub fn recipient(&self) -> &Recipient { - &self.recipient - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxInputP2TRKeyPathBuilder { - txid: Option, - vout: Option, - recipient: Option>, - satoshis: Option, -} - -impl TxInputP2TRKeyPathBuilder { - pub fn new() -> TxInputP2TRKeyPathBuilder { - Self::default() - } - pub fn txid(mut self, txid: Txid) -> TxInputP2TRKeyPathBuilder { - self.txid = Some(txid); - self - } - pub fn vout(mut self, vout: u32) -> TxInputP2TRKeyPathBuilder { - self.vout = Some(vout); - self - } - pub fn recipient( - mut self, - recipient: impl Into>, - ) -> TxInputP2TRKeyPathBuilder { - self.recipient = Some(recipient.into()); - self - } - pub fn satoshis(mut self, satoshis: u64) -> TxInputP2TRKeyPathBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn build(self) -> Result { - Ok(TxInputP2TRKeyPath::new( - self.txid.ok_or(Error::Todo)?, - self.vout.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - self.satoshis.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/input/p2tr_script_path.rs b/rust/tw_bitcoin/src/input/p2tr_script_path.rs deleted file mode 100644 index af02c107533..00000000000 --- a/rust/tw_bitcoin/src/input/p2tr_script_path.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::{Error, InputContext, Recipient, Result, TaprootScript}; -use bitcoin::script::ScriptBuf; -use bitcoin::taproot::TaprootSpendInfo; -use bitcoin::{OutPoint, Sequence, Txid}; - -#[derive(Debug, Clone)] -pub struct TxInputP2TRScriptPath { - ctx: InputContext, - recipient: Recipient, - witness: ScriptBuf, - spend_info: TaprootSpendInfo, -} - -impl TxInputP2TRScriptPath { - pub fn new( - txid: Txid, - vout: u32, - recipient: Recipient, - satoshis: u64, - witness: ScriptBuf, - spend_info: TaprootSpendInfo, - ) -> Self { - let script = ScriptBuf::new_v1_p2tr( - &secp256k1::Secp256k1::new(), - recipient.untweaked_pubkey(), - Some(recipient.merkle_root()), - ); - - Self::new_with_script(txid, vout, recipient, satoshis, script, witness, spend_info) - } - pub fn new_with_script( - txid: Txid, - vout: u32, - recipient: Recipient, - satoshis: u64, - script: ScriptBuf, - witness: ScriptBuf, - spend_info: TaprootSpendInfo, - ) -> Self { - TxInputP2TRScriptPath { - ctx: InputContext { - previous_output: OutPoint { txid, vout }, - value: satoshis, - script_pubkey: script, - sequence: Sequence::default(), - }, - recipient, - witness, - spend_info, - } - } - pub fn builder() -> TxInputP2TRScriptPathBuilder { - TxInputP2TRScriptPathBuilder::new() - } - pub fn ctx(&self) -> &InputContext { - &self.ctx - } - pub fn recipient(&self) -> &Recipient { - &self.recipient - } - pub fn witness(&self) -> &ScriptBuf { - &self.witness - } - pub fn spend_info(&self) -> &TaprootSpendInfo { - &self.spend_info - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxInputP2TRScriptPathBuilder { - txid: Option, - vout: Option, - recipient: Option>, - satoshis: Option, - script: Option, - spend_info: Option, -} - -impl TxInputP2TRScriptPathBuilder { - pub fn new() -> TxInputP2TRScriptPathBuilder { - TxInputP2TRScriptPathBuilder { - txid: None, - vout: None, - recipient: None, - satoshis: None, - script: None, - spend_info: None, - } - } - pub fn txid(mut self, txid: Txid) -> TxInputP2TRScriptPathBuilder { - self.txid = Some(txid); - self - } - pub fn vout(mut self, vout: u32) -> TxInputP2TRScriptPathBuilder { - self.vout = Some(vout); - self - } - pub fn recipient( - mut self, - recipient: Recipient, - ) -> TxInputP2TRScriptPathBuilder { - self.recipient = Some(recipient); - self - } - pub fn satoshis(mut self, satoshis: u64) -> TxInputP2TRScriptPathBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn script(mut self, script: ScriptBuf) -> TxInputP2TRScriptPathBuilder { - self.script = Some(script); - self - } - pub fn spend_info(mut self, spend_info: TaprootSpendInfo) -> TxInputP2TRScriptPathBuilder { - self.spend_info = Some(spend_info); - self - } - pub fn build(self) -> Result { - Ok(TxInputP2TRScriptPath::new( - self.txid.ok_or(Error::Todo)?, - self.vout.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - self.satoshis.ok_or(Error::Todo)?, - self.script.ok_or(Error::Todo)?, - self.spend_info.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/input/p2wpkh.rs b/rust/tw_bitcoin/src/input/p2wpkh.rs deleted file mode 100644 index 4270fe73688..00000000000 --- a/rust/tw_bitcoin/src/input/p2wpkh.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::{Error, InputContext, Recipient, Result}; -use bitcoin::{OutPoint, ScriptBuf, Sequence, Txid, WPubkeyHash}; - -#[derive(Debug, Clone)] -pub struct TxInputP2WPKH { - ctx: InputContext, - recipient: Recipient, -} - -impl TxInputP2WPKH { - pub fn new(txid: Txid, vout: u32, recipient: Recipient, satoshis: u64) -> Self { - let script = ScriptBuf::new_v0_p2wpkh(recipient.wpubkey_hash()); - Self::new_with_script(txid, vout, recipient, satoshis, script) - } - pub fn new_with_script( - txid: Txid, - vout: u32, - recipient: Recipient, - satoshis: u64, - script: ScriptBuf, - ) -> Self { - TxInputP2WPKH { - ctx: InputContext { - previous_output: OutPoint { txid, vout }, - value: satoshis, - script_pubkey: script, - sequence: Sequence::default(), - }, - recipient, - } - } - pub fn builder() -> TxInputP2WPKHBuilder { - TxInputP2WPKHBuilder::new() - } - /// Read-only exposure to the context. - pub fn ctx(&self) -> &InputContext { - &self.ctx - } - /// Read-only exposure to the recipient. - pub fn recipient(&self) -> &Recipient { - &self.recipient - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxInputP2WPKHBuilder { - txid: Option, - vout: Option, - recipient: Option>, - satoshis: Option, -} - -impl TxInputP2WPKHBuilder { - pub fn new() -> TxInputP2WPKHBuilder { - Self::default() - } - pub fn txid(mut self, txid: Txid) -> TxInputP2WPKHBuilder { - self.txid = Some(txid); - self - } - pub fn vout(mut self, vout: u32) -> TxInputP2WPKHBuilder { - self.vout = Some(vout); - self - } - pub fn recipient(mut self, recipient: Recipient) -> TxInputP2WPKHBuilder { - self.recipient = Some(recipient); - self - } - pub fn satoshis(mut self, satoshis: u64) -> TxInputP2WPKHBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn build(self) -> Result { - Ok(TxInputP2WPKH::new( - self.txid.ok_or(Error::Todo)?, - self.vout.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - self.satoshis.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/lib.rs b/rust/tw_bitcoin/src/lib.rs index 7ff788a5dc6..a3bb3d0ac69 100644 --- a/rust/tw_bitcoin/src/lib.rs +++ b/rust/tw_bitcoin/src/lib.rs @@ -1,28 +1,68 @@ extern crate serde; -pub mod brc20; -pub mod claim; -pub mod ffi; -pub mod input; -pub mod nft; -pub mod ordinals; -pub mod output; -pub mod recipient; -#[cfg(test)] -mod tests; -pub mod transaction; -pub mod utils; - -// Reexports -pub use input::*; -pub use output::*; -pub use recipient::Recipient; -pub use transaction::*; -pub use utils::*; +pub mod entry; +pub mod modules; + +use std::fmt::Display; + +pub use bitcoin as native; +pub use entry::*; +pub use secp256k1; + +use tw_proto::BitcoinV2::Proto; pub type Result = std::result::Result; -#[derive(Debug, Clone)] -pub enum Error { - Todo, +#[derive(Debug)] +pub struct Error(Proto::Error); + +// TODO: We can improve this. +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl From for Error { + fn from(value: Proto::Error) -> Self { + Error(value) + } +} + +impl From for Proto::Error { + fn from(value: Error) -> Self { + value.0 + } +} + +impl From for Error { + fn from(_: bitcoin::key::Error) -> Self { + Error::from(Proto::Error::Error_invalid_public_key) + } +} + +impl From for Error { + fn from(_: bitcoin::ecdsa::Error) -> Self { + Error::from(Proto::Error::Error_invalid_ecdsa_signature) + } +} + +impl From for Error { + fn from(_: bitcoin::taproot::Error) -> Self { + Error::from(Proto::Error::Error_invalid_schnorr_signature) + } +} + +// Convenience aliases. +#[rustfmt::skip] +pub mod aliases { + use super::Proto; + + pub type ProtoOutputRecipient<'a> = Proto::mod_Output::OneOfto_recipient<'a>; + pub type ProtoOutputBuilder<'a> = Proto::mod_Output::mod_OutputBuilder::OneOfvariant<'a>; + pub type ProtoOutputRedeemScriptOrHashBuilder<'a> = Proto::mod_Output::mod_OutputRedeemScriptOrHash::OneOfvariant<'a>; + pub type ProtoPubkeyOrHash<'a> = Proto::mod_ToPublicKeyOrHash::OneOfto_address<'a>; + pub type ProtoRedeemScriptOrHash<'a> = Proto::mod_Output::mod_OutputRedeemScriptOrHash::OneOfvariant<'a>; + pub type ProtoInputRecipient<'a> = Proto::mod_Input::OneOfto_recipient<'a>; + pub type ProtoInputBuilder<'a> = Proto::mod_Input::mod_InputBuilder::OneOfvariant<'a>; } diff --git a/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs b/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs new file mode 100644 index 00000000000..097f6b15772 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs @@ -0,0 +1,245 @@ +use crate::aliases::*; +use crate::{Error, Result}; +use bitcoin::absolute::LockTime; +use bitcoin::taproot::{LeafVersion, NodeInfo, TaprootSpendInfo}; +use bitcoin::{Network, PrivateKey, PublicKey, ScriptBuf}; +use secp256k1::XOnlyPublicKey; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex; +use tw_misc::traits::ToBytesVec; +use tw_proto::Bitcoin::Proto as LegacyProto; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Common::Proto as CommonProto; +use tw_proto::Utxo::Proto as UtxoProto; + +// Builds a Taproot transaction for the legacy protobuf structure, as used by +// `tw_bitcoin_legacy_taproot_build_and_sign_transaction` in the +// `wallet-core-rs` crate. +pub fn taproot_build_and_sign_transaction( + legacy: LegacyProto::SigningInput, +) -> Result { + let coin = TestCoinContext::default(); + + // Convert the appropriate lock time. + let native_lock_time = LockTime::from_consensus(legacy.lock_time); + let lock_time = match native_lock_time { + LockTime::Blocks(blocks) => UtxoProto::LockTime { + variant: UtxoProto::mod_LockTime::OneOfvariant::blocks(blocks.to_consensus_u32()), + }, + LockTime::Seconds(seconds) => UtxoProto::LockTime { + variant: UtxoProto::mod_LockTime::OneOfvariant::seconds(seconds.to_consensus_u32()), + }, + }; + + // Prepare the inputs. + let mut inputs = vec![]; + for (index, utxo) in legacy.utxo.iter().enumerate() { + // We try to fetch the private key in the `private_key` fields by the + // corresponding index. If there is none, we default to the first + // provided key (implying that one single private key is used for all + // inputs). + let private_key = if let Some(private_key) = legacy.private_key.get(index) { + private_key + } else { + legacy + .private_key + .get(0) + .ok_or_else(|| Error::from(Proto::Error::Error_legacy_no_private_key))? + }; + + let private_key = PrivateKey::from_slice(private_key, Network::Bitcoin)?; + let my_pubkey = private_key.public_key(&secp256k1::Secp256k1::new()); + + let mut input = input_from_legacy_utxo(my_pubkey, utxo, legacy.hash_type)?; + input.private_key = private_key.to_bytes().into(); + inputs.push(input); + } + + // We skip any sort of builders and use the provided scripts directly. + let mut outputs = vec![]; + for output in legacy + .plan + .ok_or_else(|| Error::from(Proto::Error::Error_legacy_no_plan_provided))? + .utxos + { + outputs.push(Proto::Output { + value: output.amount as u64, + to_recipient: ProtoOutputRecipient::custom_script_pubkey(output.script), + }) + } + + // We only select enough inputs to cover the output balance. However, since + // some transaction types require precise input ordering (such as BRC20), we + // do not sort the inputs and use the ordering as provided by the caller. + let input_selector = UtxoProto::InputSelector::SelectInOrder; + + // The primary payload. + let signing_input = Proto::SigningInput { + version: 2, + private_key: legacy + .private_key + .get(0) + .map(|pk| pk.to_vec().into()) + .unwrap_or_default(), + lock_time: Some(lock_time), + inputs, + outputs, + input_selector, + fee_per_vb: legacy.byte_fee as u64, + change_output: None, + disable_change_output: true, + dangerous_use_fixed_schnorr_rng: false, + }; + + // Build and sign the Bitcoin transaction. + let signed = crate::entry::BitcoinEntry.sign(&coin, signing_input); + + // Check for error. + if signed.error != Proto::Error::OK { + return Err(Error::from(signed.error)); + } + + let transaction = signed + .transaction + .expect("transaction not returned from signer"); + + // Convert the returned transaction data into the (legacy) `Transaction` + // protobuf from `Bitcoin.proto`. + let legacy_transaction = LegacyProto::Transaction { + version: 2, + lockTime: native_lock_time.to_consensus_u32(), + inputs: transaction + .inputs + .iter() + .map(|input| LegacyProto::TransactionInput { + previousOutput: Some(LegacyProto::OutPoint { + hash: input.txid.clone(), + index: input.vout, + sequence: input.sequence, + // Unused for Bitcoin + tree: Default::default(), + }), + // Notr: Not sure why this exists twice? + sequence: input.sequence, + script: input.script_sig.clone(), + }) + .collect(), + outputs: transaction + .outputs + .iter() + .map(|output| LegacyProto::TransactionOutput { + value: output.value as i64, + script: output.script_pubkey.clone(), + spendingScript: output.taproot_payload.clone(), + }) + .collect(), + }; + + let txid_hex = hex::encode(signed.txid.as_ref(), false); + + // Put the `Transaction` into the `SigningOutput`, return. + let legacy_output = LegacyProto::SigningOutput { + transaction: Some(legacy_transaction), + encoded: signed.encoded, + transaction_id: txid_hex.into(), + error: CommonProto::SigningError::OK, + error_message: Default::default(), + }; + + Ok(legacy_output) +} + +/// Convenience function. +fn input_from_legacy_utxo( + my_pubkey: PublicKey, + utxo: &LegacyProto::UnspentTransaction, + hash_type: u32, +) -> Result> { + // We identify the provided `Variant` and invoke the builder function. We + // explicitly skip/ignore any provided script in the input. + let input_builder = match utxo.variant { + LegacyProto::TransactionVariant::P2PKH => Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(my_pubkey.to_bytes().into()), + }, + LegacyProto::TransactionVariant::P2WPKH => Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(my_pubkey.to_bytes().into()), + }, + LegacyProto::TransactionVariant::P2TRKEYPATH => Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2tr_key_path(Proto::mod_Input::InputTaprootKeyPath { + one_prevout: false, + public_key: my_pubkey.to_bytes().into(), + }), + }, + LegacyProto::TransactionVariant::BRC20TRANSFER + | LegacyProto::TransactionVariant::NFTINSCRIPTION => { + // The spending script must to be empty. + if utxo.spendingScript.is_empty() { + return Err(Error::from( + Proto::Error::Error_legacy_no_spending_script_provided, + )); + } + + let spending_script = ScriptBuf::from_bytes(utxo.spendingScript.to_vec()); + + let xonly = XOnlyPublicKey::from(my_pubkey.inner); + let spend_info = TaprootSpendInfo::from_node_info( + &secp256k1::Secp256k1::new(), + xonly, + NodeInfo::new_leaf_with_ver(spending_script.clone(), LeafVersion::TapScript), + ); + + let control_block = spend_info + .control_block(&(spending_script, LeafVersion::TapScript)) + .expect("failed to construct control block"); + + Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2tr_script_path( + Proto::mod_Input::InputTaprootScriptPath { + one_prevout: false, + payload: utxo.spendingScript.to_vec().into(), + control_block: control_block.serialize().into(), + }, + ), + } + }, + }; + + // Convert the integer indicating the sighash type into the corresponding + // Utxo variant. + let sighash_type = match hash_type { + 0 => UtxoProto::SighashType::UseDefault, + 1 => UtxoProto::SighashType::All, + 2 => UtxoProto::SighashType::None_pb, + 3 => UtxoProto::SighashType::Single, + 129 => UtxoProto::SighashType::AllPlusAnyoneCanPay, + 130 => UtxoProto::SighashType::NonePlusAnyoneCanPay, + 131 => UtxoProto::SighashType::SinglePlusAnyoneCanPay, + _ => return Err(Error::from(Proto::Error::Error_utxo_invalid_sighash_type)), + }; + + // Construct Input and return. + let out_point = utxo + .out_point + .as_ref() + .ok_or_else(|| Error::from(Proto::Error::Error_legacy_outpoint_not_set))?; + + // We explicitly disable zero-valued sequences for legacy and default to + // `0xFFFFFFFF`' + let sequence = if out_point.sequence == 0 { + u32::MAX + } else { + out_point.sequence + }; + + Ok(Proto::Input { + private_key: Default::default(), + txid: out_point.hash.to_vec().into(), + vout: out_point.index, + value: utxo.amount as u64, + sequence, + sequence_enable_zero: false, + sighash_type, + to_recipient: ProtoInputRecipient::builder(input_builder), + }) +} diff --git a/rust/tw_bitcoin/src/modules/legacy/mod.rs b/rust/tw_bitcoin/src/modules/legacy/mod.rs new file mode 100644 index 00000000000..b488c44f4a6 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/legacy/mod.rs @@ -0,0 +1,6 @@ +#![allow(clippy::missing_safety_doc)] + +pub mod build_and_sign; + +// Re-exports +pub use build_and_sign::*; diff --git a/rust/tw_bitcoin/src/modules/mod.rs b/rust/tw_bitcoin/src/modules/mod.rs new file mode 100644 index 00000000000..ff6024a4895 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/mod.rs @@ -0,0 +1,5 @@ +pub mod legacy; +pub mod plan_builder; +pub mod signer; +pub mod transactions; +mod utils; diff --git a/rust/tw_bitcoin/src/modules/plan_builder.rs b/rust/tw_bitcoin/src/modules/plan_builder.rs new file mode 100644 index 00000000000..fd8eb880cad --- /dev/null +++ b/rust/tw_bitcoin/src/modules/plan_builder.rs @@ -0,0 +1,230 @@ +use crate::modules::utils::hard_clone_proto_output; +use crate::{aliases::*, pre_processor, BitcoinEntry}; +use crate::{Error, Result}; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::modules::plan_builder::PlanBuilder; +use tw_coin_entry::signing_output_error; +use tw_proto::BitcoinV2::Proto; +use tw_proto::BitcoinV2::Proto::mod_Input::InputBrc20Inscription; +use tw_proto::Utxo::Proto as UtxoProto; + +pub struct BitcoinPlanBuilder; + +impl PlanBuilder for BitcoinPlanBuilder { + type SigningInput<'a> = Proto::ComposePlan<'a>; + type Plan = Proto::TransactionPlan<'static>; + + #[inline] + fn plan( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + proto: Self::SigningInput<'_>, + ) -> Self::Plan { + self.plan_impl(_coin, proto) + .unwrap_or_else(|err| signing_output_error!(Proto::TransactionPlan, err)) + } +} + +impl BitcoinPlanBuilder { + fn plan_impl( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + proto: Proto::ComposePlan<'_>, + ) -> Result> { + let plan = match proto.compose { + Proto::mod_ComposePlan::OneOfcompose::brc20(plan) => { + let built_plan = self.plan_brc20(_coin, plan)?; + + Proto::TransactionPlan { + error: Proto::Error::OK, + error_message: Default::default(), + plan: Proto::mod_TransactionPlan::OneOfplan::brc20(built_plan), + } + }, + _ => panic!(), + }; + + Ok(plan) + } + fn plan_brc20( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + proto: Proto::mod_ComposePlan::ComposeBrc20Plan<'_>, + ) -> Result> { + // Hard-clones + let inscription = proto + .inscription + .ok_or_else(|| Error::from(Proto::Error::Error_missing_inscription))?; + + let brc20_info = InputBrc20Inscription { + one_prevout: inscription.one_prevout, + inscribe_to: inscription.inscribe_to.to_vec().into(), + ticker: inscription.ticker.to_string().into(), + transfer_amount: inscription.transfer_amount, + }; + + let tagged_output = super::utils::hard_clone_proto_output( + proto + .tagged_output + .ok_or_else(|| Error::from(Proto::Error::Error_missing_tagged_output))?, + )?; + + // First, we create the reveal transaction in order to calculate its input requirement (fee + dust limit). + + // We can use a zeroed Txid here. + let txid = vec![0; 32]; + let dummy_brc20_input = Proto::Input { + txid: txid.into(), + // The value is not relevant here, but we raise it above the output + // or we get an error. + value: u64::MAX, + sighash_type: UtxoProto::SighashType::UseDefault, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::brc20_inscribe(brc20_info.clone()), + }), + ..Default::default() + }; + + let dummy_reveal = Proto::SigningInput { + inputs: vec![dummy_brc20_input], + outputs: vec![tagged_output.clone()], + input_selector: UtxoProto::InputSelector::UseAll, + // Disable change output creation. + fee_per_vb: proto.fee_per_vb, + disable_change_output: true, + ..Default::default() + }; + + // We can now determine the fee of the reveal transaction. + let dummy_presigned = BitcoinEntry.preimage_hashes(_coin, dummy_reveal); + if dummy_presigned.error != Proto::Error::OK { + return Err(Error::from(dummy_presigned.error)); + } + + assert_eq!(dummy_presigned.error, Proto::Error::OK); + let reveal_fee_estimate = dummy_presigned.fee_estimate; + + // Create the BRC20 output for the COMMIT transaction; we set the + // amount to the estimated fee (REVEAL) plus the dust limit (`tagged_output.value`). + let brc20_output = Proto::Output { + value: reveal_fee_estimate + tagged_output.value, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::brc20_inscribe( + Proto::mod_Output::OutputBrc20Inscription { + inscribe_to: brc20_info.inscribe_to.to_vec().into(), + ticker: brc20_info.ticker.to_string().into(), + transfer_amount: brc20_info.transfer_amount, + }, + ), + }), + }; + + let brc20_output_value = brc20_output.value; + + // Clone the change output, if provided. + let change_output = if let Some(change) = proto.change_output { + Some(super::utils::hard_clone_proto_output(change)?) + } else { + None + }; + + // Create the full COMMIT transaction with the appropriately selected inputs. + let commit_signing = Proto::SigningInput { + private_key: proto.private_key.to_vec().into(), + inputs: proto + .inputs + .iter() + .cloned() + .map(super::utils::hard_clone_proto_input) + .collect::>()?, + outputs: vec![brc20_output], + input_selector: proto.input_selector, + change_output: change_output.clone(), + fee_per_vb: proto.fee_per_vb, + disable_change_output: proto.disable_change_output, + ..Default::default() + }; + + let mut commit_signing = pre_processor(commit_signing); + + // We now determine the Txid of the COMMIT transaction, which we will have + // to use in the REVEAL transaction. + let presigned = BitcoinEntry.preimage_hashes(_coin, commit_signing.clone()); + if presigned.error != Proto::Error::OK { + return Err(Error::from(presigned.error)); + } + + assert_eq!(presigned.error, Proto::Error::OK); + let commit_txid: Vec = presigned.txid.to_vec().iter().copied().rev().collect(); + + // Create a list of the selected input Txids, as indicated by the + // `InputSelector`. + let selected_txids: Vec<_> = presigned + .utxo_inputs + .iter() + .map(|utxo| utxo.txid.clone()) + .collect(); + + // Create the list of selected inputs and update the COMMIT transaction. + let selected_inputs: Vec<_> = proto + .inputs + .into_iter() + .filter(|input| selected_txids.contains(&input.txid)) + .map(super::utils::hard_clone_proto_input) + .collect::>()?; + + commit_signing.inputs = selected_inputs; + + // Update the change amount to calculated amount. + if !proto.disable_change_output && presigned.utxo_outputs.len() == 2 { + let change_amount = presigned + .utxo_outputs + .last() + .expect("No Utxo outputs generated") + .value; + + let mut change_output = change_output.expect("change output expected"); + change_output.value = change_amount; + + commit_signing + .outputs + .push(hard_clone_proto_output(change_output)?); + } + + commit_signing.input_selector = UtxoProto::InputSelector::UseAll; + commit_signing.disable_change_output = true; + commit_signing.fee_per_vb = 0; + commit_signing.change_output = Default::default(); + + // Now we construct the *actual* REVEAL transaction. + + let brc20_input = Proto::Input { + value: brc20_output_value, + txid: commit_txid.into(), // Reference COMMIT transaction. + sighash_type: UtxoProto::SighashType::UseDefault, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::brc20_inscribe(brc20_info.clone()), + }), + ..Default::default() + }; + + // Build the REVEAL transaction. + let reveal_signing = Proto::SigningInput { + private_key: proto.private_key.to_vec().into(), + inputs: vec![brc20_input], + outputs: vec![tagged_output], + input_selector: UtxoProto::InputSelector::UseAll, + change_output: Default::default(), + fee_per_vb: 0, + disable_change_output: true, + ..Default::default() + }; + + let reveal_signing = pre_processor(reveal_signing); + + Ok(Proto::mod_TransactionPlan::Brc20Plan { + commit: Some(commit_signing), + reveal: Some(reveal_signing), + }) + } +} diff --git a/rust/tw_bitcoin/src/modules/signer.rs b/rust/tw_bitcoin/src/modules/signer.rs new file mode 100644 index 00000000000..cdf17b68eef --- /dev/null +++ b/rust/tw_bitcoin/src/modules/signer.rs @@ -0,0 +1,159 @@ +use crate::{BitcoinEntry, Error, Result}; +use bitcoin::key::{TapTweak, TweakedKeyPair}; +use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; +use secp256k1::{KeyPair, Message, Secp256k1}; +use std::collections::HashMap; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PrivateKeyBytes, SignatureBytes}; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +pub struct Signer; + +impl Signer { + pub fn sign_proto( + _coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + ) -> Result> { + // Technically not required here, since this gets called by + // `preimage_hashes_impl` and `compile_impl`. But we're leaving this + // here in case this methods gets extended and the pre-processing does + // not get accidentally forgotten. + let proto = crate::entry::pre_processor(proto); + + // Collect individual private keys per input, if there are any. + let mut individual_keys = HashMap::new(); + for (index, txin) in proto.inputs.iter().enumerate() { + if !txin.private_key.is_empty() { + individual_keys.insert(index, txin.private_key.to_vec()); + } + } + + // Generate the sighashes. + let pre_signed = BitcoinEntry.preimage_hashes_impl(_coin, proto.clone())?; + + // Check for error. + if pre_signed.error != Proto::Error::OK { + return Err(Error::from(pre_signed.error)); + } + + // Sign the sighashes. + let signatures = crate::modules::signer::Signer::signatures_from_proto( + &pre_signed, + proto.private_key.to_vec(), + individual_keys, + proto.dangerous_use_fixed_schnorr_rng, + )?; + + // Construct the final transaction. + BitcoinEntry.compile_impl(_coin, proto, signatures, vec![]) + } + pub fn signatures_from_proto( + input: &Proto::PreSigningOutput<'_>, + private_key: PrivateKeyBytes, + individual_keys: HashMap, + dangerous_use_fixed_schnorr_rng: bool, + ) -> Result> { + let secp = Secp256k1::new(); + + let mut signatures = vec![]; + + for (index, (entry, utxo)) in input + .sighashes + .iter() + .zip(input.utxo_inputs.iter()) + .enumerate() + { + // Check if there's an individual private key for the given input. If not, use the primary one. + let keypair = if let Some(slice) = individual_keys.get(&index) { + KeyPair::from_seckey_slice(&secp, slice) + .map_err(|_| Error::from(Proto::Error::Error_invalid_private_key))? + } else { + KeyPair::from_seckey_slice(&secp, private_key.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_private_key))? + }; + + // Create signable message from sighash. + let sighash = Message::from_slice(entry.sighash.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_sighash))?; + + // Sign the sighash depending on signing method. + match entry.signing_method { + // Create a ECDSA signature for legacy and segwit transaction. + UtxoProto::SigningMethod::Legacy | UtxoProto::SigningMethod::Segwit => { + let sighash_type = + if let UtxoProto::SighashType::UseDefault = entry.sighash_type { + EcdsaSighashType::All + } else { + EcdsaSighashType::from_consensus(entry.sighash_type as u32) + }; + + let sig = bitcoin::ecdsa::Signature { + sig: keypair.secret_key().sign_ecdsa(sighash), + hash_ty: sighash_type, + }; + + signatures.push(sig.serialize().to_vec()); + }, + // Create a Schnorr signature for taproot transactions. + UtxoProto::SigningMethod::TaprootAll + | UtxoProto::SigningMethod::TaprootOnePrevout => { + // Note that `input.sighash_type = 0` is handled by the underlying library. + let sighash_type = TapSighashType::from_consensus_u8(entry.sighash_type as u8) + .map_err(|_| Error::from(Proto::Error::Error_utxo_invalid_sighash_type))?; + + // Any empty leaf hash implies P2TR key-path (balance transfer) + if utxo.leaf_hash.is_empty() { + // Tweak keypair for P2TR key-path (ie. zeroed Merkle root). + let tapped: TweakedKeyPair = keypair.tap_tweak(&secp, None); + let tweaked = KeyPair::from(tapped); + + // Construct the Schnorr signature. + let schnorr = if dangerous_use_fixed_schnorr_rng { + // For tests, we disable the included randomness in order to create + // reproducible signatures. Randomness should ALWAYS be used in + // production. + secp.sign_schnorr_no_aux_rand(&sighash, &tweaked) + } else { + secp.sign_schnorr(&sighash, &tweaked) + }; + + let sig = bitcoin::taproot::Signature { + sig: schnorr, + hash_ty: sighash_type, + }; + + signatures.push(sig.to_vec()); + } + // If it has a leaf hash, then it's a P2TR script-path (complex transaction) + else { + // NOTE: We do not tweak the key here since the complex + // spending condition(s) must take into account on who + // is allowed to spend the input, hence this signing + // process is simpler than for P2TR key-path. + + // Construct the Schnorr signature. + let schnorr = if dangerous_use_fixed_schnorr_rng { + // For tests, we disable the included randomness in order to create + // reproducible signatures. Randomness should ALWAYS be used in + // production. + secp.sign_schnorr_no_aux_rand(&sighash, &keypair) + } else { + secp.sign_schnorr(&sighash, &keypair) + }; + + let sig = bitcoin::taproot::Signature { + sig: schnorr, + hash_ty: sighash_type, + }; + + signatures.push(sig.to_vec()); + } + }, + } + } + + Ok(signatures) + } +} diff --git a/rust/tw_bitcoin/src/modules/transactions/brc20.rs b/rust/tw_bitcoin/src/modules/transactions/brc20.rs new file mode 100644 index 00000000000..51afda68777 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/transactions/brc20.rs @@ -0,0 +1,93 @@ +use super::ordinals::OrdinalsInscription; +use crate::{Error, Result}; +use bitcoin::PublicKey; +use serde::Serialize; +use tw_proto::BitcoinV2::Proto; + +#[derive(Debug, Clone, Serialize)] +pub struct Brc20Ticker(String); + +impl Brc20Ticker { + pub fn new(string: String) -> Result { + // Brc20Ticker must be a 4-letter identifier. + if string.len() != 4 { + return Err(Error::from(Proto::Error::Error_invalid_brc20_ticker)); + } + + Ok(Brc20Ticker(string)) + } +} + +#[derive(Serialize)] +struct BRC20TransferPayload { + #[serde(rename = "p")] + protocol: String, + #[serde(rename = "op")] + operation: String, + #[serde(rename = "tick")] + ticker: Brc20Ticker, + #[serde(rename = "amt")] + amount: String, +} + +impl BRC20TransferPayload { + const PROTOCOL_ID: &str = "brc-20"; + const MIME: &[u8] = b"text/plain;charset=utf-8"; +} + +impl BRC20TransferPayload { + const OPERATION: &str = "transfer"; + + fn new(ticker: Brc20Ticker, value: u64) -> Self { + BRC20TransferPayload { + protocol: Self::PROTOCOL_ID.to_string(), + operation: Self::OPERATION.to_string(), + ticker, + amount: value.to_string(), + } + } +} + +pub struct BRC20TransferInscription(OrdinalsInscription); + +impl BRC20TransferInscription { + pub fn new( + recipient: PublicKey, + ticker: Brc20Ticker, + value: u64, + ) -> Result { + let data: BRC20TransferPayload = BRC20TransferPayload::new(ticker, value); + + let inscription = OrdinalsInscription::new( + BRC20TransferPayload::MIME, + &serde_json::to_vec(&data).expect("badly constructed BRC20 payload"), + recipient, + )?; + + Ok(BRC20TransferInscription(inscription)) + } + pub fn inscription(&self) -> &OrdinalsInscription { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn brc20_ticker_validity() { + // Must be four characters. + let ticker = Brc20Ticker::new("invalid".to_string()); + assert!(ticker.is_err()); + + let ticker = Brc20Ticker::new("asdf".to_string()); + assert!(ticker.is_ok()); + + // Cover clone implemenation. + let ticker = ticker.unwrap(); + + let _cloned = ticker.clone(); + let _ticker = ticker; + } +} diff --git a/rust/tw_bitcoin/src/modules/transactions/input_builder.rs b/rust/tw_bitcoin/src/modules/transactions/input_builder.rs new file mode 100644 index 00000000000..4d1660cb61f --- /dev/null +++ b/rust/tw_bitcoin/src/modules/transactions/input_builder.rs @@ -0,0 +1,320 @@ +use super::brc20::{BRC20TransferInscription, Brc20Ticker}; +use crate::aliases::*; +use crate::modules::transactions::OrdinalNftInscription; +use crate::{Error, Result}; +use bitcoin::taproot::{LeafVersion, TapLeafHash}; +use bitcoin::ScriptBuf; +use secp256k1::XOnlyPublicKey; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +// Convenience varibles used solely for readability. +const NO_LEAF_HASH: Option = None; + +pub struct InputBuilder; + +impl InputBuilder { + pub fn utxo_from_proto(input: &Proto::Input<'_>) -> Result> { + let (signing_method, script_pubkey, leaf_hash, weight) = match &input.to_recipient { + ProtoInputRecipient::builder(builder) => match &builder.variant { + ProtoInputBuilder::p2sh(redeem_script) => { + // The scriptPubkey is the redeem script directly. + let script_pubkey = ScriptBuf::from_bytes(redeem_script.to_vec()); + + ( + UtxoProto::SigningMethod::Legacy, + script_pubkey, + NO_LEAF_HASH, + // scale factor applied to non-witness bytes + 4 * ( + // length + redeem script. + 1 + redeem_script.len() as u64 + ), + ) + }, + ProtoInputBuilder::p2pkh(pubkey) => { + let pubkey = bitcoin::PublicKey::from_slice(pubkey.as_ref())?; + let script_pubkey = ScriptBuf::new_p2pkh(&pubkey.pubkey_hash()); + + ( + UtxoProto::SigningMethod::Legacy, + script_pubkey, + NO_LEAF_HASH, + // scale factor applied to non-witness bytes + 4 * ( + // length + ECDSA signature + 1 + 72 + + // length + public key + 1 + { + if pubkey.compressed { + 33 + } else { + 65 + } + } + ), + ) + }, + ProtoInputBuilder::p2wsh(redeem_script) => { + // The scriptPubkey is the redeem script directly. + let script_pubkey = ScriptBuf::from_bytes(redeem_script.to_vec()); + + ( + UtxoProto::SigningMethod::Segwit, + script_pubkey, + NO_LEAF_HASH, + // witness bytes, scale factor NOT applied. + ( + // length + redeem script. + 1 + redeem_script.len() as u64 + ), + ) + }, + ProtoInputBuilder::p2wpkh(pubkey) => { + let pubkey = bitcoin::PublicKey::from_slice(pubkey.as_ref())?; + + let script_pubkey = + ScriptBuf::new_v0_p2wpkh(&pubkey.wpubkey_hash().ok_or_else(|| { + Error::from(Proto::Error::Error_invalid_witness_pubkey_hash) + })?) + // Special script code requirement for claiming P2WPKH outputs. + .p2wpkh_script_code() + .ok_or_else(|| Error::from(Proto::Error::Error_invalid_wpkh_script_code))?; + + ( + UtxoProto::SigningMethod::Segwit, + script_pubkey, + NO_LEAF_HASH, + // witness bytes, scale factor NOT applied. + ( + // indicator of witness item (2) + 1 + + // length + ECDSA signature (can be 71 or 72) + 1 + 72 + + // length + public key + 1 + { + if pubkey.compressed { + 33 + } else { + 65 + } + } + ), + ) + }, + ProtoInputBuilder::p2tr_key_path(key_path) => { + let pubkey = bitcoin::PublicKey::from_slice(key_path.public_key.as_ref())?; + let xonly = XOnlyPublicKey::from(pubkey.inner); + + let signing_method = if key_path.one_prevout { + UtxoProto::SigningMethod::TaprootOnePrevout + } else { + UtxoProto::SigningMethod::TaprootAll + }; + + let script_pubkey = + ScriptBuf::new_v1_p2tr(&secp256k1::Secp256k1::new(), xonly, None); + + ( + signing_method, + script_pubkey, + NO_LEAF_HASH, + // witness bytes, scale factor NOT applied. + ( + // indicator of witness item (1) + 1 + + // length + Schnorr signature (can be 71 or 72) + 1 + 72 + // NO public key + ), + ) + }, + ProtoInputBuilder::p2tr_script_path(complex) => { + let script_pubkey = ScriptBuf::from_bytes(complex.payload.to_vec()); + let leaf_hash = Some(TapLeafHash::from_script( + script_pubkey.as_script(), + bitcoin::taproot::LeafVersion::TapScript, + )); + + let signing_method = if complex.one_prevout { + UtxoProto::SigningMethod::TaprootOnePrevout + } else { + UtxoProto::SigningMethod::TaprootAll + }; + + ( + signing_method, + script_pubkey, + leaf_hash, + // witness bytes, scale factor NOT applied. + ( + // indicator of witness item + 1 + + // the payload/witness + complex.payload.len() as u64 + ), + ) + }, + ProtoInputBuilder::ordinal_inscribe(ordinal) => { + let pubkey = bitcoin::PublicKey::from_slice(ordinal.inscribe_to.as_ref())?; + let mime_type = ordinal.mime_type.as_ref(); + let data = ordinal.payload.as_ref(); + + let nft = OrdinalNftInscription::new(mime_type.as_bytes(), data, pubkey) + .expect("badly constructed Ordinal inscription"); + + // We construct a control block to estimate the fee, + // otherwise we do not need it here. + let control_block = nft + .inscription() + .spend_info() + .control_block(&( + nft.inscription().taproot_program().to_owned(), + LeafVersion::TapScript, + )) + .expect("badly constructed control block"); + + let leaf_hash = Some(TapLeafHash::from_script( + nft.inscription().taproot_program(), + bitcoin::taproot::LeafVersion::TapScript, + )); + + let signing_method = if ordinal.one_prevout { + UtxoProto::SigningMethod::TaprootOnePrevout + } else { + UtxoProto::SigningMethod::TaprootAll + }; + + let script_pubkey = ScriptBuf::from(nft.inscription().taproot_program()); + + ( + signing_method, + // TODO: This is technically not needed here, since + // Sighash only includes the leaf hash, not the actual + // payload. Remove this (same for other complex scripts). + script_pubkey, + leaf_hash, + // witness bytes, scale factor NOT applied. + ( + // indicator of witness item (1) + 1 + + // length + Schnorr signature (can be 71 or 72) + 1 + 72 + + // the payload/witness + nft.inscription().taproot_program().len() as u64 + + // length + control block + 1 + control_block.size() as u64 + ), + ) + }, + // TODO: Unify this and `ordinal_inscribe` somehow + ProtoInputBuilder::brc20_inscribe(brc20) => { + let pubkey = bitcoin::PublicKey::from_slice(brc20.inscribe_to.as_ref())?; + let ticker = Brc20Ticker::new(brc20.ticker.to_string())?; + + let transfer = + BRC20TransferInscription::new(pubkey, ticker, brc20.transfer_amount) + .expect("invalid BRC20 transfer construction"); + + // We construct a control block to estimate the fee, + // otherwise we do not need it here. + let control_block = transfer + .inscription() + .spend_info() + .control_block(&( + transfer.inscription().taproot_program().to_owned(), + LeafVersion::TapScript, + )) + .expect("badly constructed control block"); + + let leaf_hash = Some(TapLeafHash::from_script( + transfer.inscription().taproot_program(), + bitcoin::taproot::LeafVersion::TapScript, + )); + + let signing_method = if brc20.one_prevout { + UtxoProto::SigningMethod::TaprootOnePrevout + } else { + UtxoProto::SigningMethod::TaprootAll + }; + + let script_pubkey = ScriptBuf::from(transfer.inscription().taproot_program()); + + ( + signing_method, + script_pubkey, + leaf_hash, + // witness bytes, scale factor NOT applied. + ( + // indicator of witness item (1) + 1 + + // length + Schnorr signature (can be 71 or 72) + 1 + 72 + + // the payload/witness + transfer.inscription().taproot_program().len() as u64 + + // length + control block + 1 + control_block.size() as u64 + ), + ) + }, + ProtoInputBuilder::None => { + return Err(Error::from(Proto::Error::Error_missing_input_builder)) + }, + }, + ProtoInputRecipient::custom_script(custom) => { + let script_pubkey = ScriptBuf::from_bytes(custom.script_pubkey.to_vec()); + + #[rustfmt::skip] + let leaf_hash = if let UtxoProto::SigningMethod::TaprootAll + | UtxoProto::SigningMethod::TaprootOnePrevout = custom.signing_method + { + Some(TapLeafHash::from_script( + &script_pubkey, + bitcoin::taproot::LeafVersion::TapScript, + )) + } else { + None + }; + + ( + custom.signing_method, + script_pubkey, + leaf_hash, + ( + // scale factor applied to non-witness bytes + 4 * custom.script_sig.len() as u64 + // indicator of witness item count. + + 1 + // witness bytes, scale factor NOT applied. + + custom + .witness_items + .iter() + .map(|item| item.len() as u64) + .sum::() + ), + ) + }, + ProtoInputRecipient::None => { + return Err(Error::from(Proto::Error::Error_missing_input_builder)) + }, + }; + + // Create Utxo.proto structure. + let utxo = UtxoProto::TxIn { + txid: input.txid.to_vec().into(), + vout: input.vout, + value: input.value, + sequence: input.sequence, + script_pubkey: script_pubkey.to_vec().into(), + signing_method, + sighash_type: input.sighash_type, + weight_estimate: weight, + leaf_hash: leaf_hash + .map(|hash| hash.to_vec().into()) + .unwrap_or_default(), + }; + + Ok(utxo) + } +} diff --git a/rust/tw_bitcoin/src/modules/transactions/input_claim_builder.rs b/rust/tw_bitcoin/src/modules/transactions/input_claim_builder.rs new file mode 100644 index 00000000000..b93269031fc --- /dev/null +++ b/rust/tw_bitcoin/src/modules/transactions/input_claim_builder.rs @@ -0,0 +1,172 @@ +use super::brc20::{BRC20TransferInscription, Brc20Ticker}; +use super::OrdinalNftInscription; +use crate::aliases::*; +use crate::{Error, Result}; +use bitcoin::consensus::Decodable; +use bitcoin::taproot::{ControlBlock, LeafVersion}; +use bitcoin::{ScriptBuf, Witness}; +use std::borrow::Cow; +use tw_coin_entry::coin_entry::SignatureBytes; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +pub struct InputClaimBuilder; + +impl InputClaimBuilder { + /// Creates the claim script (_scriptSig_ or _Witness_) to be revealed + /// on-chain for a given input. + pub fn utxo_claim_from_proto( + input: &Proto::Input<'_>, + signature: SignatureBytes, + ) -> Result> { + let (script_sig, witness) = match &input.to_recipient { + ProtoInputRecipient::builder(variant) => match &variant.variant { + ProtoInputBuilder::p2sh(redeem_script) => ( + ScriptBuf::from_bytes(redeem_script.to_vec()), + Witness::new(), + ), + ProtoInputBuilder::p2pkh(pubkey) => { + let sig = bitcoin::ecdsa::Signature::from_slice(signature.as_ref())?; + let pubkey = bitcoin::PublicKey::from_slice(pubkey.as_ref())?; + + // The spending script itself. + ( + ScriptBuf::builder() + .push_slice(sig.serialize()) + .push_key(&pubkey) + .into_script(), + Witness::new(), + ) + }, + ProtoInputBuilder::p2wsh(redeem_script) => { + let witness = Witness::consensus_decode(&mut redeem_script.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_witness_encoding))?; + + (ScriptBuf::new(), witness) + }, + ProtoInputBuilder::p2wpkh(pubkey) => { + let sig = bitcoin::ecdsa::Signature::from_slice(signature.as_ref())?; + let pubkey = bitcoin::PublicKey::from_slice(pubkey.as_ref())?; + + // The spending script itself. + (ScriptBuf::new(), { + let mut w = Witness::new(); + w.push(sig.serialize()); + w.push(pubkey.to_bytes()); + w + }) + }, + ProtoInputBuilder::p2tr_key_path(_) => { + let sig = bitcoin::taproot::Signature::from_slice(signature.as_ref())?; + + // The spending script itself. + (ScriptBuf::new(), { + let mut w = Witness::new(); + w.push(sig.to_vec()); + w + }) + }, + ProtoInputBuilder::p2tr_script_path(taproot) => { + let control_block = ControlBlock::decode(taproot.control_block.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_control_block))?; + + let sig = bitcoin::taproot::Signature::from_slice(signature.as_ref())?; + + // The spending script itself. + (ScriptBuf::new(), { + let mut w = Witness::new(); + w.push(sig.to_vec()); + w.push(taproot.payload.as_ref()); + w.push(control_block.serialize()); + w + }) + }, + ProtoInputBuilder::ordinal_inscribe(ordinal) => { + let pubkey = bitcoin::PublicKey::from_slice(ordinal.inscribe_to.as_ref())?; + let mime_type = ordinal.mime_type.as_ref(); + let data = ordinal.payload.as_ref(); + + let nft = OrdinalNftInscription::new(mime_type.as_bytes(), data, pubkey) + .expect("badly constructed Ordinal inscription"); + + // Create a control block for that inscription. + let control_block = nft + .inscription() + .spend_info() + .control_block(&( + nft.inscription().taproot_program().to_owned(), + LeafVersion::TapScript, + )) + .expect("badly constructed control block"); + + let sig = bitcoin::taproot::Signature::from_slice(signature.as_ref())?; + + // The spending script itself. + (ScriptBuf::new(), { + let mut w = Witness::new(); + w.push(sig.to_vec()); + w.push(nft.inscription().taproot_program()); + w.push(control_block.serialize()); + w + }) + }, + ProtoInputBuilder::brc20_inscribe(brc20) => { + let pubkey = bitcoin::PublicKey::from_slice(brc20.inscribe_to.as_ref())?; + let ticker = Brc20Ticker::new(brc20.ticker.to_string())?; + + // Construct the BRC20 transfer inscription. + let transfer = + BRC20TransferInscription::new(pubkey, ticker, brc20.transfer_amount) + .expect("invalid BRC20 transfer construction"); + + // Create a control block for that inscription. + let control_block = transfer + .inscription() + .spend_info() + .control_block(&( + transfer.inscription().taproot_program().to_owned(), + LeafVersion::TapScript, + )) + .expect("badly constructed control block"); + + let sig = bitcoin::taproot::Signature::from_slice(signature.as_ref())?; + + // The spending script itself. + (ScriptBuf::new(), { + let mut w = Witness::new(); + w.push(sig.to_vec()); + w.push(transfer.inscription().taproot_program()); + w.push(control_block.serialize()); + w + }) + }, + ProtoInputBuilder::None => { + return Err(Error::from(Proto::Error::Error_missing_input_builder)) + }, + }, + ProtoInputRecipient::custom_script(custom) => ( + ScriptBuf::from_bytes(custom.script_sig.to_vec()), + Witness::from_slice(&custom.witness_items), + ), + ProtoInputRecipient::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + // Create Utxo.proto structure. + let claim = UtxoProto::TxInClaim { + txid: input.txid.to_vec().into(), + vout: input.vout, + sequence: input.sequence, + script_sig: script_sig.to_vec().into(), + witness_items: witness + .to_vec() + .into_iter() + .map(Cow::Owned) + .collect::>>(), + }; + + Ok(claim) + } +} diff --git a/rust/tw_bitcoin/src/modules/transactions/mod.rs b/rust/tw_bitcoin/src/modules/transactions/mod.rs new file mode 100644 index 00000000000..03172284046 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/transactions/mod.rs @@ -0,0 +1,26 @@ +use bitcoin::key::PublicKey; +use bitcoin::script::ScriptBuf; +use bitcoin::taproot::{TapNodeHash, TaprootSpendInfo}; + +mod brc20; +mod input_builder; +mod input_claim_builder; +mod ordinals; +mod output_builder; + +// Re-exports +pub use brc20::{BRC20TransferInscription, Brc20Ticker}; +pub use input_builder::InputBuilder; +pub use input_claim_builder::InputClaimBuilder; +pub use ordinals::{OrdinalNftInscription, OrdinalsInscription}; +pub use output_builder::OutputBuilder; + +pub struct TaprootScript { + pub pubkey: PublicKey, + pub merkle_root: TapNodeHash, +} + +pub struct TaprootProgram { + pub script: ScriptBuf, + pub spend_info: TaprootSpendInfo, +} diff --git a/rust/tw_bitcoin/src/ordinals.rs b/rust/tw_bitcoin/src/modules/transactions/ordinals.rs similarity index 74% rename from rust/tw_bitcoin/src/ordinals.rs rename to rust/tw_bitcoin/src/modules/transactions/ordinals.rs index 35c48a02a0c..76b4a221631 100644 --- a/rust/tw_bitcoin/src/ordinals.rs +++ b/rust/tw_bitcoin/src/modules/transactions/ordinals.rs @@ -1,35 +1,22 @@ -use crate::{Error, Recipient, Result, TaprootProgram, TaprootScript}; +use super::TaprootProgram; +use crate::{Error, Result}; use bitcoin::script::{PushBytesBuf, ScriptBuf}; use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::taproot::{TaprootBuilder, TaprootSpendInfo}; use bitcoin::{PublicKey, Script}; +use tw_proto::BitcoinV2::Proto; -#[derive(Debug, Clone)] pub struct OrdinalsInscription { envelope: TaprootProgram, - recipient: Recipient, } impl OrdinalsInscription { /// Creates a new Ordinals Inscription ("commit stage"). - pub fn new( - mime: &[u8], - data: &[u8], - recipient: Recipient, - ) -> Result { + pub fn new(mime: &[u8], data: &[u8], recipient: PublicKey) -> Result { // Create the envelope, containing the inscription content. - let envelope = create_envelope(mime, data, recipient.public_key())?; + let envelope = create_envelope(mime, data, recipient)?; - // Compute the merkle root of the inscription. - let merkle_root = envelope - .spend_info - .merkle_root() - .expect("Ordinals envelope not constructed correctly"); - - Ok(OrdinalsInscription { - envelope, - recipient: Recipient::::from_pubkey_recipient(recipient, merkle_root), - }) + Ok(OrdinalsInscription { envelope }) } pub fn taproot_program(&self) -> &Script { self.envelope.script.as_script() @@ -37,9 +24,6 @@ impl OrdinalsInscription { pub fn spend_info(&self) -> &TaprootSpendInfo { &self.envelope.spend_info } - pub fn recipient(&self) -> &Recipient { - &self.recipient - } } /// Creates an [Ordinals Inscription](https://docs.ordinals.com/inscriptions.html). @@ -63,7 +47,9 @@ fn create_envelope(mime: &[u8], data: &[u8], internal_key: PublicKey) -> Result< // Create MIME buffer. let mut mime_buf = PushBytesBuf::new(); - mime_buf.extend_from_slice(mime).map_err(|_| Error::Todo)?; + mime_buf + .extend_from_slice(mime) + .map_err(|_| Error::from(Proto::Error::Error_ordinal_mime_type_too_large))?; // Create an Ordinals Inscription. let mut builder = ScriptBuf::builder() @@ -89,7 +75,9 @@ fn create_envelope(mime: &[u8], data: &[u8], internal_key: PublicKey) -> Result< for chunk in data.chunks(520) { // Create data buffer. let mut data_buf = PushBytesBuf::new(); - data_buf.extend_from_slice(chunk).map_err(|_| Error::Todo)?; + data_buf + .extend_from_slice(chunk) + .map_err(|_| Error::from(Proto::Error::Error_ordinal_payload_too_large))?; // Push buffer builder = builder.push_slice(data_buf); @@ -113,3 +101,25 @@ fn create_envelope(mime: &[u8], data: &[u8], internal_key: PublicKey) -> Result< Ok(TaprootProgram { script, spend_info }) } + +pub struct OrdinalNftInscription(OrdinalsInscription); + +impl OrdinalNftInscription { + // Constructs an [Ordinal inscription] with a given MIME type. Common MIME + // types are: + // * "application/json", + // * "application/pdf", + // * "image/gif", + // * "image/jpeg", + // * "image/png", + // * "text/plain;charset=utf-8" + // * ... + // + // [Ordinal inscription]: https://docs.ordinals.com/inscriptions.html + pub fn new(mime_type: &[u8], data: &[u8], recipient: PublicKey) -> Result { + OrdinalsInscription::new(mime_type, data, recipient).map(OrdinalNftInscription) + } + pub fn inscription(&self) -> &OrdinalsInscription { + &self.0 + } +} diff --git a/rust/tw_bitcoin/src/modules/transactions/output_builder.rs b/rust/tw_bitcoin/src/modules/transactions/output_builder.rs new file mode 100644 index 00000000000..72fef4c0c39 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/transactions/output_builder.rs @@ -0,0 +1,371 @@ +use std::str::FromStr; + +use super::brc20::{BRC20TransferInscription, Brc20Ticker}; +use super::OrdinalNftInscription; +use crate::aliases::*; +use crate::{Error, Result}; +use bitcoin::address::{Payload, WitnessVersion}; +use bitcoin::key::TweakedPublicKey; +use bitcoin::taproot::{LeafVersion, TapNodeHash}; +use bitcoin::{Address, PubkeyHash, ScriptBuf, ScriptHash, WPubkeyHash, WScriptHash}; +use secp256k1::hashes::Hash; +use secp256k1::XOnlyPublicKey; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; + +pub struct OutputBuilder; + +// Convenience varibles used solely for readability. +const NO_CONTROL_BLOCK: Option> = None; +const NO_TAPROOT_PAYLOAD: Option> = None; + +impl OutputBuilder { + /// Creates the spending condition (_scriptPubkey_) for a given output. + pub fn utxo_from_proto( + output: &Proto::Output<'_>, + ) -> Result> { + let secp = secp256k1::Secp256k1::new(); + + let (script_pubkey, control_block, taproot_payload) = match &output.to_recipient { + // Script spending condition was passed on directly. + ProtoOutputRecipient::custom_script_pubkey(script) => ( + ScriptBuf::from_bytes(script.to_vec()), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ), + // Process builder methods. We construct the Script spending + // conditions by using the specified parameters. + ProtoOutputRecipient::builder(builder) => match &builder.variant { + ProtoOutputBuilder::p2sh(script_or_hash) => { + let script_hash = redeem_script_or_hash(script_or_hash)?; + ( + ScriptBuf::new_p2sh(&script_hash), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::p2pkh(pubkey_or_hash) => { + let pubkey_hash = pubkey_hash_from_proto(pubkey_or_hash)?; + ( + ScriptBuf::new_p2pkh(&pubkey_hash), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::p2wsh(script_or_hash) => { + let wscript_hash = witness_redeem_script_or_hash(script_or_hash)?; + ( + ScriptBuf::new_v0_p2wsh(&wscript_hash), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::p2wpkh(pubkey_or_hash) => { + let wpubkey_hash = witness_pubkey_hash_from_proto(pubkey_or_hash)?; + ( + ScriptBuf::new_v0_p2wpkh(&wpubkey_hash), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::p2tr_key_path(pubkey) => { + let pubkey = bitcoin::PublicKey::from_slice(pubkey.as_ref())?; + let xonly = XOnlyPublicKey::from(pubkey.inner); + ( + ScriptBuf::new_v1_p2tr(&secp, xonly, None), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::p2tr_script_path(complex) => { + let node_hash = TapNodeHash::from_slice(complex.merkle_root.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_taproot_root))?; + + let pubkey = bitcoin::PublicKey::from_slice(complex.internal_key.as_ref())?; + let xonly = XOnlyPublicKey::from(pubkey.inner); + + ( + ScriptBuf::new_v1_p2tr(&secp, xonly, Some(node_hash)), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::p2tr_dangerous_assume_tweaked(tweaked_pubkey) => { + let xonly = XOnlyPublicKey::from_slice(tweaked_pubkey).map_err(|_| { + Error::from(Proto::Error::Error_invalid_taproot_tweaked_pubkey) + })?; + + let tweaked = TweakedPublicKey::dangerous_assume_tweaked(xonly); + + ( + ScriptBuf::new_v1_p2tr_tweaked(tweaked), + NO_CONTROL_BLOCK, + NO_TAPROOT_PAYLOAD, + ) + }, + ProtoOutputBuilder::ordinal_inscribe(ordinal) => { + let pubkey = bitcoin::PublicKey::from_slice(ordinal.inscribe_to.as_ref())?; + let xonly = XOnlyPublicKey::from(pubkey.inner); + let mime_type = ordinal.mime_type.as_ref(); + let data = ordinal.payload.as_ref(); + + let nft = OrdinalNftInscription::new(mime_type.as_bytes(), data, pubkey) + .expect("badly constructed Ordinal inscription"); + + // Construct the control block. + let control_block = nft + .inscription() + .spend_info() + .control_block(&( + nft.inscription().taproot_program().to_owned(), + LeafVersion::TapScript, + )) + .expect("badly constructed control block"); + + // Construct the merkle root. + let merkle_root = nft + .inscription() + .spend_info() + .merkle_root() + .expect("badly constructed Taproot merkle root"); + + ( + ScriptBuf::new_v1_p2tr(&secp, xonly, Some(merkle_root)), + Some(control_block.serialize()), + Some(nft.inscription().taproot_program().to_vec()), + ) + }, + ProtoOutputBuilder::brc20_inscribe(brc20) => { + let pubkey = bitcoin::PublicKey::from_slice(brc20.inscribe_to.as_ref())?; + let xonly = XOnlyPublicKey::from(pubkey.inner); + + let ticker = Brc20Ticker::new(brc20.ticker.to_string())?; + let transfer = + BRC20TransferInscription::new(pubkey, ticker, brc20.transfer_amount) + .expect("invalid BRC20 transfer construction"); + + // Construct the control block. + let control_block = transfer + .inscription() + .spend_info() + .control_block(&( + transfer.inscription().taproot_program().to_owned(), + LeafVersion::TapScript, + )) + .expect("badly constructed control block"); + + // Construct the merkle root. + let merkle_root = transfer + .inscription() + .spend_info() + .merkle_root() + .expect("badly constructed Taproot merkle root"); + + ( + ScriptBuf::new_v1_p2tr(&secp, xonly, Some(merkle_root)), + Some(control_block.serialize()), + Some(transfer.inscription().taproot_program().to_vec()), + ) + }, + ProtoOutputBuilder::None => { + return Err(Error::from(Proto::Error::Error_missing_output_builder)) + }, + }, + // We derive the transaction type from the address. + ProtoOutputRecipient::from_address(addr) => { + let proto = output_from_address(output.value, addr.as_ref())?; + + // Recursive call, will initiate the appropraite builder. + return Self::utxo_from_proto(&proto); + }, + ProtoOutputRecipient::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + let utxo = Proto::mod_PreSigningOutput::TxOut { + value: output.value, + script_pubkey: script_pubkey.to_vec().into(), + control_block: control_block.map(|cb| cb.into()).unwrap_or_default(), + taproot_payload: taproot_payload.map(|cb| cb.into()).unwrap_or_default(), + }; + + Ok(utxo) + } +} + +// Convenience helper function. +fn redeem_script_or_hash( + script_or_hash: &Proto::mod_Output::OutputRedeemScriptOrHash, +) -> Result { + let pubkey_hash = match &script_or_hash.variant { + ProtoRedeemScriptOrHash::hash(hash) => ScriptHash::from_slice(hash.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_redeem_script))?, + ProtoRedeemScriptOrHash::redeem_script(script) => { + ScriptBuf::from_bytes(script.to_vec()).script_hash() + }, + ProtoRedeemScriptOrHash::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + Ok(pubkey_hash) +} + +// Convenience helper function. +fn witness_redeem_script_or_hash( + script_or_hash: &Proto::mod_Output::OutputRedeemScriptOrHash, +) -> Result { + let pubkey_hash = match &script_or_hash.variant { + ProtoRedeemScriptOrHash::hash(hash) => WScriptHash::from_slice(hash) + .map_err(|_| Error::from(Proto::Error::Error_invalid_witness_redeem_script_hash))?, + ProtoRedeemScriptOrHash::redeem_script(script) => { + ScriptBuf::from_bytes(script.to_vec()).wscript_hash() + }, + ProtoRedeemScriptOrHash::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + Ok(pubkey_hash) +} + +// Convenience helper function. +fn pubkey_hash_from_proto(pubkey_or_hash: &Proto::ToPublicKeyOrHash) -> Result { + let pubkey_hash = match &pubkey_or_hash.to_address { + ProtoPubkeyOrHash::hash(hash) => PubkeyHash::from_slice(hash.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_pubkey_hash))?, + ProtoPubkeyOrHash::pubkey(pubkey) => { + bitcoin::PublicKey::from_slice(pubkey.as_ref())?.pubkey_hash() + }, + ProtoPubkeyOrHash::None => return Err(Error::from(Proto::Error::Error_missing_recipient)), + }; + + Ok(pubkey_hash) +} + +// Convenience helper function. +fn witness_pubkey_hash_from_proto( + pubkey_or_hash: &Proto::ToPublicKeyOrHash, +) -> Result { + let wpubkey_hash = match &pubkey_or_hash.to_address { + ProtoPubkeyOrHash::hash(hash) => WPubkeyHash::from_slice(hash.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_witness_pubkey_hash))?, + ProtoPubkeyOrHash::pubkey(pubkey) => bitcoin::PublicKey::from_slice(pubkey.as_ref())? + .wpubkey_hash() + .ok_or_else(|| Error::from(Proto::Error::Error_invalid_witness_pubkey_hash))?, + ProtoPubkeyOrHash::None => return Err(Error::from(Proto::Error::Error_missing_recipient)), + }; + + Ok(wpubkey_hash) +} + +// Derives the P2* output from the given address. +fn output_from_address(value: u64, addr: &str) -> Result> { + let string = String::from_utf8(addr.to_vec()) + .map_err(|_| Error::from(Proto::Error::Error_bad_address_recipient))?; + + let addr = Address::from_str(&string) + .map_err(|_| Error::from(Proto::Error::Error_bad_address_recipient))? + .require_network(bitcoin::Network::Bitcoin) + .map_err(|_| Error::from(Proto::Error::Error_bad_address_recipient))?; + + let proto = match addr.payload { + // Identified a "PubkeyHash" address (i.e. P2PKH). + Payload::PubkeyHash(pubkey_hash) => Proto::Output { + value, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2pkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::hash(pubkey_hash.to_vec().into()), + }), + }), + }, + // Identified a witness program (i.e. Segwit or Taproot). + Payload::WitnessProgram(progam) => { + match progam.version() { + // Identified version 0, i.e. Segwit + WitnessVersion::V0 => { + let payload = progam.program().as_bytes().to_vec(); + + // Check for P2WPKH. + if payload.len() == 20 { + return Ok(Proto::Output { + value, + to_recipient: ProtoOutputRecipient::builder( + Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::hash( + // Payload is the WPubkey hash. + payload.into(), + ), + }), + }, + ), + }); + } + + // Check for P2WSH. + if payload.len() == 32 { + return Ok(Proto::Output { + value, + to_recipient: ProtoOutputRecipient::builder( + Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wsh( + Proto::mod_Output::OutputRedeemScriptOrHash { + variant: ProtoOutputRedeemScriptOrHashBuilder::hash( + // Payload is the WScript hash. + payload.to_vec().into(), + ), + }, + ), + }, + ), + }); + } + + return Err(Error::from(Proto::Error::Error_bad_address_recipient)); + }, + // Identified version 1, i.e P2TR key-path (Taproot) + WitnessVersion::V1 => { + let pubkey = progam.program().as_bytes().to_vec(); + if pubkey.len() != 32 { + return Err(Error::from(Proto::Error::Error_bad_address_recipient)); + } + + Proto::Output { + value, + to_recipient: ProtoOutputRecipient::builder( + Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2tr_dangerous_assume_tweaked( + pubkey.into(), + ), + }, + ), + } + }, + _ => { + return Err(Error::from( + Proto::Error::Error_unsupported_address_recipient, + )) + }, + } + }, + Payload::ScriptHash(script_hash) => Proto::Output { + value, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2sh(Proto::mod_Output::OutputRedeemScriptOrHash { + variant: ProtoOutputRedeemScriptOrHashBuilder::hash( + script_hash.to_vec().into(), + ), + }), + }), + }, + _ => { + return Err(Error::from( + Proto::Error::Error_unsupported_address_recipient, + )) + }, + }; + + Ok(proto) +} diff --git a/rust/tw_bitcoin/src/modules/utils.rs b/rust/tw_bitcoin/src/modules/utils.rs new file mode 100644 index 00000000000..f64f9bfb29e --- /dev/null +++ b/rust/tw_bitcoin/src/modules/utils.rs @@ -0,0 +1,188 @@ +use crate::aliases::*; +use crate::{Error, Result}; +use tw_proto::BitcoinV2::Proto; + +// Convenience function: our protobuf library wraps certain types (such as +// `bytes`) in `Cow`, but given that calling `clone()` on a `Cow::Borrowed(T)` +// does not actually clone the underlying data (but the smart pointer instead), +// we must hard-clone individual fields manually. This is unfortunately required +// due to how protobuf library works and our use of the 'static constraints. +pub fn hard_clone_proto_input(proto: Proto::Input<'_>) -> Result> { + fn new_builder(variant: ProtoInputBuilder<'static>) -> ProtoInputRecipient<'static> { + ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { variant }) + } + + let to_recipient = match proto.to_recipient { + ProtoInputRecipient::builder(builder) => match builder.variant { + ProtoInputBuilder::p2sh(script) => { + new_builder(ProtoInputBuilder::p2sh(script.to_vec().into())) + }, + ProtoInputBuilder::p2pkh(script) => { + new_builder(ProtoInputBuilder::p2pkh(script.to_vec().into())) + }, + ProtoInputBuilder::p2wsh(script) => { + new_builder(ProtoInputBuilder::p2wsh(script.to_vec().into())) + }, + ProtoInputBuilder::p2wpkh(script) => { + new_builder(ProtoInputBuilder::p2wpkh(script.to_vec().into())) + }, + ProtoInputBuilder::p2tr_key_path(key_path) => new_builder( + ProtoInputBuilder::p2tr_key_path(Proto::mod_Input::InputTaprootKeyPath { + one_prevout: key_path.one_prevout, + public_key: key_path.public_key.to_vec().into(), + }), + ), + ProtoInputBuilder::p2tr_script_path(script) => new_builder( + ProtoInputBuilder::p2tr_script_path(Proto::mod_Input::InputTaprootScriptPath { + one_prevout: script.one_prevout, + payload: script.payload.to_vec().into(), + control_block: script.control_block.to_vec().into(), + }), + ), + ProtoInputBuilder::brc20_inscribe(brc20) => new_builder( + ProtoInputBuilder::brc20_inscribe(Proto::mod_Input::InputBrc20Inscription { + one_prevout: brc20.one_prevout, + inscribe_to: brc20.inscribe_to.to_vec().into(), + ticker: brc20.ticker.to_string().into(), + transfer_amount: brc20.transfer_amount, + }), + ), + ProtoInputBuilder::ordinal_inscribe(ord) => new_builder( + ProtoInputBuilder::ordinal_inscribe(Proto::mod_Input::InputOrdinalInscription { + one_prevout: ord.one_prevout, + inscribe_to: ord.inscribe_to.to_vec().into(), + mime_type: ord.mime_type.to_string().into(), + payload: ord.payload.to_vec().into(), + }), + ), + ProtoInputBuilder::None => { + return Err(Error::from(Proto::Error::Error_missing_input_builder)) + }, + }, + ProtoInputRecipient::custom_script(custom) => { + ProtoInputRecipient::custom_script(Proto::mod_Input::InputScriptWitness { + script_pubkey: custom.script_pubkey.to_vec().into(), + script_sig: custom.script_sig.to_vec().into(), + witness_items: custom + .witness_items + .iter() + .map(|item| item.to_vec().into()) + .collect(), + signing_method: custom.signing_method, + }) + }, + ProtoInputRecipient::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + Ok(Proto::Input { + private_key: proto.private_key.to_vec().into(), + txid: proto.txid.to_vec().into(), + to_recipient, + ..proto + }) +} + +// Convenience function: our protobuf library wraps certain types (such as +// `bytes`) in `Cow`, but given that calling `clone()` on a `Cow::Borrowed(T)` +// does not actually clone the underlying data (but the smart pointer instead), +// we must hard-clone individual fields manually. This is unfortunately required +// due to how protobuf library works and our use of the 'static constraints. +pub fn hard_clone_proto_output(proto: Proto::Output<'_>) -> Result> { + fn new_builder(variant: ProtoOutputBuilder<'static>) -> ProtoOutputRecipient<'static> { + ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { variant }) + } + + fn new_script_or_hash( + proto: Proto::mod_Output::OutputRedeemScriptOrHash<'_>, + ) -> Result> { + let variant = match proto.variant { + ProtoOutputRedeemScriptOrHashBuilder::redeem_script(script) => { + ProtoOutputRedeemScriptOrHashBuilder::redeem_script(script.to_vec().into()) + }, + ProtoOutputRedeemScriptOrHashBuilder::hash(hash) => { + ProtoOutputRedeemScriptOrHashBuilder::hash(hash.to_vec().into()) + }, + ProtoOutputRedeemScriptOrHashBuilder::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + Ok(Proto::mod_Output::OutputRedeemScriptOrHash { variant }) + } + + fn new_pubkey_or_hash( + proto: Proto::ToPublicKeyOrHash<'_>, + ) -> Result> { + let to_address = match proto.to_address { + ProtoPubkeyOrHash::pubkey(pubkey) => ProtoPubkeyOrHash::pubkey(pubkey.to_vec().into()), + ProtoPubkeyOrHash::hash(hash) => ProtoPubkeyOrHash::hash(hash.to_vec().into()), + ProtoPubkeyOrHash::None => { + return Err(Error::from(Proto::Error::Error_missing_recipient)) + }, + }; + + Ok(Proto::ToPublicKeyOrHash { to_address }) + } + + let to_recipient = match proto.to_recipient { + ProtoOutputRecipient::builder(builder) => match builder.variant { + ProtoOutputBuilder::p2sh(script_or_hash) => new_builder(ProtoOutputBuilder::p2sh( + new_script_or_hash(script_or_hash)?, + )), + ProtoOutputBuilder::p2pkh(pubkey_or_hash) => new_builder(ProtoOutputBuilder::p2pkh( + new_pubkey_or_hash(pubkey_or_hash)?, + )), + ProtoOutputBuilder::p2wsh(script_or_hash) => new_builder(ProtoOutputBuilder::p2wsh( + new_script_or_hash(script_or_hash)?, + )), + ProtoOutputBuilder::p2wpkh(pubkey_or_hash) => new_builder(ProtoOutputBuilder::p2wpkh( + new_pubkey_or_hash(pubkey_or_hash)?, + )), + ProtoOutputBuilder::p2tr_key_path(pubkey) => { + new_builder(ProtoOutputBuilder::p2tr_key_path(pubkey.to_vec().into())) + }, + ProtoOutputBuilder::p2tr_script_path(script_path) => new_builder( + ProtoOutputBuilder::p2tr_script_path(Proto::mod_Output::OutputTaprootScriptPath { + internal_key: script_path.internal_key.to_vec().into(), + merkle_root: script_path.merkle_root.to_vec().into(), + }), + ), + ProtoOutputBuilder::p2tr_dangerous_assume_tweaked(tweaked) => new_builder( + ProtoOutputBuilder::p2tr_dangerous_assume_tweaked(tweaked.to_vec().into()), + ), + ProtoOutputBuilder::brc20_inscribe(brc20) => new_builder( + ProtoOutputBuilder::brc20_inscribe(Proto::mod_Output::OutputBrc20Inscription { + inscribe_to: brc20.inscribe_to.to_vec().into(), + ticker: brc20.ticker.to_string().into(), + transfer_amount: brc20.transfer_amount, + }), + ), + ProtoOutputBuilder::ordinal_inscribe(ord) => new_builder( + ProtoOutputBuilder::ordinal_inscribe(Proto::mod_Output::OutputOrdinalInscription { + inscribe_to: ord.inscribe_to.to_vec().into(), + mime_type: ord.mime_type.to_string().into(), + payload: ord.payload.to_vec().into(), + }), + ), + ProtoOutputBuilder::None => { + return Err(Error::from(Proto::Error::Error_missing_output_builder)) + }, + }, + ProtoOutputRecipient::custom_script_pubkey(custom) => { + ProtoOutputRecipient::custom_script_pubkey(custom.to_vec().into()) + }, + ProtoOutputRecipient::from_address(address) => { + ProtoOutputRecipient::from_address(address.to_string().into()) + }, + ProtoOutputRecipient::None => { + return Err(Error::from(Proto::Error::Error_missing_output_builder)) + }, + }; + + Ok(Proto::Output { + value: proto.value, + to_recipient, + }) +} diff --git a/rust/tw_bitcoin/src/nft.rs b/rust/tw_bitcoin/src/nft.rs deleted file mode 100644 index 8d25b55fd2a..00000000000 --- a/rust/tw_bitcoin/src/nft.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::ordinals::OrdinalsInscription; -use crate::{Recipient, Result}; -use bitcoin::PublicKey; - -pub struct OrdinalNftInscription(OrdinalsInscription); - -impl OrdinalNftInscription { - // Constructs an [Ordinal inscription] with a given MIME type. Common MIME - // types are: - // * "application/json", - // * "application/pdf", - // * "image/gif", - // * "image/jpeg", - // * "image/png", - // * "text/plain;charset=utf-8" - // * ... - // - // [Ordinal inscription]: https://docs.ordinals.com/inscriptions.html - pub fn new(mime_type: &[u8], data: &[u8], recipient: Recipient) -> Result { - OrdinalsInscription::new(mime_type, data, recipient).map(OrdinalNftInscription) - } - pub fn inscription(&self) -> &OrdinalsInscription { - &self.0 - } -} diff --git a/rust/tw_bitcoin/src/output/mod.rs b/rust/tw_bitcoin/src/output/mod.rs deleted file mode 100644 index a93ccda8c87..00000000000 --- a/rust/tw_bitcoin/src/output/mod.rs +++ /dev/null @@ -1,75 +0,0 @@ -mod p2pkh; -mod p2tr_key_path; -mod p2tr_script_path; -mod p2wpkh; - -pub use p2pkh::*; -pub use p2tr_key_path::*; -pub use p2tr_script_path::*; -pub use p2wpkh::*; - -#[derive(Debug, Clone)] -pub enum TxOutput { - P2PKH(TxOutputP2PKH), - P2WPKH(TxOutputP2WPKH), - P2TRKeyPath(TxOutputP2TRKeyPath), - P2TRScriptPath(TXOutputP2TRScriptPath), -} - -impl TxOutput { - pub fn satoshis(&self) -> u64 { - match self { - TxOutput::P2PKH(p) => p.satoshis, - TxOutput::P2WPKH(p) => p.satoshis, - TxOutput::P2TRKeyPath(p) => p.satoshis, - TxOutput::P2TRScriptPath(p) => p.satoshis, - } - } -} - -impl From for TxOutput { - fn from(output: TxOutputP2PKH) -> Self { - TxOutput::P2PKH(output) - } -} - -impl From for TxOutput { - fn from(output: TxOutputP2TRKeyPath) -> Self { - TxOutput::P2TRKeyPath(output) - } -} - -impl From for TxOutput { - fn from(output: TxOutputP2WPKH) -> Self { - TxOutput::P2WPKH(output) - } -} - -impl From for TxOutput { - fn from(output: TXOutputP2TRScriptPath) -> Self { - TxOutput::P2TRScriptPath(output) - } -} - -impl From for bitcoin::TxOut { - fn from(out: TxOutput) -> Self { - match out { - TxOutput::P2PKH(p) => Self { - value: p.satoshis, - script_pubkey: p.script_pubkey, - }, - TxOutput::P2WPKH(p) => Self { - value: p.satoshis, - script_pubkey: p.script_pubkey, - }, - TxOutput::P2TRKeyPath(p) => Self { - value: p.satoshis, - script_pubkey: p.script_pubkey, - }, - TxOutput::P2TRScriptPath(p) => Self { - value: p.satoshis, - script_pubkey: p.script_pubkey, - }, - } - } -} diff --git a/rust/tw_bitcoin/src/output/p2pkh.rs b/rust/tw_bitcoin/src/output/p2pkh.rs deleted file mode 100644 index 168d260339d..00000000000 --- a/rust/tw_bitcoin/src/output/p2pkh.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::{Error, Recipient, Result}; -use bitcoin::{PubkeyHash, ScriptBuf}; - -#[derive(Debug, Clone)] -pub struct TxOutputP2PKH { - pub(crate) satoshis: u64, - pub(crate) script_pubkey: ScriptBuf, -} - -impl TxOutputP2PKH { - pub fn new(satoshis: u64, recipient: impl Into>) -> Self { - let recipient: Recipient = recipient.into(); - - TxOutputP2PKH { - satoshis, - script_pubkey: ScriptBuf::new_p2pkh(recipient.pubkey_hash()), - } - } - pub fn new_with_script(satoshis: u64, script_pubkey: ScriptBuf) -> Self { - TxOutputP2PKH { - satoshis, - script_pubkey, - } - } - pub fn builder() -> TxOutputP2PKHBuilder { - TxOutputP2PKHBuilder::new() - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxOutputP2PKHBuilder { - satoshis: Option, - recipient: Option>, -} - -impl TxOutputP2PKHBuilder { - pub fn new() -> TxOutputP2PKHBuilder { - TxOutputP2PKHBuilder { - satoshis: None, - recipient: None, - } - } - pub fn satoshis(mut self, satoshis: u64) -> TxOutputP2PKHBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn recipient( - mut self, - recipient: impl Into>, - ) -> TxOutputP2PKHBuilder { - self.recipient = Some(recipient.into()); - self - } - pub fn build(self) -> Result { - Ok(TxOutputP2PKH::new( - self.satoshis.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/output/p2tr_key_path.rs b/rust/tw_bitcoin/src/output/p2tr_key_path.rs deleted file mode 100644 index 94e166fd4ff..00000000000 --- a/rust/tw_bitcoin/src/output/p2tr_key_path.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{Error, Recipient, Result}; -use bitcoin::key::TweakedPublicKey; -use bitcoin::script::ScriptBuf; - -#[derive(Debug, Clone)] -pub struct TxOutputP2TRKeyPath { - pub(crate) satoshis: u64, - pub(crate) script_pubkey: ScriptBuf, -} - -impl TxOutputP2TRKeyPath { - pub fn new(satoshis: u64, recipient: Recipient) -> Self { - TxOutputP2TRKeyPath { - satoshis, - script_pubkey: ScriptBuf::new_v1_p2tr_tweaked(recipient.tweaked_pubkey()), - } - } - pub fn new_with_script(satoshis: u64, script_pubkey: ScriptBuf) -> Self { - TxOutputP2TRKeyPath { - satoshis, - script_pubkey, - } - } - pub fn builder() -> TxOutputP2TRKeyPathBuilder { - TxOutputP2TRKeyPathBuilder::new() - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxOutputP2TRKeyPathBuilder { - satoshis: Option, - recipient: Option>, -} - -impl TxOutputP2TRKeyPathBuilder { - pub fn new() -> Self { - Self::default() - } - pub fn satoshis(mut self, satoshis: u64) -> TxOutputP2TRKeyPathBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn recipient( - mut self, - recipient: impl Into>, - ) -> TxOutputP2TRKeyPathBuilder { - self.recipient = Some(recipient.into()); - self - } - pub fn build(self) -> Result { - Ok(TxOutputP2TRKeyPath::new( - self.satoshis.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/output/p2tr_script_path.rs b/rust/tw_bitcoin/src/output/p2tr_script_path.rs deleted file mode 100644 index cf7e4f55a8c..00000000000 --- a/rust/tw_bitcoin/src/output/p2tr_script_path.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::{Error, Recipient, Result}; -use bitcoin::key::PublicKey; -use bitcoin::script::ScriptBuf; -use bitcoin::secp256k1; -use bitcoin::taproot::{TapNodeHash, TaprootSpendInfo}; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TaprootScript { - pub pubkey: PublicKey, - pub merkle_root: TapNodeHash, -} - -#[derive(Debug, Clone)] -pub struct TaprootProgram { - pub script: ScriptBuf, - pub spend_info: TaprootSpendInfo, -} - -#[derive(Debug, Clone)] -pub struct TXOutputP2TRScriptPath { - pub(crate) satoshis: u64, - pub(crate) script_pubkey: ScriptBuf, -} - -impl TXOutputP2TRScriptPath { - pub fn new(satoshis: u64, recipient: &Recipient) -> Self { - let script_pubkey = ScriptBuf::new_v1_p2tr( - &secp256k1::Secp256k1::new(), - recipient.untweaked_pubkey(), - Some(recipient.merkle_root()), - ); - - TXOutputP2TRScriptPath { - satoshis, - script_pubkey, - } - } - pub fn new_with_script(satoshis: u64, script_pubkey: ScriptBuf) -> Self { - TXOutputP2TRScriptPath { - satoshis, - script_pubkey, - } - } - pub fn builder() -> TxOutputP2TRScriptPathBuilder { - TxOutputP2TRScriptPathBuilder::new() - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxOutputP2TRScriptPathBuilder { - satoshis: Option, - recipient: Option>, -} - -impl TxOutputP2TRScriptPathBuilder { - pub fn new() -> Self { - Self::default() - } - pub fn satoshis(mut self, satoshis: u64) -> TxOutputP2TRScriptPathBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn recipient( - mut self, - recipient: Recipient, - ) -> TxOutputP2TRScriptPathBuilder { - self.recipient = Some(recipient); - self - } - pub fn build(self) -> Result { - let recipient = self.recipient.ok_or(Error::Todo)?; - Ok(TXOutputP2TRScriptPath::new( - self.satoshis.ok_or(Error::Todo)?, - &recipient, - )) - } -} diff --git a/rust/tw_bitcoin/src/output/p2wpkh.rs b/rust/tw_bitcoin/src/output/p2wpkh.rs deleted file mode 100644 index 82877517874..00000000000 --- a/rust/tw_bitcoin/src/output/p2wpkh.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{Error, Recipient, Result}; -use bitcoin::{ScriptBuf, WPubkeyHash}; - -#[derive(Debug, Clone)] -pub struct TxOutputP2WPKH { - pub(crate) satoshis: u64, - pub(crate) script_pubkey: ScriptBuf, -} - -impl TxOutputP2WPKH { - pub fn new(satoshis: u64, recipient: Recipient) -> Self { - TxOutputP2WPKH { - satoshis, - script_pubkey: ScriptBuf::new_v0_p2wpkh(recipient.wpubkey_hash()), - } - } - pub fn new_with_script(satoshis: u64, script_pubkey: ScriptBuf) -> Self { - TxOutputP2WPKH { - satoshis, - script_pubkey, - } - } - pub fn builder() -> TxOutputP2WPKHBuilder { - TxOutputP2WPKHBuilder::new() - } -} - -#[derive(Debug, Clone, Default)] -pub struct TxOutputP2WPKHBuilder { - satoshis: Option, - recipient: Option>, -} - -impl TxOutputP2WPKHBuilder { - pub fn new() -> TxOutputP2WPKHBuilder { - Self::default() - } - pub fn satoshis(mut self, satoshis: u64) -> TxOutputP2WPKHBuilder { - self.satoshis = Some(satoshis); - self - } - pub fn recipient(mut self, recipient: Recipient) -> TxOutputP2WPKHBuilder { - self.recipient = Some(recipient); - self - } - pub fn build(self) -> Result { - Ok(TxOutputP2WPKH::new( - self.satoshis.ok_or(Error::Todo)?, - self.recipient.ok_or(Error::Todo)?, - )) - } -} diff --git a/rust/tw_bitcoin/src/recipient.rs b/rust/tw_bitcoin/src/recipient.rs deleted file mode 100644 index f284ac84ab3..00000000000 --- a/rust/tw_bitcoin/src/recipient.rs +++ /dev/null @@ -1,260 +0,0 @@ -use std::str::FromStr; - -use crate::output::TaprootScript; -use crate::{tweak_pubkey, Error, Result}; -use bitcoin::key::{KeyPair, PublicKey, TweakedPublicKey, UntweakedPublicKey}; -use bitcoin::taproot::TapNodeHash; -use bitcoin::{ - secp256k1::{self, XOnlyPublicKey}, - Address, Network, PubkeyHash, WPubkeyHash, -}; - -/// This type is used to specify the recipient of a Bitcoin transaction, -/// depending on the required information that's required. For example, P2PKH -/// only requires the public key hash (`Recipient`), while P2TR -/// key-path requires the actual (tweaked) public key (`Recipient`). -/// -/// The recipient can easily downgrade, such as -/// ```rust,ignore -/// let pubkey = Recipient::::from_keypair(keypair); -/// let hash: Recipient = pubkey.into(); -/// ``` -/// -/// But it cannot, for example, derive a public key from just the hash. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Recipient { - inner: T, -} - -impl Recipient { - pub fn from_keypair(keypair: &KeyPair) -> Self { - Recipient { - inner: PublicKey::new(keypair.public_key()), - } - } - pub fn public_key(&self) -> PublicKey { - self.inner - } - pub fn pubkey_hash(&self) -> PubkeyHash { - PubkeyHash::from(self.inner) - } - pub fn wpubkey_hash(&self) -> Result { - self.inner.wpubkey_hash().ok_or(Error::Todo) - } - pub fn tweaked_pubkey(&self) -> TweakedPublicKey { - tweak_pubkey(self.inner) - } - pub fn untweaked_pubkey(&self) -> UntweakedPublicKey { - XOnlyPublicKey::from(self.inner.inner) - } - pub fn legacy_address(&self, network: Network) -> Address { - Address::p2pkh(&self.inner, network) - } - pub fn segwit_address(&self, network: Network) -> Result
{ - Address::p2wpkh(&self.inner, network).map_err(|_| Error::Todo) - } - pub fn taproot_address(&self, network: Network) -> Address { - let untweaked = UntweakedPublicKey::from(self.inner.inner); - Address::p2tr(&secp256k1::Secp256k1::new(), untweaked, None, network) - } - pub fn legacy_address_string(&self, network: Network) -> String { - self.legacy_address(network).to_string() - } - pub fn segwit_address_string(&self, network: Network) -> Result { - self.segwit_address(network).map(|addr| addr.to_string()) - } - pub fn taproot_address_string(&self, network: Network) -> String { - self.taproot_address(network).to_string() - } -} - -impl Recipient { - pub fn pubkey_hash(&self) -> &PubkeyHash { - &self.inner - } -} - -impl Recipient { - pub fn from_slice(slice: &[u8]) -> Result { - Ok(Recipient { - inner: PublicKey::from_slice(slice) - .map_err(|_| Error::Todo)? - .wpubkey_hash() - .ok_or(Error::Todo)?, - }) - } - pub fn wpubkey_hash(&self) -> &WPubkeyHash { - &self.inner - } -} - -impl Recipient { - pub fn from_slice(slice: &[u8]) -> Result { - Ok(Recipient { - inner: PublicKey::from_slice(slice).map_err(|_| Error::Todo)?, - }) - } -} - -impl FromStr for Recipient { - type Err = Error; - - fn from_str(string: &str) -> Result { - Self::from_slice(string.as_bytes()) - } -} - -impl From for Recipient { - fn from(inner: PublicKey) -> Self { - Recipient { inner } - } -} - -impl From for Recipient { - fn from(keypair: KeyPair) -> Self { - Self::from(&keypair) - } -} - -impl From<&KeyPair> for Recipient { - fn from(keypair: &KeyPair) -> Self { - Recipient { - inner: PublicKey::new(keypair.public_key()), - } - } -} - -impl From for Recipient { - fn from(pubkey: PublicKey) -> Self { - let tweaked = tweak_pubkey(pubkey); - Recipient { inner: tweaked } - } -} - -impl From> for Recipient { - fn from(recipient: Recipient) -> Self { - Recipient { - inner: Self::from(recipient.inner).inner, - } - } -} - -impl From for Recipient { - fn from(keypair: KeyPair) -> Self { - Self::from(&keypair) - } -} - -impl From<&KeyPair> for Recipient { - fn from(keypair: &KeyPair) -> Self { - let pk = Recipient::::from(keypair); - let tweaked = Recipient::::from(pk); - - Recipient { - inner: tweaked.inner, - } - } -} - -impl TryFrom for Recipient { - type Error = Error; - - fn try_from(pubkey: PublicKey) -> Result { - Ok(Recipient { - inner: pubkey.wpubkey_hash().unwrap(), - }) - } -} - -impl TryFrom> for Recipient { - type Error = Error; - - fn try_from(recipient: Recipient) -> Result { - Ok(Recipient { - inner: Self::try_from(recipient.inner)?.inner, - }) - } -} - -impl TryFrom<&KeyPair> for Recipient { - type Error = Error; - - fn try_from(keypair: &KeyPair) -> Result { - let pubkey = Recipient::::from(keypair); - Self::try_from(pubkey.inner) - } -} - -impl TryFrom for Recipient { - type Error = Error; - - fn try_from(keypair: KeyPair) -> Result { - Self::try_from(&keypair) - } -} - -impl From for Recipient { - fn from(pubkey: PublicKey) -> Self { - Recipient { - inner: pubkey.into(), - } - } -} - -impl From> for Recipient { - fn from(recipient: Recipient) -> Self { - Recipient { - inner: Self::from(recipient.inner).inner, - } - } -} - -impl From for Recipient { - fn from(keypair: KeyPair) -> Self { - Self::from(&keypair) - } -} - -impl From<&KeyPair> for Recipient { - fn from(keypair: &KeyPair) -> Self { - let pk = Recipient::::from(keypair); - - Recipient { - inner: pk.inner.into(), - } - } -} - -impl Recipient { - pub fn tweaked_pubkey(&self) -> TweakedPublicKey { - self.inner - } -} - -impl Recipient { - pub fn from_keypair(keypair: &KeyPair, merkle_root: TapNodeHash) -> Self { - Recipient { - inner: TaprootScript { - pubkey: PublicKey::new(keypair.public_key()), - merkle_root, - }, - } - } - pub fn from_pubkey_recipient( - recipient: Recipient, - merkle_root: TapNodeHash, - ) -> Self { - Recipient { - inner: TaprootScript { - pubkey: recipient.inner, - merkle_root, - }, - } - } - pub fn untweaked_pubkey(&self) -> UntweakedPublicKey { - XOnlyPublicKey::from(self.inner.pubkey.inner) - } - pub fn merkle_root(&self) -> TapNodeHash { - self.inner.merkle_root - } -} diff --git a/rust/tw_bitcoin/src/tests/address.rs b/rust/tw_bitcoin/src/tests/address.rs deleted file mode 100644 index 032771bb1b7..00000000000 --- a/rust/tw_bitcoin/src/tests/address.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::{keypair_from_wif, Recipient}; -use bitcoin::{Network, PublicKey}; - -// This private key was used in a Bitcoin regtest environment. -pub const ALICE_WIF: &str = "cQUNzeMnF9xPPLqZhH7hMVYGwSuu3b78zznuc5UrxgXnYQBq6Bx1"; - -#[test] -fn addresses() { - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let recipient = Recipient::::from(&alice); - - assert_eq!( - recipient.legacy_address_string(Network::Bitcoin), - "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx" - ); - assert_eq!( - recipient.segwit_address_string(Network::Bitcoin).unwrap(), - "bc1qunq74p3h8425hr6wllevlvqqr6sezfxj262rff" - ); - assert_eq!( - recipient.taproot_address_string(Network::Bitcoin), - "bc1pwse34zfpvt344rvlt7tw0ngjtfh9xasc4q03avf0lk74jzjpzjuqaz7ks5" - ); -} diff --git a/rust/tw_bitcoin/src/tests/brc20_transfer.rs b/rust/tw_bitcoin/src/tests/brc20_transfer.rs deleted file mode 100644 index 40b8ff40837..00000000000 --- a/rust/tw_bitcoin/src/tests/brc20_transfer.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::{ - brc20::{BRC20TransferInscription, Ticker}, - keypair_from_wif, TXOutputP2TRScriptPath, TransactionBuilder, TxInputP2TRScriptPath, - TxInputP2WPKH, TxOutputP2WPKH, -}; -use bitcoin::Txid; -use std::str::FromStr; -use tw_encoding::hex; - -// Those private keys were used for Bitcoin mainnet tests and have a transaction -// history. BTC holdings have been emptied. -pub const ALICE_WIF: &str = "L4of5AJ6aKmvChg7gQ7m2RzHFgpWe5Uirmuey1fXJ1FtfmXj59LW"; -pub const BOB_WIF: &str = "L59WHi2hj1HnMAYaFyMqR4Z36HrUDTZQCixzTHachAxbUU9VUCjp"; - -pub const FULL_SATOSHIS: u64 = 26_400; -pub const MINER_FEE: u64 = 3_000; - -pub const BRC20_TICKER: &str = "oadf"; -pub const BRC20_AMOUNT: u64 = 20; -pub const BRC20_INSCRIBE_SATOSHIS: u64 = 7_000; -pub const BRC20_DUST_SATOSHIS: u64 = 546; - -pub const FOR_FEE_SATOSHIS: u64 = FULL_SATOSHIS - BRC20_INSCRIBE_SATOSHIS - MINER_FEE; - -// Used for the committing the Inscription. -// https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 -pub const COMMIT_TXID: &str = "8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008"; -pub const COMMIT_TX_RAW: &str = "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; - -// Used for revealing the Inscription. -// https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca -pub const REVEAL_TXID: &str = "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1"; -pub const REVEAL_TX_RAW: &str = "02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d0340de6fd13e43700f59876d305e5a4a5c41ad7ada10bc5a4e4bdd779eb0060c0a78ebae9c33daf77bb3725172edb5bd12e26f00c08f9263e480d53b93818138ad0b5b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; - -// Used for transfering the Inscription ("BRC20 transfer"). -// https://www.blockchain.com/explorer/transactions/btc/3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7 -pub use skip::*; -// We skip formatting for the `skip` module, re-exporting everything. -#[rustfmt::skip] -mod skip { -pub const TRANSFER_TXID_INSCRIPTION: &str = "7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca"; -pub const TRANSFER_TXID_FOR_FEES: &str = "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1"; -pub const TRANSFER_RAW: &str = "02000000000102ca3edda74a46877efa5364ab85947e148508713910ada23e147ea28926dc46700000000000ffffffffb11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790100000000ffffffff022202000000000000160014e891850afc55b64aa8247b2076f8894ebdf889015834000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d024830450221008798393eb0b7390217591a8c33abe18dd2f7ea7009766e0d833edeaec63f2ec302200cf876ff52e68dbaf108a3f6da250713a9b04949a8f1dcd1fb867b24052236950121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb0248304502210096bbb9d1f0596d69875646689e46f29485e8ceccacde9d0025db87fd96d3066902206d6de2dd69d965d28df3441b94c76e812384ab9297e69afe3480ee4031e1b2060121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; -} - -#[test] -fn brc20_transfer() { - let ticker = Ticker::new(BRC20_TICKER.to_string()).unwrap(); - - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let bob = keypair_from_wif(BOB_WIF).unwrap(); - - let txid = Txid::from_str(COMMIT_TXID).unwrap(); - - // # Make "available" tokens "transferable". - // Based on Bitcoin transaction: - // https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 - - // Commit transfer. - let input = TxInputP2WPKH::builder() - .txid(txid) - .vout(1) - .recipient(alice.try_into().unwrap()) - .satoshis(FULL_SATOSHIS) - .build() - .unwrap(); - - let transfer = BRC20TransferInscription::new(alice.into(), ticker, BRC20_AMOUNT).unwrap(); - - let output = TXOutputP2TRScriptPath::builder() - .recipient(transfer.inscription().recipient().clone()) - .satoshis(BRC20_INSCRIBE_SATOSHIS) - .build() - .unwrap(); - - let output_change = TxOutputP2WPKH::builder() - .recipient(alice.try_into().unwrap()) - .satoshis(FOR_FEE_SATOSHIS) - .build() - .unwrap(); - - let transaction = TransactionBuilder::new() - .add_input(input.into()) - .add_output(output.into()) - .add_output(output_change.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - // Encode the signed transaction. - let hex = hex::encode(&transaction, false); - assert_eq!(hex, COMMIT_TX_RAW); - - // # Reveal transfer. - // Based on Bitcoin transaction: - // https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca - - let txid = - Txid::from_str("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").unwrap(); - - let input = TxInputP2TRScriptPath::builder() - .txid(txid) - .vout(0) - .recipient(transfer.inscription().recipient().clone()) - .satoshis(BRC20_INSCRIBE_SATOSHIS) - .script(transfer.inscription().taproot_program().to_owned()) - .spend_info(transfer.inscription().spend_info().clone()) - .build() - .unwrap(); - - let output = TxOutputP2WPKH::builder() - .recipient(alice.try_into().unwrap()) - .satoshis(BRC20_DUST_SATOSHIS) - .build() - .unwrap(); - - let transaction = TransactionBuilder::new() - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - // Encode the signed transaction. - let hex = hex::encode(&transaction, false); - - assert_eq!(hex[..164], REVEAL_TX_RAW[..164]); - // We ignore the 64-byte Schnorr signature, since it uses random data for - // signing on each construction and is therefore not reproducible. - assert_ne!(hex[164..292], REVEAL_TX_RAW[164..292]); - assert_eq!(hex[292..], REVEAL_TX_RAW[292..]); - - // # Actually transfer the "transferable" tokens. - // Based on Bitcoin transaction: - // https://www.blockchain.com/explorer/transactions/btc/3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7 - - // We use a normal P2WPKH output for this. - let input_for_brc20_transfer = TxInputP2WPKH::builder() - .txid(Txid::from_str(TRANSFER_TXID_INSCRIPTION).unwrap()) - .vout(0) - .recipient(alice.try_into().unwrap()) - .satoshis(BRC20_DUST_SATOSHIS) - .build() - .unwrap(); - - let input_for_fee = TxInputP2WPKH::builder() - .txid(Txid::from_str(TRANSFER_TXID_FOR_FEES).unwrap()) - .vout(1) - .recipient(alice.try_into().unwrap()) - .satoshis(FOR_FEE_SATOSHIS) - .build() - .unwrap(); - - // We transfer the tokens to Bob. - let output_brc20_transfer = TxOutputP2WPKH::builder() - .recipient(bob.try_into().unwrap()) - .satoshis(BRC20_DUST_SATOSHIS) - .build() - .unwrap(); - - let output_change = TxOutputP2WPKH::builder() - .recipient(alice.try_into().unwrap()) - .satoshis(FOR_FEE_SATOSHIS - MINER_FEE) - .build() - .unwrap(); - - // We carefully add the BRC20 transfer in the first position for both input and output. - let transaction = TransactionBuilder::new() - .add_input(input_for_brc20_transfer.into()) - .add_input(input_for_fee.into()) - .add_output(output_brc20_transfer.into()) - .add_output(output_change.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - // Encode the signed transaction. - let hex = hex::encode(&transaction, false); - assert_eq!(hex, TRANSFER_RAW); -} diff --git a/rust/tw_bitcoin/src/tests/fee.rs b/rust/tw_bitcoin/src/tests/fee.rs deleted file mode 100644 index f7cfa85d8f9..00000000000 --- a/rust/tw_bitcoin/src/tests/fee.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::calculate_fee; -use bitcoin::{consensus::Decodable, Transaction}; - -// 10 satoshis per virtual byte. -const SAT_VB: u64 = 12; - -fn decode_tx(raw: &str) -> Transaction { - let hex = tw_encoding::hex::decode(raw).unwrap(); - Transaction::consensus_decode(&mut hex.as_slice()).unwrap() -} - -#[test] -fn p2pkh_fee() { - let tx = decode_tx(super::p2pkh::TX_RAW); - - let (weight, fee) = calculate_fee(&tx, SAT_VB); - assert_eq!(weight.to_vbytes_ceil(), 191); - assert_eq!(fee, 191 * SAT_VB); -} - -#[test] -fn p2wpkh_fee() { - let tx = decode_tx(super::p2wpkh::TX_RAW); - - let (weight, fee) = calculate_fee(&tx, SAT_VB); - assert_eq!(weight.to_vbytes_ceil(), 189); - assert_eq!(fee, 189 * SAT_VB); -} - -#[test] -fn brc20_commit_fee() { - // Metadata can be observed live on: - // https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 - // - // Fee/VB 19.608 sat/vByte - // Size 235 Bytes - // Weight 610 - - // 19 satoshis per vbyte. - const SAT_19_VB: u64 = 19; - - let tx = decode_tx(super::brc20_transfer::COMMIT_TX_RAW); - - let (weight, fee) = calculate_fee(&tx, SAT_19_VB); - assert_eq!(weight.to_vbytes_ceil(), 153); // 153 = ceil(610/4) - assert_eq!(fee, 153 * SAT_19_VB); -} - -#[test] -fn brc20_reveal_fee() { - // Metadata can be observed live on: - // https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca - // - // Fee/VB 49.267 sat/vByte - // Size 276 Bytes - // Weight 522 - - // 49 satoshis per vbyte (slightly overpaid here...) - const SAT_49_VB: u64 = 49; - - let tx = decode_tx(super::brc20_transfer::REVEAL_TX_RAW); - - let (weight, fee) = calculate_fee(&tx, SAT_49_VB); - assert_eq!(weight.to_vbytes_ceil(), 131); // 131 = ceil(522/4) - assert_eq!(fee, 131 * SAT_49_VB); -} - -#[test] -fn ordinal_nft_commit_fee() { - // Metadata can be observed live on: - // https://www.blockchain.com/explorer/transactions/btc/f1e708e5c5847339e16accf8716c14b33717c14d6fe68f9db36627cecbde7117 - // - // Fee/VB 10.656 sat/vByte - // Size 203 Bytes - // Weight 485 - - // 19 satoshis per vbyte. - const SAT_10_VB: u64 = 10; - - let tx = decode_tx(super::nft::COMMIT_RAW_TX); - - let (weight, fee) = calculate_fee(&tx, SAT_10_VB); - assert_eq!(weight.to_vbytes_ceil(), 122); // 122 = ceil(485/4) - assert_eq!(fee, 122 * SAT_10_VB); -} - -#[test] -fn ordinal_nft_reveal_fee() { - // Metadata can be observed live on: - // https://www.blockchain.com/explorer/transactions/btc/173f8350b722243d44cc8db5584de76b432eb6d0888d9e66e662db51584f44ac - // - // Fee/VB 15.133 sat/vByte - // Size 7'829 Bytes - // Weight 8'075 - - // 19 satoshis per vbyte. - const SAT_15_VB: u64 = 15; - - let tx = decode_tx(super::nft::REVEAL_RAW_TX); - - let (weight, fee) = calculate_fee(&tx, SAT_15_VB); - assert_eq!(weight.to_vbytes_ceil(), 2019); // 2019 = ceil(8_075/4) - assert_eq!(fee, 2019 * SAT_15_VB); -} diff --git a/rust/tw_bitcoin/src/tests/ffi/brc20_transfer.rs b/rust/tw_bitcoin/src/tests/ffi/brc20_transfer.rs deleted file mode 100644 index 4c210a85c6a..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/brc20_transfer.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crate::brc20::{BRC20TransferInscription, Ticker}; -use crate::ffi::taproot_build_and_sign_transaction; -use crate::tests::ffi::utils::{ - call_ffi_build_brc20_transfer_script, call_ffi_build_p2wpkh_script, reverse_txid, - ProtoSigningInputBuilder, ProtoTransactionBuilder, -}; -use crate::tests::p2pkh::ALICE_WIF; -use crate::{keypair_from_wif, Recipient, TXOutputP2TRScriptPath}; -use bitcoin::PublicKey; -use std::borrow::Cow; -use tw_encoding::hex; -use tw_proto::Bitcoin::Proto::{TransactionOutput, TransactionVariant}; - -#[test] -fn proto_brc20_transfer_script() { - let keypair: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let recipient = Recipient::::from(keypair); - - let satoshis: u64 = 1_000; - let brc20_amount = 20; - let ticker = "oadf"; - - // Call FFI function. - let ffi_out = call_ffi_build_brc20_transfer_script(ticker, brc20_amount, satoshis, &recipient); - - // Compare with native call. - let transfer = BRC20TransferInscription::new( - recipient, - Ticker::new(ticker.to_string()).unwrap(), - brc20_amount, - ) - .unwrap(); - - let tapscript = transfer.inscription().recipient().clone(); - let spending_script = transfer.inscription().taproot_program(); - - let tx_out = TXOutputP2TRScriptPath::new(satoshis, &tapscript); - // Wrap in Protobuf structure. - let proto = TransactionOutput { - value: satoshis as i64, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::from(spending_script.as_bytes()), - }; - - assert_eq!(ffi_out, proto); -} - -/// Commit the Inscription. -#[test] -fn proto_sign_brc20_transfer_inscription_commit() { - use crate::tests::brc20_transfer::*; - - // Prepare keys. - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let alice_privkey = alice.secret_bytes(); - let alice_recipient = Recipient::::from(&alice); - - // Note that the Txid must be reversed. - let txid = reverse_txid(COMMIT_TXID); - - // Build input script. - let input = call_ffi_build_p2wpkh_script(FULL_SATOSHIS, &alice_recipient); - - // Build inscription output. - let output_inscribe = call_ffi_build_brc20_transfer_script( - BRC20_TICKER, - BRC20_AMOUNT, - BRC20_INSCRIBE_SATOSHIS, - &alice_recipient, - ); - - // Build change output. - let output_change = call_ffi_build_p2wpkh_script(FOR_FEE_SATOSHIS, &alice_recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&alice_privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(1) - .script_pubkey(&input.script) - .satoshis(FULL_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output_inscribe.script) - .satoshis(BRC20_INSCRIBE_SATOSHIS) - .variant(TransactionVariant::BRC20TRANSFER) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output_change.script) - .satoshis(FOR_FEE_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - assert_eq!(hex::encode(&signed.encoded, false), COMMIT_TX_RAW); -} - -/// Reveal the Inscription. -#[test] -fn proto_sign_brc20_transfer_inscription_reveal() { - use crate::tests::brc20_transfer::*; - - // Prepare keys. - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let alice_privkey = alice.secret_bytes(); - let alice_recipient = Recipient::::from(&alice); - - // Note that the Txid must be reversed. - let txid = reverse_txid(REVEAL_TXID); - - // Build input script. - let input = call_ffi_build_brc20_transfer_script( - BRC20_TICKER, - BRC20_AMOUNT, - BRC20_INSCRIBE_SATOSHIS, - &alice_recipient, - ); - - // Build inscription output. - let output_p2wpkh = call_ffi_build_p2wpkh_script(BRC20_DUST_SATOSHIS, &alice_recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&alice_privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(0) - .script_pubkey(&input.script) - .satoshis(BRC20_INSCRIBE_SATOSHIS) - .variant(TransactionVariant::BRC20TRANSFER) - // IMPORANT: include the witness containing the actual inscription. - .spending_script(&input.spendingScript) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output_p2wpkh.script) - .satoshis(BRC20_DUST_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - let hex = hex::encode(&signed.encoded, false); - - assert_eq!(hex[..164], REVEAL_TX_RAW[..164]); - // We ignore the 64-byte Schnorr signature, since it uses random data for - // signing on each construction and is therefore not reproducible. - assert_ne!(hex[164..292], REVEAL_TX_RAW[164..292]); - assert_eq!(hex[292..], REVEAL_TX_RAW[292..]); -} - -/// Transfer the Inscription with P2WPKH. -#[test] -fn proto_sign_brc20_transfer_inscription_p2wpkh_transfer() { - use crate::tests::brc20_transfer::*; - - // Prepare keys. - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let alice_privkey = alice.secret_bytes(); - let alice_recipient = Recipient::::from(&alice); - - let bob = keypair_from_wif(BOB_WIF).unwrap(); - let bob_recipient = Recipient::::from(&bob); - - // The Txid to reference the Inscription. - let txid_inscription = reverse_txid(TRANSFER_TXID_INSCRIPTION); - - // The Txid for paying fees. - let txid_for_fees = reverse_txid(TRANSFER_TXID_FOR_FEES); - - // Build input script for Inscription transfer. - let input_transfer = call_ffi_build_p2wpkh_script(BRC20_DUST_SATOSHIS, &alice_recipient); - - // Build input for paying fees. - let input_fees = call_ffi_build_p2wpkh_script(FOR_FEE_SATOSHIS, &alice_recipient); - - // Build Inscription transfer output with Bob as recipient. - let output_transfer = call_ffi_build_p2wpkh_script(BRC20_DUST_SATOSHIS, &bob_recipient); - - // Build change output. - let output_change = - call_ffi_build_p2wpkh_script(FOR_FEE_SATOSHIS - MINER_FEE, &alice_recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&alice_privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid_inscription) - .vout(0) - .script_pubkey(&input_transfer.script) - .satoshis(BRC20_DUST_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .input( - ProtoTransactionBuilder::new() - .txid(&txid_for_fees) - .vout(1) - .script_pubkey(&input_fees.script) - .satoshis(FOR_FEE_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output_transfer.script) - .satoshis(BRC20_DUST_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output_change.script) - .satoshis(FOR_FEE_SATOSHIS - MINER_FEE) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - assert_eq!(hex::encode(&signed.encoded, false), TRANSFER_RAW); -} diff --git a/rust/tw_bitcoin/src/tests/ffi/fees.rs b/rust/tw_bitcoin/src/tests/ffi/fees.rs deleted file mode 100644 index a5cdea4f7b3..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/fees.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::ffi::tw_bitcoin_calculate_transaction_fee; -use tw_memory::ffi::c_result::CUInt64Result; - -/// Convenience wrapper. -fn call_ffi_calculate_fee(hex: &str, sat_vb: u64) -> u64 { - let hex = tw_encoding::hex::decode(hex).unwrap(); - - let res: CUInt64Result = - unsafe { tw_bitcoin_calculate_transaction_fee(hex.as_ptr(), hex.len(), sat_vb) }; - - res.unwrap() -} - -#[test] -fn ffi_calculate_p2pkh_fee() { - let fee = call_ffi_calculate_fee(crate::tests::p2pkh::TX_RAW, 10); - assert_eq!(fee, 191 * 10); -} - -#[test] -fn ffi_calculate_p2wpkh_fee() { - let fee = call_ffi_calculate_fee(crate::tests::p2wpkh::TX_RAW, 10); - assert_eq!(fee, 189 * 10); -} - -#[test] -fn ffi_calculate_brc20_commit_fee() { - let fee = call_ffi_calculate_fee(crate::tests::brc20_transfer::COMMIT_TX_RAW, 19); - assert_eq!(fee, 153 * 19); -} - -#[test] -fn ffi_calculate_brc20_reveal_fee() { - let fee = call_ffi_calculate_fee(crate::tests::brc20_transfer::REVEAL_TX_RAW, 49); - assert_eq!(fee, 131 * 49); -} - -#[test] -fn ffi_calculate_ordinal_nft_commit_fee() { - let fee = call_ffi_calculate_fee(crate::tests::nft::COMMIT_RAW_TX, 10); - assert_eq!(fee, 122 * 10); -} - -#[test] -fn ffi_calculate_ordinal_nft_reveal_fee() { - let fee = call_ffi_calculate_fee(crate::tests::nft::REVEAL_RAW_TX, 15); - assert_eq!(fee, 2019 * 15); -} diff --git a/rust/tw_bitcoin/src/tests/ffi/mod.rs b/rust/tw_bitcoin/src/tests/ffi/mod.rs deleted file mode 100644 index fcb183bbdf0..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod brc20_transfer; -mod fees; -mod nft; -mod scripts; -mod transaction; -mod utils; diff --git a/rust/tw_bitcoin/src/tests/ffi/nft.rs b/rust/tw_bitcoin/src/tests/ffi/nft.rs deleted file mode 100644 index 9a16acd7e31..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/nft.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::ffi::taproot_build_and_sign_transaction; -use crate::nft::OrdinalNftInscription; -use crate::tests::ffi::utils::{ - call_ffi_build_p2wpkh_script, reverse_txid, ProtoSigningInputBuilder, ProtoTransactionBuilder, -}; -use crate::tests::nft::ALICE_WIF; -use crate::{keypair_from_wif, Recipient, TXOutputP2TRScriptPath}; -use bitcoin::PublicKey; -use std::borrow::Cow; -use tw_encoding::hex; -use tw_proto::Bitcoin::Proto::{TransactionOutput, TransactionVariant}; - -use super::utils::call_ffi_build_nft_inscription; - -#[test] -fn proto_nft_inscription_script() { - let keypair: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let recipient = Recipient::::from(keypair); - - let mime_type = b"image/png"; - let payload = hex::decode(crate::tests::data::NFT_INSCRIPTION_IMAGE_DATA).unwrap(); - let satoshis: u64 = 1_000; - - // Call FFI function. - let ffi_out = call_ffi_build_nft_inscription(mime_type, &payload, satoshis, &recipient); - - // Compare with native call. - let nft = OrdinalNftInscription::new(mime_type, &payload, recipient).unwrap(); - - let tapscript = nft.inscription().recipient().clone(); - let spending_script = nft.inscription().taproot_program(); - - let tx_out = TXOutputP2TRScriptPath::new(satoshis, &tapscript); - // Wrap in Protobuf structure. - let proto = TransactionOutput { - value: satoshis as i64, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::from(spending_script.as_bytes()), - }; - - assert_eq!(ffi_out, proto); -} - -/// Commit the Inscription. -#[test] -fn proto_sign_nft_inscription_commit() { - use crate::tests::nft::*; - - // Prepare keys. - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let privkey = alice.secret_bytes(); - let recipient = Recipient::::from(&alice); - - let mime_type = b"image/png"; - let payload = hex::decode(crate::tests::data::NFT_INSCRIPTION_IMAGE_DATA).unwrap(); - let satoshis: u64 = 1_000; - - // Note that the Txid must be reversed. - let txid = reverse_txid(COMMIT_TXID); - - // Build input script. - let input = call_ffi_build_p2wpkh_script(FULL_SATOSHIS, &recipient); - - // Build inscription output. - let output = call_ffi_build_nft_inscription(mime_type, &payload, satoshis, &recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(0) - .script_pubkey(&input.script) - .satoshis(FULL_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output.script) - .satoshis(INSCRIBE_SATOSHIS) - .variant(TransactionVariant::NFTINSCRIPTION) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - assert_eq!(hex::encode(&signed.encoded, false), COMMIT_RAW_TX); -} - -/// Reveal the Inscription. -#[test] -fn proto_sign_nft_inscription_reveal() { - use crate::tests::nft::*; - - // Prepare keys. - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let privkey = alice.secret_bytes(); - let recipient = Recipient::::from(&alice); - - let mime_type = b"image/png"; - let payload = hex::decode(crate::tests::data::NFT_INSCRIPTION_IMAGE_DATA).unwrap(); - let satoshis: u64 = 1_000; - - // Note that the Txid must be reversed. - let txid = reverse_txid(REVEAL_TXID); - - // Build inscription input. - let input = call_ffi_build_nft_inscription(mime_type, &payload, satoshis, &recipient); - - // Build inscription output. - let output_p2wpkh = call_ffi_build_p2wpkh_script(DUST_SATOSHIS, &recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(0) - .script_pubkey(&input.script) - .satoshis(INSCRIBE_SATOSHIS) - .variant(TransactionVariant::NFTINSCRIPTION) - // IMPORANT: include the witness containing the actual inscription. - .spending_script(&input.spendingScript) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output_p2wpkh.script) - .satoshis(DUST_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - let hex = hex::encode(&signed.encoded, false); - - assert_eq!(hex[..164], REVEAL_RAW_TX[..164]); - // We ignore the 64-byte Schnorr signature, since it uses random data for - // signing on each construction and is therefore not reproducible. - assert_ne!(hex[164..292], REVEAL_RAW_TX[164..292]); - assert_eq!(hex[292..], REVEAL_RAW_TX[292..]); -} diff --git a/rust/tw_bitcoin/src/tests/ffi/scripts.rs b/rust/tw_bitcoin/src/tests/ffi/scripts.rs deleted file mode 100644 index b0cc8853f7e..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/scripts.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::tests::ffi::utils::{ - call_ffi_build_p2pkh_script, call_ffi_build_p2tr_key_path_script, call_ffi_build_p2wpkh_script, -}; -use crate::tests::p2pkh::ALICE_WIF; -use crate::{keypair_from_wif, Recipient, TxOutputP2PKH, TxOutputP2TRKeyPath, TxOutputP2WPKH}; -use bitcoin::PublicKey; -use std::borrow::Cow; -use tw_proto::Bitcoin::Proto::TransactionOutput; - -#[test] -fn proto_build_p2pkh_script() { - // Prepare keys. - let keypair: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let recipient = Recipient::::from(keypair); - - let satoshis: u64 = 1_000; - - // Call FFI function. - let ffi_out = call_ffi_build_p2pkh_script(satoshis, &recipient); - - // Compare with native call. - let tx_out = TxOutputP2PKH::new(satoshis, recipient); - // Wrap in Protobuf structure. - let proto = TransactionOutput { - value: satoshis as i64, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::default(), - }; - - assert_eq!(ffi_out, proto); -} - -#[test] -fn proto_build_p2wpkh_script() { - // Prepare keys. - let keypair: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let recipient = Recipient::::from(keypair); - - let satoshis: u64 = 1_000; - - // Call FFI function. - let ffi_out = call_ffi_build_p2wpkh_script(satoshis, &recipient); - - // Compare with native call. - let tx_out = TxOutputP2WPKH::new(satoshis, recipient.try_into().unwrap()); - // Wrap in Protobuf structure. - let proto = TransactionOutput { - value: satoshis as i64, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::default(), - }; - - assert_eq!(ffi_out, proto); -} - -#[test] -fn proto_build_p2tr_key_path_script() { - // Prepare keys. - let keypair: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let recipient = Recipient::::from(keypair); - - let satoshis: u64 = 1_000; - - // Call FFI function. - let ffi_out = call_ffi_build_p2tr_key_path_script(satoshis, &recipient); - - // Compare with native call. - let tx_out = TxOutputP2TRKeyPath::new(satoshis, recipient.try_into().unwrap()); - // Wrap in Protobuf structure. - let proto = TransactionOutput { - value: satoshis as i64, - script: Cow::from(tx_out.script_pubkey.as_bytes()), - spendingScript: Cow::default(), - }; - - assert_eq!(ffi_out, proto); -} diff --git a/rust/tw_bitcoin/src/tests/ffi/transaction.rs b/rust/tw_bitcoin/src/tests/ffi/transaction.rs deleted file mode 100644 index 10d8035d92b..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/transaction.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::ffi::taproot_build_and_sign_transaction; -use crate::tests::ffi::utils::{ - call_ffi_build_p2pkh_script, call_ffi_build_p2tr_key_path_script, call_ffi_build_p2wpkh_script, - reverse_txid, ProtoSigningInputBuilder, ProtoTransactionBuilder, -}; -use crate::{keypair_from_wif, Recipient}; -use bitcoin::PublicKey; -use tw_encoding::hex; -use tw_proto::Bitcoin::Proto::TransactionVariant; - -#[test] -pub fn proto_sign_input_p2pkh_output_p2pkh() { - use crate::tests::p2pkh::*; - - // Prepare keys. - let alice: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let alice_privkey = alice.secret_bytes(); - let alice_recipient = Recipient::::from_keypair(&alice); - - let bob = keypair_from_wif(BOB_WIF).unwrap(); - let bob_recipient = Recipient::::from_keypair(&bob); - - let txid = reverse_txid(TXID); - - // Prepare the scripts. - let input = call_ffi_build_p2pkh_script(FULL_SATOSHIS, &alice_recipient); - let output = call_ffi_build_p2pkh_script(SEND_SATOSHIS, &bob_recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&alice_privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(0) - .script_pubkey(&input.script) - .satoshis(FULL_SATOSHIS) - .variant(TransactionVariant::P2PKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output.script) - .satoshis(SEND_SATOSHIS) - .variant(TransactionVariant::P2PKH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - assert_eq!(hex::encode(&signed.encoded, false), TX_RAW); -} - -#[test] -pub fn proto_sign_input_p2pkh_output_p2wpkh() { - use crate::tests::p2wpkh::*; - - // Prepare keys. - let alice: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let alice_privkey = alice.secret_bytes(); - let alice_recipient = Recipient::::from_keypair(&alice); - - let bob = keypair_from_wif(BOB_WIF).unwrap(); - let bob_recipient = Recipient::::from_keypair(&bob); - - let txid = reverse_txid(TXID); - - // Prepare the scripts. - let input = call_ffi_build_p2pkh_script(FULL_SATOSHIS, &alice_recipient); - let output = call_ffi_build_p2wpkh_script(SEND_SATOSHIS, &bob_recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&alice_privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(0) - .script_pubkey(&input.script) - .satoshis(FULL_SATOSHIS) - .variant(TransactionVariant::P2PKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output.script) - .satoshis(SEND_SATOSHIS) - .variant(TransactionVariant::P2WPKH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - assert_eq!(hex::encode(&signed.encoded, false), TX_RAW); -} - -#[test] -pub fn proto_sign_input_p2pkh_output_p2tr_key_path() { - use crate::tests::p2tr_key_path::*; - - // Prepare keys. - let alice: secp256k1::KeyPair = keypair_from_wif(ALICE_WIF).unwrap(); - let alice_privkey = alice.secret_bytes(); - let alice_recipient = Recipient::::from_keypair(&alice); - - let bob = keypair_from_wif(BOB_WIF).unwrap(); - let bob_recipient = Recipient::::from_keypair(&bob); - - let txid = reverse_txid(FIRST_TXID); - - // Prepare the scripts. - let input = call_ffi_build_p2pkh_script(FULL_SATOSHIS, &alice_recipient); - let output = call_ffi_build_p2tr_key_path_script(SEND_SATOSHIS_TO_BOB, &bob_recipient); - - // Construct Protobuf payload. - let signing = ProtoSigningInputBuilder::new() - .private_key(&alice_privkey) - .input( - ProtoTransactionBuilder::new() - .txid(&txid) - .vout(0) - .script_pubkey(&input.script) - .satoshis(FULL_SATOSHIS) - .variant(TransactionVariant::P2PKH) - .build(), - ) - .output( - ProtoTransactionBuilder::new() - .script_pubkey(&output.script) - .satoshis(SEND_SATOSHIS_TO_BOB) - .variant(TransactionVariant::P2TRKEYPATH) - .build(), - ) - .build(); - - let signed = taproot_build_and_sign_transaction(signing).unwrap(); - assert_eq!(hex::encode(&signed.encoded, false), FIRST_TX_RAW); -} diff --git a/rust/tw_bitcoin/src/tests/ffi/utils.rs b/rust/tw_bitcoin/src/tests/ffi/utils.rs deleted file mode 100644 index 67e041c3938..00000000000 --- a/rust/tw_bitcoin/src/tests/ffi/utils.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::ffi::{ - tw_bitcoin_build_nft_inscription, tw_build_brc20_transfer_inscription, tw_build_p2pkh_script, - tw_build_p2tr_key_path_script, tw_build_p2wpkh_script, -}; -use crate::Recipient; -use bitcoin::PublicKey; -use std::borrow::Cow; -use std::ffi::CString; -use tw_proto::Bitcoin::Proto::{ - OutPoint, SigningInput, TransactionOutput, TransactionPlan, TransactionVariant, - UnspentTransaction, -}; - -/// Convenience function for reversing the Txid before it's being passed on to -/// the FFI. -pub fn reverse_txid(txid: &str) -> Vec { - tw_encoding::hex::decode(txid) - .unwrap() - .into_iter() - .rev() - .collect() -} - -/// Convenience wrapper over `tw_build_p2pkh_script` with Protobuf -/// deserialization support. -pub fn call_ffi_build_p2pkh_script<'a, 'b>( - satoshis: u64, - // We use 'b to clarify that `recipient` is not tied to the return value. - recipient: &'b Recipient, -) -> TransactionOutput<'a> { - let pubkey = recipient.public_key().to_bytes(); - - let raw = - unsafe { tw_build_p2pkh_script(satoshis as i64, pubkey.as_ptr(), pubkey.len()).into_vec() }; - - let des: TransactionOutput = tw_proto::deserialize(&raw).unwrap(); - - // We convert the referenced data into owned data since `raw` goes out of - // scope at the end of the function. - TransactionOutput { - value: des.value, - script: des.script.into_owned().into(), - spendingScript: des.spendingScript.into_owned().into(), - } -} - -/// Convenience wrapper over `tw_build_p2wpkh_script` with Protobuf -/// deserialization support. -pub fn call_ffi_build_p2wpkh_script<'a, 'b>( - satoshis: u64, - // We use 'b to clarify that `recipient` is not tied to the return value. - recipient: &'b Recipient, -) -> TransactionOutput<'a> { - let pubkey = recipient.public_key().to_bytes(); - - let raw = unsafe { - tw_build_p2wpkh_script(satoshis as i64, pubkey.as_ptr(), pubkey.len()).into_vec() - }; - - let des: TransactionOutput = tw_proto::deserialize(&raw).unwrap(); - - // We convert the referenced data into owned data since `raw` goes out of - // scope at the end of the function. - TransactionOutput { - value: des.value, - script: des.script.into_owned().into(), - spendingScript: des.spendingScript.into_owned().into(), - } -} - -/// Convenience wrapper over `tw_build_p2tr_key_path_script` with Protobuf -/// deserialization support. -pub fn call_ffi_build_p2tr_key_path_script<'a, 'b>( - satoshis: u64, - // We use 'b to clarify that `recipient` is not tied to the return value. - recipient: &'b Recipient, -) -> TransactionOutput<'a> { - let pubkey = recipient.public_key().to_bytes(); - - let raw = unsafe { - tw_build_p2tr_key_path_script(satoshis as i64, pubkey.as_ptr(), pubkey.len()).into_vec() - }; - - let des: TransactionOutput = tw_proto::deserialize(&raw).unwrap(); - - // We convert the referenced data into owned data since `raw` goes out of - // scope at the end of the function. - TransactionOutput { - value: des.value, - script: des.script.into_owned().into(), - spendingScript: des.spendingScript.into_owned().into(), - } -} - -/// Convenience wrapper over `tw_build_brc20_inscribe_transfer` with Protobuf -/// deserialization support. -pub fn call_ffi_build_brc20_transfer_script<'a, 'b>( - ticker: &str, - brc20_amount: u64, - satoshis: u64, - // We use 'b to clarify that `recipient` is not tied to the return value. - recipient: &'b Recipient, -) -> TransactionOutput<'a> { - let pubkey = recipient.public_key().to_bytes(); - let c_ticker = CString::new(ticker).unwrap(); - - let raw = unsafe { - tw_build_brc20_transfer_inscription( - c_ticker.as_ptr(), - brc20_amount, - satoshis as i64, - pubkey.as_ptr(), - pubkey.len(), - ) - .into_vec() - }; - - let des: TransactionOutput = tw_proto::deserialize(&raw).unwrap(); - - // We convert the referenced data into owned data since `raw` goes out of - // scope at the end of the function. - TransactionOutput { - value: des.value, - script: des.script.into_owned().into(), - spendingScript: des.spendingScript.into_owned().into(), - } -} - -/// Convenience wrapper over `tw_bitcoin_build_nft_inscription` with Protobuf -/// deserialization support. -pub fn call_ffi_build_nft_inscription<'a, 'b>( - mime_type: &[u8], - data: &[u8], - satoshis: u64, - // We use 'b to clarify that `recipient` is not tied to the return value. - recipient: &'b Recipient, -) -> TransactionOutput<'a> { - let pubkey = recipient.public_key().to_bytes(); - let c_mime_type = CString::new(mime_type).unwrap(); - - let raw = unsafe { - tw_bitcoin_build_nft_inscription( - c_mime_type.as_ptr(), - data.as_ptr(), - data.len(), - satoshis as i64, - pubkey.as_ptr(), - pubkey.len(), - ) - .into_vec() - }; - - let des: TransactionOutput = tw_proto::deserialize(&raw).unwrap(); - - // We convert the referenced data into owned data since `raw` goes out of - // scope at the end of the function. - TransactionOutput { - value: des.value, - script: des.script.into_owned().into(), - spendingScript: des.spendingScript.into_owned().into(), - } -} - -/// Builder for creating the `SigningInput` Protobuf structure. -pub struct ProtoSigningInputBuilder<'a> { - inner: SigningInput<'a>, -} - -impl<'a> ProtoSigningInputBuilder<'a> { - pub fn new() -> Self { - let signing = SigningInput { - plan: Some(TransactionPlan::default()), - ..Default::default() - }; - - ProtoSigningInputBuilder { inner: signing } - } - pub fn private_key(mut self, privkey: &'a [u8]) -> Self { - self.inner.private_key = vec![Cow::from(privkey)]; - self - } - pub fn input(mut self, tx: UnspentTransaction<'a>) -> Self { - self.inner.utxo.push(tx); - self - } - pub fn output(mut self, tx: UnspentTransaction<'a>) -> Self { - self.inner.plan.as_mut().unwrap().utxos.push(tx); - self - } - pub fn build(self) -> SigningInput<'a> { - self.inner - } -} - -/// Builder for creating the `UnspentTransaction` Protobuf structure. -pub struct ProtoTransactionBuilder<'a> { - inner: UnspentTransaction<'a>, -} - -impl<'a> ProtoTransactionBuilder<'a> { - pub fn new() -> Self { - let unspent = UnspentTransaction { - out_point: Some(OutPoint::default()), - ..Default::default() - }; - - ProtoTransactionBuilder { inner: unspent } - } - pub fn txid(mut self, slice: &'a [u8]) -> Self { - self.inner.out_point.as_mut().unwrap().hash = slice.into(); - self - } - pub fn vout(mut self, vout: u32) -> Self { - self.inner.out_point.as_mut().unwrap().index = vout; - self - } - pub fn variant(mut self, variant: TransactionVariant) -> Self { - self.inner.variant = variant; - self - } - pub fn satoshis(mut self, satoshis: u64) -> Self { - self.inner.amount = satoshis as i64; - self - } - pub fn script_pubkey(mut self, script: &'a [u8]) -> Self { - self.inner.script = script.into(); - self - } - pub fn spending_script(mut self, script: &'a [u8]) -> Self { - self.inner.spendingScript = script.into(); - self - } - pub fn build(self) -> UnspentTransaction<'a> { - self.inner - } -} diff --git a/rust/tw_bitcoin/src/tests/mod.rs b/rust/tw_bitcoin/src/tests/mod.rs deleted file mode 100644 index d95d0a493c7..00000000000 --- a/rust/tw_bitcoin/src/tests/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod address; -mod brc20_transfer; -mod data; -mod fee; -mod ffi; -mod nft; -mod p2pkh; -mod p2tr_key_path; -mod p2wpkh; - -pub const ONE_BTC: u64 = 100_000_000; diff --git a/rust/tw_bitcoin/src/tests/nft.rs b/rust/tw_bitcoin/src/tests/nft.rs deleted file mode 100644 index 20e262307cb..00000000000 --- a/rust/tw_bitcoin/src/tests/nft.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::nft::OrdinalNftInscription; -use crate::{ - keypair_from_wif, TXOutputP2TRScriptPath, TransactionBuilder, TxInputP2TRScriptPath, - TxInputP2WPKH, TxOutputP2WPKH, -}; -use bitcoin::Txid; -use std::str::FromStr; -use tw_encoding::hex; - -pub const ALICE_WIF: &str = "L4of5AJ6aKmvChg7gQ7m2RzHFgpWe5Uirmuey1fXJ1FtfmXj59LW"; -pub const FULL_SATOSHIS: u64 = 32_400; -pub const INSCRIBE_SATOSHIS: u64 = FULL_SATOSHIS - MINER_FEE; -pub const DUST_SATOSHIS: u64 = 546; - -pub const MINER_FEE: u64 = 1_300; - -pub const COMMIT_TXID: &str = "579590c3227253ad423b1e7e3c5b073b8a280d307c68aecd779df2600daa2f99"; -pub const COMMIT_RAW_TX: &str = "02000000000101992faa0d60f29d77cdae687c300d288a3b075b3c7e1e3b42ad537222c39095570000000000ffffffff017c790000000000002251202ac69a7e9dba801e9fcba826055917b84ca6fba4d51a29e47d478de603eedab602473044022054212984443ed4c66fc103d825bfd2da7baf2ab65d286e3c629b36b98cd7debd022050214cfe5d3b12a17aaaf1a196bfeb2f0ad15ffb320c4717eb7614162453e4fe0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; - -pub const REVEAL_TXID: &str = "f1e708e5c5847339e16accf8716c14b33717c14d6fe68f9db36627cecbde7117"; -pub const REVEAL_RAW_TX: &str = super::data::NFT_INSCRIPTION_RAW_HEX; - -#[test] -fn inscribe_nft() { - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - - let payload = hex::decode(super::data::NFT_INSCRIPTION_IMAGE_DATA).unwrap(); - let nft_inscription = OrdinalNftInscription::new(b"image/png", &payload, alice.into()).unwrap(); - - let txid = Txid::from_str(COMMIT_TXID).unwrap(); - - // Commit NFT. - let input = TxInputP2WPKH::builder() - .txid(txid) - .vout(0) - .recipient(alice.try_into().unwrap()) - .satoshis(FULL_SATOSHIS) - .build() - .unwrap(); - - let output = TXOutputP2TRScriptPath::builder() - .recipient(nft_inscription.inscription().recipient().clone()) - .satoshis(INSCRIBE_SATOSHIS) - .build() - .unwrap(); - - let transaction = TransactionBuilder::new() - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&transaction, false); - assert_eq!(hex, COMMIT_RAW_TX); - - // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/f1e708e5c5847339e16accf8716c14b33717c14d6fe68f9db36627cecbde7117 - - let txid = Txid::from_str(REVEAL_TXID).unwrap(); - - // Reveal NFT. - let input = TxInputP2TRScriptPath::builder() - .txid(txid) - .vout(0) - .recipient(nft_inscription.inscription().recipient().clone()) - .satoshis(INSCRIBE_SATOSHIS) - .script(nft_inscription.inscription().taproot_program().to_owned()) - .spend_info(nft_inscription.inscription().spend_info().clone()) - .build() - .unwrap(); - - let output = TxOutputP2WPKH::builder() - .recipient(alice.try_into().unwrap()) - .satoshis(DUST_SATOSHIS) - .build() - .unwrap(); - - let transaction = TransactionBuilder::new() - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&transaction, false); - assert_eq!(hex[..164], REVEAL_RAW_TX[..164]); - // We ignore the 64-byte Schnorr signature, since it uses random data for - // signing on each construction and is therefore not reproducible. - assert_ne!(hex[164..292], REVEAL_RAW_TX[164..292]); - assert_eq!(hex[292..], REVEAL_RAW_TX[292..]); - - // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/173f8350b722243d44cc8db5584de76b432eb6d0888d9e66e662db51584f44ac -} diff --git a/rust/tw_bitcoin/src/tests/p2pkh.rs b/rust/tw_bitcoin/src/tests/p2pkh.rs deleted file mode 100644 index 238ba909abf..00000000000 --- a/rust/tw_bitcoin/src/tests/p2pkh.rs +++ /dev/null @@ -1,54 +0,0 @@ -use super::*; -use crate::{keypair_from_wif, TransactionBuilder, TxInputP2PKH, TxOutputP2PKH}; -use bitcoin::Txid; -use std::str::FromStr; -use tw_encoding::hex; - -// Those private keys were used in a Bitcoin regtest environment. -pub const ALICE_WIF: &str = "cQUNzeMnF9xPPLqZhH7hMVYGwSuu3b78zznuc5UrxgXnYQBq6Bx1"; -pub const BOB_WIF: &str = "cTk5wSci88FPka7JwHpNEA82dUMjAysdDbCiuYB2fegfgGESAZVn"; -pub const TXID: &str = "1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"; - -pub const FULL_SATOSHIS: u64 = ONE_BTC * 50; -pub const MINER_FEE: u64 = ONE_BTC / 100; -pub const SEND_SATOSHIS: u64 = FULL_SATOSHIS - MINER_FEE; - -// This passed the `bitcoin-cli -retest testmempoolaccept` command. -pub const TX_RAW: &str = "02000000017be4e642bb278018ab12277de9427773ad1c5f5b1d164a157e0d99aa48dc1c1e000000006a473044022078eda020d4b86fcb3af78ef919912e6d79b81164dbbb0b0b96da6ac58a2de4b102201a5fd8d48734d5a02371c4b5ee551a69dca3842edbf577d863cf8ae9fdbbd4590121036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536ffffffff01c0aff629010000001976a9145eaaa4f458f9158f86afcba08dd7448d27045e3d88ac00000000"; - -#[test] -fn sign_input_p2pkh_output_p2pkh() { - // This passed the `bitcoin-cli -retest testmempoolaccept` command. - - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let bob = keypair_from_wif(BOB_WIF).unwrap(); - - // Prepare inputs for Alice. - let input = TxInputP2PKH::builder() - .txid(Txid::from_str(TXID).unwrap()) - .vout(0) - .recipient(alice) - .satoshis(FULL_SATOSHIS) - .build() - .unwrap(); - - // Prepare outputs for Bob. - let output = TxOutputP2PKH::builder() - .satoshis(SEND_SATOSHIS) - .recipient(bob) - .build() - .unwrap(); - - // Alice signs the transaction. - let signed_transaction = TransactionBuilder::new() - .miner_fee(MINER_FEE) - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&signed_transaction, false); - assert_eq!(&hex, TX_RAW); -} diff --git a/rust/tw_bitcoin/src/tests/p2tr_key_path.rs b/rust/tw_bitcoin/src/tests/p2tr_key_path.rs deleted file mode 100644 index cf876fb8975..00000000000 --- a/rust/tw_bitcoin/src/tests/p2tr_key_path.rs +++ /dev/null @@ -1,94 +0,0 @@ -use super::ONE_BTC; -use crate::{ - keypair_from_wif, TransactionBuilder, TxInputP2PKH, TxInputP2TRKeyPath, TxOutputP2TRKeyPath, -}; -use bitcoin::Txid; -use std::str::FromStr; -use tw_encoding::hex; - -// Those private keys were used in a Bitcoin regtest environment. -pub const ALICE_WIF: &str = "cNDFvH3TXCjxgWeVc7vbu4Jw5m2Lu8FkQ69Z2XvFUD9D9rGjofN1"; -pub const BOB_WIF: &str = "cNt3XNHiJdJpoX5zt3CXY8ncgrCted8bxmFBzcGeTZbBw6jkByWB"; - -pub const FULL_SATOSHIS: u64 = ONE_BTC * 50; -pub const MINER_FEE: u64 = ONE_BTC / 100; -pub const SEND_SATOSHIS_TO_BOB: u64 = FULL_SATOSHIS - MINER_FEE; - -// The raw transactions passed the `bitcoin-cli -retest testmempoolaccept` command. -pub const FIRST_TXID: &str = "c50563913e5a838f937c94232f5a8fc74e58b629fae41dfdffcc9a70f833b53a"; -pub const FIRST_TX_RAW: &str = "02000000013ab533f8709accfffd1de4fa29b6584ec78f5a2f23947c938f835a3e916305c5000000006b48304502210086ab2c2192e2738529d6cd9604d8ee75c5b09b0c2f4066a5c5fa3f87a26c0af602202afc7096aaa992235c43e712146057b5ed6a776d82b9129620bc5a21991c0a5301210351e003fdc48e7f31c9bc94996c91f6c3273b7ef4208a1686021bedf7673bb058ffffffff01c0aff62901000000225120e01cfdd05da8fa1d71f987373f3790d45dea9861acb0525c86656fe50f4397a600000000"; - -pub const SEND_SATOSHIS_TO_ALICE: u64 = SEND_SATOSHIS_TO_BOB - MINER_FEE; -pub const SECOND_TXID: &str = "9a582032f6a50cedaff77d3d5604b33adf8bc31bdaef8de977c2187e395860ac"; -pub const SECOND_TX_RAW: &str = "02000000000101ac6058397e18c277e98defda1bc38bdf3ab304563d7df7afed0ca5f63220589a0000000000ffffffff01806de72901000000225120a5c027857e359d19f625e52a106b8ac6ca2d6a8728f6cf2107cd7958ee0787c20140ec2d3910d41506b60aaa20520bb72f15e2d2cbd97e3a8e26ee7bad5f4c56b0f2fb0ceaddac33cb2813a33ba017ba6b1d011bab74a0426f12a2bcf47b4ed5bc8600000000"; - -#[test] -fn sign_input_p2pkh_output_p2tr_key_path() { - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let bob = keypair_from_wif(BOB_WIF).unwrap(); - - // # First transaction: Alice spends the P2PKH coinbase input and creates - // # a P2WPKH output for Bob. - - // Prepare inputs for Alice. - let input = TxInputP2PKH::builder() - .txid(Txid::from_str(FIRST_TXID).unwrap()) - .vout(0) - .recipient(alice) - .satoshis(FULL_SATOSHIS) - .build() - .unwrap(); - - // Prepare outputs for Bob. - let output = TxOutputP2TRKeyPath::builder() - .recipient(bob) - .satoshis(SEND_SATOSHIS_TO_BOB) - .build() - .unwrap(); - - // Alice signs the transaction. - let signed_transaction = TransactionBuilder::new() - .miner_fee(MINER_FEE) - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&signed_transaction, false); - assert_eq!(&hex, FIRST_TX_RAW); - - // # Second transaction: Bob spends the P2WPKH input and creates - // # a P2WPKH output for Alice. - - // Transaction was submitted in regtest env via `sendrawtransaction` and - // mined with `-generate 1` - let input = TxInputP2TRKeyPath::builder() - .txid(Txid::from_str(SECOND_TXID).unwrap()) - .vout(0) - .recipient(bob) - .satoshis(SEND_SATOSHIS_TO_BOB) - .build() - .unwrap(); - - // Prepare outputs for Bob. - let output = TxOutputP2TRKeyPath::builder() - .recipient(alice) - .satoshis(SEND_SATOSHIS_TO_ALICE) - .build() - .unwrap(); - - // Alice signs the transaction. - let signed_transaction = TransactionBuilder::new() - .miner_fee(MINER_FEE) - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(bob) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&signed_transaction, false); - assert_eq!(hex, SECOND_TX_RAW); -} diff --git a/rust/tw_bitcoin/src/tests/p2wpkh.rs b/rust/tw_bitcoin/src/tests/p2wpkh.rs deleted file mode 100644 index 1422e825896..00000000000 --- a/rust/tw_bitcoin/src/tests/p2wpkh.rs +++ /dev/null @@ -1,92 +0,0 @@ -use super::*; -use crate::{keypair_from_wif, TransactionBuilder, TxInputP2PKH, TxInputP2WPKH, TxOutputP2WPKH}; -use bitcoin::Txid; -use std::str::FromStr; -use tw_encoding::hex; - -// Those private keys were used in a Bitcoin regtest environment. -pub const ALICE_WIF: &str = "cQX5ePcXjTx7C5p6xV8zkp2NN9unhZx4a8RQVPiHd52WxoApV6yK"; -pub const BOB_WIF: &str = "cMn7SSCtE5yt2PS97P4NCMvxpCVvT4cBuHiCzKFW5XMvio4fQbD1"; -pub const TXID: &str = "181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911"; - -pub const FULL_SATOSHIS: u64 = ONE_BTC * 50; -pub const MINER_FEE: u64 = ONE_BTC / 100; -pub const SEND_SATOSHIS: u64 = FULL_SATOSHIS - MINER_FEE; - -// This passed the `bitcoin-cli -retest testmempoolaccept` command. -pub const TX_RAW: &str = "020000000111b9f62923af73e297abb69f749e7a1aa2735fbdfd32ac5f6aa89e5c96841c18000000006b483045022100df9ed0b662b759e68b89a42e7144cddf787782a7129d4df05642dd825930e6e6022051a08f577f11cc7390684bbad2951a6374072253ffcf2468d14035ed0d8cd6490121028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28fffffffff01c0aff629010000001600140d0e1cec6c2babe8badde5e9b3dea667da90036d00000000"; - -#[test] -fn sign_input_p2pkh_and_p2wpkh_output_p2wpkh() { - let alice = keypair_from_wif(ALICE_WIF).unwrap(); - let bob = keypair_from_wif(BOB_WIF).unwrap(); - - // # First transaction: Alice spends the P2PKH coinbase input and creates - // # a P2WPKH output for Bob. - - // Prepare inputs for Alice. - let input = TxInputP2PKH::builder() - .txid(Txid::from_str(TXID).unwrap()) - .vout(0) - .recipient(alice) - .satoshis(FULL_SATOSHIS) - .build() - .unwrap(); - - // Prepare outputs for Bob. - let output = TxOutputP2WPKH::builder() - .recipient(bob.try_into().unwrap()) - .satoshis(SEND_SATOSHIS) - .build() - .unwrap(); - - // Alice signs the transaction. - let signed_transaction = TransactionBuilder::new() - .miner_fee(MINER_FEE) - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(alice) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&signed_transaction, false); - assert_eq!(&hex, TX_RAW); - - // # Second transaction: Bob spends the P2WPKH input and creates - // # a P2WPKH output for Alice. - - // Transaction was submitted in regtest env via `sendrawtransaction` and - // mined with `-generate 1` - const TX_RAW_SECOND: &str = "020000000001016e1f16dcfafbb3a83697f6c23c624cd71085a7f8a25ce0bd9743a41d0a458e850000000000ffffffff01806de7290100000016001460cda7b50f14c152d7401c28ae773c698db9237302483045022100a9b517de5a5e036d7133df499b5b751db6f9a01576a6c5dc38229ec08b6c45cd02200e42c9f8c707c9bf0ceab4f739ec8d683dc1f1f29e195a8da9bc183584d624a60121025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f00000000"; - const LATEST_TXID: &str = "858e450a1da44397bde05ca2f8a78510d74c623cc2f69736a8b3fbfadc161f6e"; - const SEND_TO_ALICE: u64 = SEND_SATOSHIS - MINER_FEE; - - let input = TxInputP2WPKH::builder() - .txid(Txid::from_str(LATEST_TXID).unwrap()) - .vout(0) - .recipient(bob.try_into().unwrap()) - .satoshis(SEND_SATOSHIS) - .build() - .unwrap(); - - // Prepare outputs for Bob. - let output = TxOutputP2WPKH::builder() - .recipient(alice.try_into().unwrap()) - .satoshis(SEND_TO_ALICE) - .build() - .unwrap(); - - // Alice signs the transaction. - let signed_transaction = TransactionBuilder::new() - .miner_fee(MINER_FEE) - .add_input(input.into()) - .add_output(output.into()) - .sign_inputs(bob) - .unwrap() - .serialize() - .unwrap(); - - let hex = hex::encode(&signed_transaction, false); - assert_eq!(&hex, TX_RAW_SECOND); -} diff --git a/rust/tw_bitcoin/src/transaction.rs b/rust/tw_bitcoin/src/transaction.rs deleted file mode 100644 index 7706f85eca8..00000000000 --- a/rust/tw_bitcoin/src/transaction.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::claim::{ClaimLocation, TransactionSigner}; -use crate::input::*; -use crate::output::*; -use crate::{Error, Result}; -use bitcoin::blockdata::locktime::absolute::{Height, LockTime}; -use bitcoin::consensus::Encodable; -use bitcoin::sighash::{EcdsaSighashType, SighashCache, TapSighashType}; -use bitcoin::taproot::{LeafVersion, TapLeafHash}; -use bitcoin::{secp256k1, Address, TxIn, TxOut}; -use bitcoin::{Transaction, Weight}; - -/// Determines the weight of the transaction and calculates the fee with the -/// given satoshis per vbyte. -pub fn calculate_fee(tx: &Transaction, sat_vb: u64) -> (Weight, u64) { - let weight = tx.weight(); - (weight, weight.to_vbytes_ceil() * sat_vb) -} - -#[derive(Debug, Clone)] -pub struct TransactionBuilder { - pub version: i32, - pub lock_time: LockTime, - inputs: Vec, - outputs: Vec, - miner_fee: Option, - return_address: Option
, - contains_taproot: bool, -} - -impl Default for TransactionBuilder { - fn default() -> Self { - TransactionBuilder { - version: 2, - // No lock time, transaction is immediately spendable. - lock_time: LockTime::Blocks(Height::ZERO), - inputs: vec![], - outputs: vec![], - miner_fee: None, - return_address: None, - contains_taproot: false, - } - } -} - -impl TransactionBuilder { - pub fn new() -> Self { - Self::default() - } - pub fn version(mut self, version: i32) -> Self { - self.version = version; - self - } - pub fn lock_time_height(mut self, height: u32) -> Result { - self.lock_time = LockTime::Blocks(Height::from_consensus(height).map_err(|_| Error::Todo)?); - Ok(self) - } - pub fn return_address(mut self, address: Address) -> Self { - self.return_address = Some(address); - self - } - pub fn miner_fee(mut self, satoshis: u64) -> Self { - self.miner_fee = Some(satoshis); - self - } - pub fn add_input(mut self, input: TxInput) -> Self { - match input { - TxInput::P2TRKeyPath(_) | TxInput::P2TRScriptPath(_) => self.contains_taproot = true, - _ => {}, - } - - self.inputs.push(input); - self - } - pub fn add_output(mut self, output: TxOutput) -> Self { - self.outputs.push(output); - self - } - pub fn sign_inputs(self, signer: S) -> Result - where - S: TransactionSigner, - { - self.sign_inputs_fn(|input, sighash| match input { - TxInput::P2PKH(p) => signer - .claim_p2pkh(p, sighash, EcdsaSighashType::All) - .map(|claim| ClaimLocation::Script(claim.0)), - TxInput::P2WPKH(p) => signer - .claim_p2wpkh(p, sighash, EcdsaSighashType::All) - .map(|claim| ClaimLocation::Witness(claim.0)), - TxInput::P2TRKeyPath(p) => signer - .claim_p2tr_key_path(p, sighash, TapSighashType::Default) - .map(|claim| ClaimLocation::Witness(claim.0)), - TxInput::P2TRScriptPath(p) => signer - .claim_p2tr_script_path(p, sighash, TapSighashType::Default) - .map(|claim| ClaimLocation::Witness(claim.0)), - }) - } - pub fn sign_inputs_fn(self, signer: F) -> Result - where - F: Fn(&TxInput, secp256k1::Message) -> Result, - { - // Prepare boilerplate transaction for `bitcoin` crate. - let mut tx = Transaction { - version: self.version, - lock_time: self.lock_time, - input: vec![], - output: vec![], - }; - - // Prepare the inputs for `bitcoin` crate. - for input in self.inputs.iter().cloned() { - let btxin = TxIn::from(input); - tx.input.push(btxin); - } - - // Prepare the outputs for `bitcoin` crate. - for output in self.outputs.iter().cloned() { - let btc_txout = TxOut::from(output); - tx.output.push(btc_txout); - } - - // Satoshi output check - /* - // TODO: This should be enabled, eventually. - let miner_fee = self.miner_fee.ok_or(Error::Todo)?; - if total_satoshis_outputs + miner_fee > total_satoshi_inputs { - return Err(Error::Todo); - } - */ - - // If Taproot is enabled, we prepare the full `TxOuts` (value and - // scriptPubKey) for hashing, which will then be signed. What - // distinguishes this from legacy signing is that the output value in - // satoshis is actually part of the signature. - let mut prevouts = vec![]; - if self.contains_taproot { - for input in &self.inputs { - prevouts.push(TxOut { - value: input.ctx().value, - script_pubkey: input.ctx().script_pubkey.clone(), - }); - } - } - - let mut cache = SighashCache::new(tx); - - let mut claims = vec![]; - - // For each input (index), we create a hash which is to be signed. - for (index, input) in self.inputs.iter().enumerate() { - match input { - TxInput::P2PKH(p2pkh) => { - let hash = cache - .legacy_signature_hash( - index, - &p2pkh.ctx().script_pubkey, - EcdsaSighashType::All.to_u32(), - ) - .map_err(|_| Error::Todo)?; - - let message = secp256k1::Message::from_slice(hash.as_ref()) - .expect("Sighash must always convert to secp256k1::Message"); - let updated = signer(input, message)?; - - claims.push((index, updated)); - }, - TxInput::P2WPKH(p2wpkh) => { - let hash = cache - .segwit_signature_hash( - index, - p2wpkh - .ctx() - .script_pubkey - .p2wpkh_script_code() - .as_ref() - .expect("P2WPKH builder must set the script code correctly"), - p2wpkh.ctx().value, - EcdsaSighashType::All, - ) - .map_err(|_| Error::Todo)?; - - let message = secp256k1::Message::from_slice(hash.as_ref()) - .expect("Sighash must always convert to secp256k1::Message"); - let updated = signer(input, message)?; - - claims.push((index, updated)); - }, - TxInput::P2TRKeyPath(_) => { - let hash = cache - .taproot_key_spend_signature_hash( - index, - &bitcoin::sighash::Prevouts::All(&prevouts), - TapSighashType::Default, - ) - .map_err(|_| Error::Todo)?; - - let message = secp256k1::Message::from_slice(hash.as_ref()) - .expect("Sighash must always convert to secp256k1::Message"); - let updated = signer(input, message)?; - - claims.push((index, updated)); - }, - TxInput::P2TRScriptPath(p2trsp) => { - let leaf_hash = - TapLeafHash::from_script(p2trsp.witness(), LeafVersion::TapScript); - - let hash = cache - .taproot_script_spend_signature_hash( - index, - &bitcoin::sighash::Prevouts::All(&prevouts), - leaf_hash, - TapSighashType::Default, - ) - .map_err(|_| Error::Todo)?; - - let message = secp256k1::Message::from_slice(hash.as_ref()) - .expect("Sighash must always convert to secp256k1::Message"); - let updated = signer(input, message)?; - - claims.push((index, updated)); - }, - }; - } - - let mut tx = cache.into_transaction(); - - // Update the transaction with the updated scriptSig/Witness. - for (index, claim_loc) in claims { - match claim_loc { - ClaimLocation::Script(script) => { - tx.input[index].script_sig = script; - }, - ClaimLocation::Witness(witness) => { - tx.input[index].witness = witness; - }, - } - } - - Ok(TransactionSigned { inner: tx }) - } -} - -pub struct TransactionSigned { - pub inner: Transaction, -} - -impl TransactionSigned { - pub fn serialize(&self) -> Result> { - let mut buffer = vec![]; - self.inner - .consensus_encode(&mut buffer) - .map_err(|_| Error::Todo)?; - - Ok(buffer) - } -} diff --git a/rust/tw_bitcoin/src/utils.rs b/rust/tw_bitcoin/src/utils.rs deleted file mode 100644 index 399a84526e5..00000000000 --- a/rust/tw_bitcoin/src/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{Error, Result}; -use bitcoin::key::{KeyPair, PrivateKey, PublicKey, TapTweak, TweakedPublicKey}; -use bitcoin::secp256k1::{self, XOnlyPublicKey}; - -pub fn keypair_from_wif(string: &str) -> Result { - let pk = PrivateKey::from_wif(string).map_err(|_| Error::Todo)?; - let keypair = KeyPair::from_secret_key(&secp256k1::Secp256k1::new(), &pk.inner); - Ok(keypair) -} - -pub(crate) fn tweak_pubkey(pubkey: PublicKey) -> TweakedPublicKey { - let xonly = XOnlyPublicKey::from(pubkey.inner); - let (tweaked, _) = xonly.tap_tweak(&secp256k1::Secp256k1::new(), None); - tweaked -} diff --git a/rust/tw_bitcoin/tests/brc20.rs b/rust/tw_bitcoin/tests/brc20.rs new file mode 100644 index 00000000000..67c5a2678b8 --- /dev/null +++ b/rust/tw_bitcoin/tests/brc20.rs @@ -0,0 +1,137 @@ +mod common; + +use common::hex; +use tw_bitcoin::aliases::*; +use tw_bitcoin::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_sign_brc20_commit_reveal_transfer() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); + + let txid: Vec = hex("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 1, + value: 26_400, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 7_000, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::brc20_inscribe( + Proto::mod_Output::OutputBrc20Inscription { + inscribe_to: alice_pubkey.as_slice().into(), + ticker: "oadf".into(), + transfer_amount: 20, + }, + ), + }), + }; + + // Change/return transaction. + let out2 = Proto::Output { + value: 16_400, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1, out2], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!( + signed.txid, + hex("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + ); + + let encoded = tw_encoding::hex::encode(signed.encoded, false); + let transaction = signed.transaction.unwrap(); + + assert_eq!(transaction.inputs.len(), 1); + assert_eq!(transaction.outputs.len(), 2); + assert_eq!(&encoded, "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + + // https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 + let txid: Vec = hex("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 7_000, + sighash_type: UtxoProto::SighashType::UseDefault, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::brc20_inscribe(Proto::mod_Input::InputBrc20Inscription { + one_prevout: false, + inscribe_to: alice_pubkey.as_slice().into(), + ticker: "oadf".into(), + transfer_amount: 20, + }), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 546, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + // We enable deterministic Schnorr signatures here + dangerous_use_fixed_schnorr_rng: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + // https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca + assert_eq!( + signed.txid, + hex("7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca") + ); + + let encoded = tw_encoding::hex::encode(signed.encoded, false); + let transaction = signed.transaction.unwrap(); + + assert_eq!(encoded, "02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03406a35548b8fa4620028e021a944c1d3dc6e947243a7bfc901bf63fefae0d2460efa149a6440cab51966aa4f09faef2d1e5efcba23ab4ca6e669da598022dbcfe35b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + assert_eq!(transaction.inputs.len(), 1); + assert_eq!(transaction.outputs.len(), 1); +} diff --git a/rust/tw_bitcoin/src/tests/data.rs b/rust/tw_bitcoin/tests/common/data.rs similarity index 99% rename from rust/tw_bitcoin/src/tests/data.rs rename to rust/tw_bitcoin/tests/common/data.rs index 4ea13a2b2dd..c17d4f0113b 100644 --- a/rust/tw_bitcoin/src/tests/data.rs +++ b/rust/tw_bitcoin/tests/common/data.rs @@ -256,9 +256,9 @@ d1d75f32de1ddd2b7e64faa36f9bd0caa3fa7df3b7fb9b3e92bb7f6d48a9\ pub const NFT_INSCRIPTION_RAW_HEX: &str = "\ 020000000001011771decbce2766b39d8fe66f4dc11737b3146c71f8cc6a\ e1397384c5e508e7f10000000000ffffffff012202000000000000160014\ -e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d0340cc1e7b0b5fa18b28\ -dce702e4e8ed2e91069d682b8daa3a773774bfc7d0e6f737d403016a9016\ -b58a92592ad0b41682e6209167444eb56605532b28e9be922d3afdda1d00\ +e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d0340e9bb1aaf8cb98c60\ +f793f08fc4258a9ad5a7b430fc5906fe3b522b67431fec087cbaf54767d4\ +3061be51d8f49fa179cb9b4f0f9e00ee23101962ac64f9d71a76fdda1d00\ 63036f7264010109696d6167652f706e67004d080289504e470d0a1a0a00\ 00000d4948445200000360000002be0803000000f30f8d7d000000d8504c\ 54450000003070bf3070af3173bd3078b73870b73070b73575ba3075ba30\ diff --git a/rust/tw_bitcoin/tests/common/mod.rs b/rust/tw_bitcoin/tests/common/mod.rs new file mode 100644 index 00000000000..9afe54f6d4f --- /dev/null +++ b/rust/tw_bitcoin/tests/common/mod.rs @@ -0,0 +1,12 @@ +// This seems to be required, even if the tests in `tests/` actually use +// functions/constants. +#![allow(dead_code)] + +pub mod data; + +pub const ONE_BTC: u64 = 100_000_000; +pub const MINER_FEE: u64 = 1_000_000; + +pub fn hex(string: &str) -> Vec { + tw_encoding::hex::decode(string).unwrap() +} diff --git a/rust/tw_bitcoin/tests/free_estimate.rs b/rust/tw_bitcoin/tests/free_estimate.rs new file mode 100644 index 00000000000..bc64fbfa429 --- /dev/null +++ b/rust/tw_bitcoin/tests/free_estimate.rs @@ -0,0 +1,246 @@ +mod common; + +use common::{hex, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +const SAT_VB: u64 = 20; + +#[test] +fn p2pkh_fee_estimate() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let mut signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![], + outputs: vec![], + input_selector: UtxoProto::InputSelector::UseAll, + fee_per_vb: SAT_VB, + disable_change_output: true, + ..Default::default() + }; + + signing.inputs.push(Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 2 * ONE_BTC, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }); + + signing.outputs.push(Proto::Output { + value: ONE_BTC, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2pkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(bob_pubkey.as_slice().into()), + }), + }), + }); + + let prehashes = BitcoinEntry.preimage_hashes(&coin, signing.clone()); + assert_eq!(prehashes.error, Proto::Error::OK); + assert_eq!(prehashes.weight_estimate, 768); + assert_eq!(prehashes.fee_estimate, (768 + 3) / 4 * SAT_VB); + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(signed.weight, 768); + assert_eq!(signed.fee, (768 + 3) / 4 * SAT_VB); +} + +#[test] +fn p2wpkh_fee_estimate() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let mut signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![], + outputs: vec![], + input_selector: UtxoProto::InputSelector::UseAll, + fee_per_vb: SAT_VB, + disable_change_output: true, + ..Default::default() + }; + + signing.inputs.push(Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 2 * ONE_BTC, + sequence: u32::MAX, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }); + + signing.outputs.push(Proto::Output { + value: ONE_BTC, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(bob_pubkey.as_slice().into()), + }), + }), + }); + + let prehashes = BitcoinEntry.preimage_hashes(&coin, signing.clone()); + assert_eq!(prehashes.error, Proto::Error::OK); + // TODO: The estimated weight/fee is slightly off from the finalized + // weight/fee. This is probably good enough, but we can probably improve + // this. + assert_eq!(prehashes.weight_estimate, 436); + assert_eq!(prehashes.fee_estimate, (436 + 3) / 4 * SAT_VB); + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(signed.weight, 438); + assert_eq!(signed.fee, (438 + 3) / 4 * SAT_VB); +} + +#[test] +fn p2tr_key_path_fee_estimate() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 2 * ONE_BTC, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2tr_key_path(Proto::mod_Input::InputTaprootKeyPath { + one_prevout: false, + public_key: alice_pubkey.as_slice().into(), + }), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2tr_key_path(bob_pubkey.as_slice().into()), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + fee_per_vb: SAT_VB, + disable_change_output: true, + ..Default::default() + }; + + let prehashes = BitcoinEntry.preimage_hashes(&coin, signing.clone()); + assert_eq!(prehashes.error, Proto::Error::OK); + // TODO: The estimated weight/fee is slightly off from the finalized + // weight/fee. This is probably good enough, but we can probably improve + // this. + assert_eq!(prehashes.weight_estimate, 450); + assert_eq!(prehashes.fee_estimate, (450 + 3) / 4 * SAT_VB); + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(signed.weight, 445); + assert_eq!(signed.fee, (445 + 3) / 4 * SAT_VB); +} + +#[test] +fn brc20_inscribe_fee_estimate() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 2 * ONE_BTC, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::brc20_inscribe(Proto::mod_Input::InputBrc20Inscription { + one_prevout: false, + inscribe_to: alice_pubkey.as_slice().into(), + ticker: "oadf".into(), + transfer_amount: 20, + }), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::brc20_inscribe( + Proto::mod_Output::OutputBrc20Inscription { + inscribe_to: alice_pubkey.as_slice().into(), + ticker: "oadf".into(), + transfer_amount: 20, + }, + ), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + fee_per_vb: SAT_VB, + disable_change_output: true, + ..Default::default() + }; + + let prehashes = BitcoinEntry.preimage_hashes(&coin, signing.clone()); + assert_eq!(prehashes.error, Proto::Error::OK); + // TODO: The estimated weight/fee is slightly off from the finalized + // weight/fee. This is probably good enough, but we can probably improve + // this. + assert_eq!(prehashes.weight_estimate, 575); + assert_eq!(prehashes.fee_estimate, (575 + 3) / 4 * SAT_VB); + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(signed.weight, 571); + assert_eq!(signed.fee, (571 + 3) / 4 * SAT_VB); +} diff --git a/rust/tw_bitcoin/tests/legacy_build_sign.rs b/rust/tw_bitcoin/tests/legacy_build_sign.rs new file mode 100644 index 00000000000..4c871fb0dc9 --- /dev/null +++ b/rust/tw_bitcoin/tests/legacy_build_sign.rs @@ -0,0 +1,445 @@ +#![allow(deprecated)] + +mod common; + +use common::{hex, MINER_FEE, ONE_BTC}; +use secp256k1::ffi::CPtr; +use std::ffi::CString; +use tw_proto::Bitcoin::Proto as LegacyProto; +use tw_proto::Common::Proto as CommonProto; +use wallet_core_rs::ffi::bitcoin::legacy as legacy_ffi; + +const ONE_BTC_I64: i64 = ONE_BTC as i64; +const MINER_FEE_I64: i64 = MINER_FEE as i64; + +#[test] +fn ffi_proto_sign_input_p2pkh_output_p2pkh() { + let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); + let bob_pubkey = hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); + + let txid = hex("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b") + .into_iter() + .rev() + .collect(); + + // Output. + let output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2pkh_script( + ONE_BTC_I64 * 50 - MINER_FEE_I64, + bob_pubkey.as_c_ptr(), + bob_pubkey.len(), + ) + .into_vec() + }; + let output: LegacyProto::TransactionOutput = tw_proto::deserialize(&output).unwrap(); + + // Prepare SigningInput. + let signing = LegacyProto::SigningInput { + private_key: vec![alice_private_key.into()], + utxo: vec![LegacyProto::UnspentTransaction { + out_point: Some(LegacyProto::OutPoint { + hash: txid, + index: 0, + sequence: u32::MAX, + ..Default::default() + }), + // For inputs, script is not needed (derived from variant). + script: Default::default(), + amount: ONE_BTC_I64 * 50, + variant: LegacyProto::TransactionVariant::P2PKH, + spendingScript: Default::default(), + }], + plan: Some(LegacyProto::TransactionPlan { + utxos: vec![LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: output.script, + amount: output.value, + variant: LegacyProto::TransactionVariant::P2PKH, + spendingScript: Default::default(), + }], + ..Default::default() + }), + ..Default::default() + }; + let serialized = tw_proto::serialize(&signing).unwrap(); + + // Sign and build the transaction. + let signed = unsafe { + legacy_ffi::tw_bitcoin_legacy_taproot_build_and_sign_transaction( + serialized.as_c_ptr(), + serialized.len(), + ) + .into_vec() + }; + let signed: LegacyProto::SigningOutput = tw_proto::deserialize(&signed).unwrap(); + + // Check result. + assert_eq!(signed.error, CommonProto::SigningError::OK); + let encoded_hex = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(encoded_hex, "02000000017be4e642bb278018ab12277de9427773ad1c5f5b1d164a157e0d99aa48dc1c1e000000006a473044022078eda020d4b86fcb3af78ef919912e6d79b81164dbbb0b0b96da6ac58a2de4b102201a5fd8d48734d5a02371c4b5ee551a69dca3842edbf577d863cf8ae9fdbbd4590121036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536ffffffff01c0aff629010000001976a9145eaaa4f458f9158f86afcba08dd7448d27045e3d88ac00000000"); +} + +#[test] +fn ffi_proto_sign_input_p2pkh_output_p2wpkh() { + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + let txid = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + // Output. + let output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2wpkh_script( + ONE_BTC_I64 * 50 - MINER_FEE_I64, + bob_pubkey.as_c_ptr(), + bob_pubkey.len(), + ) + .into_vec() + }; + let output: LegacyProto::TransactionOutput = tw_proto::deserialize(&output).unwrap(); + + // Prepare SigningInput. + let signing = LegacyProto::SigningInput { + private_key: vec![alice_private_key.into()], + utxo: vec![LegacyProto::UnspentTransaction { + out_point: Some(LegacyProto::OutPoint { + hash: txid, + index: 0, + sequence: u32::MAX, + ..Default::default() + }), + // For inputs, script is not needed (derived from variant). + script: Default::default(), + amount: ONE_BTC_I64 * 50, + variant: LegacyProto::TransactionVariant::P2PKH, + spendingScript: Default::default(), + }], + plan: Some(LegacyProto::TransactionPlan { + utxos: vec![LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: output.script, + amount: output.value, + variant: LegacyProto::TransactionVariant::P2WPKH, + spendingScript: Default::default(), + }], + ..Default::default() + }), + ..Default::default() + }; + let serialized = tw_proto::serialize(&signing).unwrap(); + + // Sign and build the transaction. + let signed = unsafe { + legacy_ffi::tw_bitcoin_legacy_taproot_build_and_sign_transaction( + serialized.as_c_ptr(), + serialized.len(), + ) + .into_vec() + }; + let signed: LegacyProto::SigningOutput = tw_proto::deserialize(&signed).unwrap(); + + // Check result. + assert_eq!(signed.error, CommonProto::SigningError::OK); + let encoded_hex = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(encoded_hex, "020000000111b9f62923af73e297abb69f749e7a1aa2735fbdfd32ac5f6aa89e5c96841c18000000006b483045022100df9ed0b662b759e68b89a42e7144cddf787782a7129d4df05642dd825930e6e6022051a08f577f11cc7390684bbad2951a6374072253ffcf2468d14035ed0d8cd6490121028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28fffffffff01c0aff629010000001600140d0e1cec6c2babe8badde5e9b3dea667da90036d00000000"); +} + +#[test] +fn ffi_proto_sign_input_p2pkh_output_p2tr_key_path() { + let alice_private_key = hex("12ce558df23528f1aa86f1f51ac7e13a197a06bda27610fa89e13b04c40ee999"); + let alice_pubkey = hex("0351e003fdc48e7f31c9bc94996c91f6c3273b7ef4208a1686021bedf7673bb058"); + let bob_private_key = hex("26c2566adcc030a1799213bfd546e615f6ab06f72085ec6806ff1761da48d227"); + let bob_pubkey = hex("02c0938cf377023dfde55e9c96b3cff4ca8894fb6b5d2009006bd43c0bff69cac9"); + + let txid = hex("c50563913e5a838f937c94232f5a8fc74e58b629fae41dfdffcc9a70f833b53a") + .into_iter() + .rev() + .collect(); + + // Output. + let output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2tr_key_path_script( + ONE_BTC_I64 * 50 - MINER_FEE_I64, + bob_pubkey.as_c_ptr(), + bob_pubkey.len(), + ) + .into_vec() + }; + let output: LegacyProto::TransactionOutput = tw_proto::deserialize(&output).unwrap(); + + // Prepare SigningInput. + let signing = LegacyProto::SigningInput { + private_key: vec![alice_private_key.into()], + utxo: vec![LegacyProto::UnspentTransaction { + out_point: Some(LegacyProto::OutPoint { + hash: txid, + index: 0, + sequence: u32::MAX, + ..Default::default() + }), + // For inputs, script is not needed (derived from variant). + script: Default::default(), + amount: ONE_BTC_I64 * 50, + variant: LegacyProto::TransactionVariant::P2PKH, + spendingScript: Default::default(), + }], + plan: Some(LegacyProto::TransactionPlan { + utxos: vec![LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: output.script, + amount: output.value, + variant: LegacyProto::TransactionVariant::P2TRKEYPATH, + spendingScript: Default::default(), + }], + ..Default::default() + }), + ..Default::default() + }; + let serialized = tw_proto::serialize(&signing).unwrap(); + + // Sign and build the transaction. + let signed = unsafe { + legacy_ffi::tw_bitcoin_legacy_taproot_build_and_sign_transaction( + serialized.as_c_ptr(), + serialized.len(), + ) + .into_vec() + }; + let signed: LegacyProto::SigningOutput = tw_proto::deserialize(&signed).unwrap(); + + // Check result. + assert_eq!(signed.error, CommonProto::SigningError::OK); + let encoded_hex = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(encoded_hex, "02000000013ab533f8709accfffd1de4fa29b6584ec78f5a2f23947c938f835a3e916305c5000000006b48304502210086ab2c2192e2738529d6cd9604d8ee75c5b09b0c2f4066a5c5fa3f87a26c0af602202afc7096aaa992235c43e712146057b5ed6a776d82b9129620bc5a21991c0a5301210351e003fdc48e7f31c9bc94996c91f6c3273b7ef4208a1686021bedf7673bb058ffffffff01c0aff62901000000225120e01cfdd05da8fa1d71f987373f3790d45dea9861acb0525c86656fe50f4397a600000000"); + + // Next transaction; try to spend the P2TR key-path. + + let txid: Vec = hex("9a582032f6a50cedaff77d3d5604b33adf8bc31bdaef8de977c2187e395860ac") + .into_iter() + .rev() + .collect(); + + // Output. + let output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2tr_key_path_script( + ONE_BTC_I64 * 50 - MINER_FEE_I64 - MINER_FEE_I64, + alice_pubkey.as_c_ptr(), + alice_pubkey.len(), + ) + .into_vec() + }; + let output: LegacyProto::TransactionOutput = tw_proto::deserialize(&output).unwrap(); + + // Prepare SigningInput. + let signing = LegacyProto::SigningInput { + private_key: vec![bob_private_key.into()], + utxo: vec![LegacyProto::UnspentTransaction { + out_point: Some(LegacyProto::OutPoint { + hash: txid.into(), + index: 0, + sequence: u32::MAX, + ..Default::default() + }), + // For inputs, script is not needed (derived from variant). + script: Default::default(), + amount: ONE_BTC_I64 * 50 - MINER_FEE_I64, + variant: LegacyProto::TransactionVariant::P2TRKEYPATH, + spendingScript: Default::default(), + }], + plan: Some(LegacyProto::TransactionPlan { + utxos: vec![LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: output.script, + amount: output.value, + variant: LegacyProto::TransactionVariant::P2TRKEYPATH, + spendingScript: Default::default(), + }], + ..Default::default() + }), + ..Default::default() + }; + let serialized = tw_proto::serialize(&signing).unwrap(); + + // Sign and build the transaction. + let signed = unsafe { + legacy_ffi::tw_bitcoin_legacy_taproot_build_and_sign_transaction( + serialized.as_c_ptr(), + serialized.len(), + ) + .into_vec() + }; + let signed: LegacyProto::SigningOutput = tw_proto::deserialize(&signed).unwrap(); + + // Check result. + const REVEAL_RAW: &str = "02000000000101ac6058397e18c277e98defda1bc38bdf3ab304563d7df7afed0ca5f63220589a0000000000ffffffff01806de72901000000225120a5c027857e359d19f625e52a106b8ac6ca2d6a8728f6cf2107cd7958ee0787c20140ec2d3910d41506b60aaa20520bb72f15e2d2cbd97e3a8e26ee7bad5f4c56b0f2fb0ceaddac33cb2813a33ba017ba6b1d011bab74a0426f12a2bcf47b4ed5bc8600000000"; + + assert_eq!(signed.error, CommonProto::SigningError::OK); + let encoded_hex = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(encoded_hex[..188], REVEAL_RAW[..188]); + // Schnorr signature does not match (non-deterministic). + assert_ne!(encoded_hex[188..316], REVEAL_RAW[188..316]); + assert_eq!(encoded_hex[316..], REVEAL_RAW[316..]); +} + +#[test] +fn ffi_proto_sign_input_p2wpkh_output_brc20() { + let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); + + let txid = hex("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008") + .into_iter() + .rev() + .collect(); + + // Output. + let c_ticker = CString::new("oadf").unwrap(); + let brc20_output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_brc20_transfer_inscription( + c_ticker.as_ptr(), + 20, + 7_000, + alice_pubkey.as_c_ptr(), + alice_pubkey.len(), + ) + .into_vec() + }; + let brc20_output: LegacyProto::TransactionOutput = + tw_proto::deserialize(&brc20_output).unwrap(); + + // Change output. + let change_output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2wpkh_script( + 16_400, + alice_pubkey.as_c_ptr(), + alice_pubkey.len(), + ) + .into_vec() + }; + let change_output: LegacyProto::TransactionOutput = + tw_proto::deserialize(&change_output).unwrap(); + + // Prepare SigningInput. + let signing = LegacyProto::SigningInput { + private_key: vec![alice_private_key.clone().into()], + utxo: vec![LegacyProto::UnspentTransaction { + out_point: Some(LegacyProto::OutPoint { + hash: txid, + index: 1, + sequence: u32::MAX, + ..Default::default() + }), + // For inputs, script is not needed (derived from variant). + script: Default::default(), + amount: 26_400, + variant: LegacyProto::TransactionVariant::P2WPKH, + spendingScript: Default::default(), + }], + plan: Some(LegacyProto::TransactionPlan { + utxos: vec![ + LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: brc20_output.script.clone(), + amount: brc20_output.value, + variant: LegacyProto::TransactionVariant::BRC20TRANSFER, + spendingScript: Default::default(), + }, + // Change output. + LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: change_output.script, + amount: change_output.value, + variant: LegacyProto::TransactionVariant::P2WPKH, + spendingScript: Default::default(), + }, + ], + ..Default::default() + }), + ..Default::default() + }; + let serialized = tw_proto::serialize(&signing).unwrap(); + + // Sign and build the transaction. + let signed = unsafe { + legacy_ffi::tw_bitcoin_legacy_taproot_build_and_sign_transaction( + serialized.as_c_ptr(), + serialized.len(), + ) + .into_vec() + }; + let signed: LegacyProto::SigningOutput = tw_proto::deserialize(&signed).unwrap(); + + // Check result. + assert_eq!(signed.error, CommonProto::SigningError::OK); + let encoded_hex = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(encoded_hex, "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + + // Next transaction; try to spend the BRC20 transfer inscription. + + let txid: Vec = hex("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + .into_iter() + .rev() + .collect(); + + // Tagged output. + let output = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2wpkh_script( + 546, + alice_pubkey.as_c_ptr(), + alice_pubkey.len(), + ) + .into_vec() + }; + let output: LegacyProto::TransactionOutput = tw_proto::deserialize(&output).unwrap(); + + // Prepare SigningInput. + let signing = LegacyProto::SigningInput { + private_key: vec![alice_private_key.into()], + utxo: vec![LegacyProto::UnspentTransaction { + out_point: Some(LegacyProto::OutPoint { + hash: txid.into(), + index: 0, + sequence: u32::MAX, + ..Default::default() + }), + script: Default::default(), + amount: brc20_output.value, + variant: LegacyProto::TransactionVariant::BRC20TRANSFER, + // IMPORTANT: spending script is specified. + spendingScript: brc20_output.spendingScript, + }], + plan: Some(LegacyProto::TransactionPlan { + utxos: vec![LegacyProto::UnspentTransaction { + out_point: Default::default(), + script: output.script, + amount: 546, + variant: LegacyProto::TransactionVariant::P2WPKH, + spendingScript: Default::default(), + }], + ..Default::default() + }), + ..Default::default() + }; + let serialized = tw_proto::serialize(&signing).unwrap(); + + // Sign and build the transaction. + let signed = unsafe { + legacy_ffi::tw_bitcoin_legacy_taproot_build_and_sign_transaction( + serialized.as_c_ptr(), + serialized.len(), + ) + .into_vec() + }; + let signed: LegacyProto::SigningOutput = tw_proto::deserialize(&signed).unwrap(); + + // Check result. + const REVEAL_RAW: &str = "02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03406a35548b8fa4620028e021a944c1d3dc6e947243a7bfc901bf63fefae0d2460efa149a6440cab51966aa4f09faef2d1e5efcba23ab4ca6e669da598022dbcfe35b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; + + assert_eq!(signed.error, CommonProto::SigningError::OK); + let encoded_hex = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(encoded_hex[..164], REVEAL_RAW[..164]); + // Schnorr signature does not match (non-deterministic). + assert_ne!(encoded_hex[164..292], REVEAL_RAW[164..292]); + assert_eq!(encoded_hex[292..], REVEAL_RAW[292..]); +} diff --git a/rust/tw_bitcoin/tests/legacy_scripts.rs b/rust/tw_bitcoin/tests/legacy_scripts.rs new file mode 100644 index 00000000000..77d5fadf82d --- /dev/null +++ b/rust/tw_bitcoin/tests/legacy_scripts.rs @@ -0,0 +1,177 @@ +#![allow(deprecated)] + +mod common; + +use bitcoin::{PublicKey, ScriptBuf}; +use secp256k1::XOnlyPublicKey; +use std::ffi::CString; +use tw_bitcoin::modules::transactions::{ + BRC20TransferInscription, Brc20Ticker, OrdinalNftInscription, +}; +use tw_encoding::hex; +use tw_proto::Bitcoin::Proto as LegacyProto; +use wallet_core_rs::ffi::bitcoin::legacy as legacy_ffi; + +// When building the spending conditions of inputs (scriptPubkey), then the +// actual value is not important. We can just use 0 here. +const SATOSHIS: i64 = 0; +const PUBKEY: &str = "028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"; + +#[test] +fn ffi_tw_bitcoin_legacy_build_p2pkh_script() { + let pubkey_slice = hex::decode(PUBKEY).unwrap(); + let pubkey = PublicKey::from_slice(&pubkey_slice).unwrap(); + + let raw = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2pkh_script( + SATOSHIS, + pubkey_slice.as_ptr(), + pubkey_slice.len(), + ) + .into_vec() + }; + + // The expected script. + let expected = ScriptBuf::new_p2pkh(&pubkey.pubkey_hash()); + + let proto: LegacyProto::TransactionOutput = tw_proto::deserialize(&raw).unwrap(); + assert_eq!(proto.value, SATOSHIS); + assert_eq!(proto.script, expected.as_bytes()); + assert!(proto.spendingScript.is_empty()); +} + +#[test] +fn ffi_tw_bitcoin_legacy_build_p2wpkh_script() { + let pubkey_slice = hex::decode(PUBKEY).unwrap(); + let pubkey = PublicKey::from_slice(&pubkey_slice).unwrap(); + + let raw = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2wpkh_script( + SATOSHIS, + pubkey_slice.as_ptr(), + pubkey_slice.len(), + ) + .into_vec() + }; + + // The expected script. + let expected = ScriptBuf::new_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()); + + let proto: LegacyProto::TransactionOutput = tw_proto::deserialize(&raw).unwrap(); + assert_eq!(proto.value, SATOSHIS); + assert_eq!(proto.script, expected.as_bytes()); + assert!(proto.spendingScript.is_empty()); +} + +#[test] +fn ffi_tw_bitcoin_legacy_build_p2tr_key_path_script() { + let pubkey_slice = hex::decode(PUBKEY).unwrap(); + let pubkey = PublicKey::from_slice(&pubkey_slice).unwrap(); + + let raw = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_p2tr_key_path_script( + SATOSHIS, + pubkey_slice.as_ptr(), + pubkey_slice.len(), + ) + .into_vec() + }; + + // The expected script. + let xonly = XOnlyPublicKey::from(pubkey.inner); + let expected = ScriptBuf::new_v1_p2tr(&secp256k1::Secp256k1::new(), xonly, None); + + let proto: LegacyProto::TransactionOutput = tw_proto::deserialize(&raw).unwrap(); + assert_eq!(proto.value, SATOSHIS); + assert_eq!(proto.script, expected.as_bytes()); + assert!(proto.spendingScript.is_empty()); +} + +#[test] +fn ffi_tw_bitcoin_legacy_build_brc20_transfer_inscription() { + let pubkey_slice = hex::decode(PUBKEY).unwrap(); + let pubkey = PublicKey::from_slice(&pubkey_slice).unwrap(); + + let ticker_str = "oadf"; + let c_ticker = CString::new(ticker_str).unwrap(); + let brc20_amount = 100; + + // Call the FFI function. + let raw = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_brc20_transfer_inscription( + c_ticker.as_ptr(), + brc20_amount, + SATOSHIS, + pubkey_slice.as_ptr(), + pubkey_slice.len(), + ) + .into_vec() + }; + + // Prepare the BRC20 payload + merkle root. + let ticker = Brc20Ticker::new(ticker_str.to_string()).unwrap(); + let transfer = BRC20TransferInscription::new(pubkey, ticker, brc20_amount).unwrap(); + + let merkle_root = transfer + .inscription() + .spend_info() + .merkle_root() + .expect("incorrectly constructed Taproot merkle root"); + + // The expected script. + let xonly = XOnlyPublicKey::from(pubkey.inner); + let expected = ScriptBuf::new_v1_p2tr(&secp256k1::Secp256k1::new(), xonly, Some(merkle_root)); + + let proto: LegacyProto::TransactionOutput = tw_proto::deserialize(&raw).unwrap(); + assert_eq!(proto.value, SATOSHIS); + assert_eq!(proto.script, expected.as_bytes()); + assert_eq!( + proto.spendingScript, + transfer.inscription().taproot_program().as_bytes() + ); +} + +#[test] +fn ffi_tw_bitcoin_legacy_build_nft_inscription() { + let pubkey_slice = hex::decode(PUBKEY).unwrap(); + let pubkey = PublicKey::from_slice(&pubkey_slice).unwrap(); + + let mime_type = "image/png"; + let c_mime_type = CString::new(mime_type).unwrap(); + let payload_hex = common::data::NFT_INSCRIPTION_IMAGE_DATA; + let payload = tw_encoding::hex::decode(payload_hex).unwrap(); + + // Call the FFI function. + let raw = unsafe { + legacy_ffi::tw_bitcoin_legacy_build_nft_inscription( + c_mime_type.as_ptr(), + payload.as_ptr(), + payload.len(), + SATOSHIS, + pubkey_slice.as_ptr(), + pubkey_slice.len(), + ) + .into_vec() + }; + + // Prepare the NFT inscription + merkle root. + let nft = OrdinalNftInscription::new(mime_type.as_bytes(), &payload, pubkey).unwrap(); + + let merkle_root = nft + .inscription() + .spend_info() + .merkle_root() + .expect("incorrectly constructed Taproot merkle root"); + + // The expected script. + let xonly = XOnlyPublicKey::from(pubkey.inner); + let expected = ScriptBuf::new_v1_p2tr(&secp256k1::Secp256k1::new(), xonly, Some(merkle_root)); + + let proto: LegacyProto::TransactionOutput = tw_proto::deserialize(&raw).unwrap(); + assert_eq!(proto.value, SATOSHIS); + assert_eq!(proto.script, expected.as_bytes()); + assert_eq!( + proto.spendingScript, + nft.inscription().taproot_program().as_bytes() + ); +} diff --git a/rust/tw_bitcoin/tests/ordinal_nft.rs b/rust/tw_bitcoin/tests/ordinal_nft.rs new file mode 100644 index 00000000000..d72c8450372 --- /dev/null +++ b/rust/tw_bitcoin/tests/ordinal_nft.rs @@ -0,0 +1,130 @@ +mod common; + +use common::hex; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_sign_ordinal_nft_commit_reveal_transfer() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); + + let txid: Vec = hex("579590c3227253ad423b1e7e3c5b073b8a280d307c68aecd779df2600daa2f99") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 32_400, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 31_100, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::ordinal_inscribe( + Proto::mod_Output::OutputOrdinalInscription { + inscribe_to: alice_pubkey.as_slice().into(), + mime_type: "image/png".into(), + payload: hex(common::data::NFT_INSCRIPTION_IMAGE_DATA).into(), + }, + ), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + // https://www.blockchain.com/explorer/transactions/btc/f1e708e5c5847339e16accf8716c14b33717c14d6fe68f9db36627cecbde7117 + assert_eq!( + signed.txid, + hex("f1e708e5c5847339e16accf8716c14b33717c14d6fe68f9db36627cecbde7117") + ); + + let encoded = tw_encoding::hex::encode(signed.encoded, false); + let transaction = signed.transaction.unwrap(); + + assert_eq!(transaction.inputs.len(), 1); + assert_eq!(transaction.outputs.len(), 1); + assert_eq!(&encoded, "02000000000101992faa0d60f29d77cdae687c300d288a3b075b3c7e1e3b42ad537222c39095570000000000ffffffff017c790000000000002251202ac69a7e9dba801e9fcba826055917b84ca6fba4d51a29e47d478de603eedab602473044022054212984443ed4c66fc103d825bfd2da7baf2ab65d286e3c629b36b98cd7debd022050214cfe5d3b12a17aaaf1a196bfeb2f0ad15ffb320c4717eb7614162453e4fe0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + + let txid: Vec = hex("f1e708e5c5847339e16accf8716c14b33717c14d6fe68f9db36627cecbde7117") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 31_100, + sighash_type: UtxoProto::SighashType::UseDefault, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::ordinal_inscribe( + Proto::mod_Input::InputOrdinalInscription { + one_prevout: false, + inscribe_to: alice_pubkey.as_slice().into(), + mime_type: "image/png".into(), + payload: hex(common::data::NFT_INSCRIPTION_IMAGE_DATA).into(), + }, + ), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 546, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + // We enable deterministic Schnorr signatures here + dangerous_use_fixed_schnorr_rng: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + // https://www.blockchain.com/explorer/transactions/btc/173f8350b722243d44cc8db5584de76b432eb6d0888d9e66e662db51584f44ac + assert_eq!( + signed.txid, + hex("173f8350b722243d44cc8db5584de76b432eb6d0888d9e66e662db51584f44ac") + ); + + let encoded = tw_encoding::hex::encode(signed.encoded, false); + let transaction = signed.transaction.unwrap(); + + assert_eq!(encoded, common::data::NFT_INSCRIPTION_RAW_HEX); + assert_eq!(transaction.inputs.len(), 1); + assert_eq!(transaction.outputs.len(), 1); +} diff --git a/rust/tw_bitcoin/tests/p2pkh.rs b/rust/tw_bitcoin/tests/p2pkh.rs new file mode 100644 index 00000000000..cb7a49829a4 --- /dev/null +++ b/rust/tw_bitcoin/tests/p2pkh.rs @@ -0,0 +1,75 @@ +mod common; + +use common::{hex, MINER_FEE, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_emtpy() { + let _coin = TestCoinContext::default(); + let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); + + let signing = Proto::SigningInput { + private_key: alice_private_key.into(), + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&_coin, signing); + assert_eq!(signed.error, Proto::Error::OK); +} + +#[test] +fn coin_entry_sign_input_p2pkh_output_p2pkh() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); + let alice_pubkey = hex("036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536"); + let _bob_private_key = hex("b7da1ec42b19085fe09fec54b9d9eacd998ae4e6d2ad472be38d8393391b9ead"); + let bob_pubkey = hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); + + // Create transaction with P2PKH as input and output. + let txid: Vec = hex("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: ONE_BTC * 50, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC * 50 - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2pkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(bob_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "02000000017be4e642bb278018ab12277de9427773ad1c5f5b1d164a157e0d99aa48dc1c1e000000006a473044022078eda020d4b86fcb3af78ef919912e6d79b81164dbbb0b0b96da6ac58a2de4b102201a5fd8d48734d5a02371c4b5ee551a69dca3842edbf577d863cf8ae9fdbbd4590121036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536ffffffff01c0aff629010000001976a9145eaaa4f458f9158f86afcba08dd7448d27045e3d88ac00000000"); +} diff --git a/rust/tw_bitcoin/tests/p2sh.rs b/rust/tw_bitcoin/tests/p2sh.rs new file mode 100644 index 00000000000..9d8323e4e5b --- /dev/null +++ b/rust/tw_bitcoin/tests/p2sh.rs @@ -0,0 +1,147 @@ +mod common; + +use bitcoin::script::PushBytesBuf; +use bitcoin::{PublicKey, ScriptBuf}; +use common::{hex, MINER_FEE, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_bitcoin::modules::signer::Signer; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_sign_input_p2pkh_output_p2sh() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); + let alice_pubkey = hex("036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536"); + let bob_private_key = hex("b7da1ec42b19085fe09fec54b9d9eacd998ae4e6d2ad472be38d8393391b9ead"); + let bob_pubkey = hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); + + // Create transaction with P2SH as output (spend). + + let txid: Vec = hex("e7503721268d0547b3b009dab56e5ebd8bcadbfc7dfae3468a56b5cb0c07a2f7") + .into_iter() + .rev() + .collect(); + + // We use a simple P2PKH as the redeem script (ie. P2PKH embedded inside P2SH). + let bob_native_pubkey = PublicKey::from_slice(&bob_pubkey).unwrap(); + let redeem_script = ScriptBuf::new_p2pkh(&bob_native_pubkey.pubkey_hash()); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 50 * ONE_BTC, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 50 * ONE_BTC - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2sh(Proto::mod_Output::OutputRedeemScriptOrHash { + variant: ProtoOutputRedeemScriptOrHashBuilder::redeem_script( + redeem_script.as_bytes().into(), + ), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "0200000001f7a2070ccbb5568a46e3fa7dfcdbca8bbd5e6eb5da09b0b347058d26213750e7000000006a473044022007c88caf624c0a130fc79d2835ed5b6db49f2dea0d5e685f06138aaa4a904d690220243fe7744c8b48759e74a87075de3f548988252a770871fc1444652bb32ec46e0121036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536ffffffff01c0aff6290100000017a914a519b524d55ae8972e8e0e6b9d645ab20eb2635e8700000000"); + + // Create transaction with P2SH as input (claim). + + let txid: Vec = hex("5d99b77a411a879fb6fa5b442f0d121965346d8e5ab61e0d189967fd5f49bd82") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 50 * ONE_BTC - MINER_FEE, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + // The way P2SH is signed in Bitcoin, we first place the redeem script directly here. + variant: ProtoInputBuilder::p2sh(redeem_script.as_bytes().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 50 * ONE_BTC - MINER_FEE - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2pkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let mut signing = Proto::SigningInput { + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + // Generate the sighashes. + let sighashes = BitcoinEntry.preimage_hashes(&coin, signing.clone()); + assert_eq!(sighashes.error, Proto::Error::OK); + + // Sign the sighashes. + let signatures = Signer::signatures_from_proto( + &sighashes, + bob_private_key.to_vec(), + Default::default(), + false, + ) + .unwrap(); + + let sig = &signatures[0]; + + // Construc the final redeem scrip with the necessary stack items (signature + pubkey). + let mut sig_buf = PushBytesBuf::new(); + sig_buf.extend_from_slice(sig).unwrap(); + + let mut redeem_buf = PushBytesBuf::new(); + redeem_buf + .extend_from_slice(redeem_script.as_bytes()) + .unwrap(); + + let finalized = ScriptBuf::builder() + .push_slice(sig_buf) + .push_key(&bob_native_pubkey) + .push_slice(redeem_buf.as_push_bytes()) + .into_script(); + + // Now that we've signed the input, we update the input with the complete, + // finalized redeem script. + signing.inputs[0].to_recipient = ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2sh(finalized.as_bytes().into()), + }); + + // Compile the final transaction. + let signed = BitcoinEntry.compile(&coin, signing, signatures, vec![]); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "020000000182bd495ffd6799180d1eb65a8e6d346519120d2f445bfab69f871a417ab7995d000000008447304402207aad4b72c6d78c81a1e795325bd5ddb449f0a1363205903f5e37950e6b89054102202aaf4dd919700d21fe2431352df99c434378bd0d46b778b445079579300effdf0121037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf1976a9145eaaa4f458f9158f86afcba08dd7448d27045e3d88acffffffff01806de729010000001976a914e4c1ea86373d554b8f4efff2cfb0001ea19124d288ac00000000"); +} diff --git a/rust/tw_bitcoin/tests/p2tr_key_path.rs b/rust/tw_bitcoin/tests/p2tr_key_path.rs new file mode 100644 index 00000000000..8a4c617f345 --- /dev/null +++ b/rust/tw_bitcoin/tests/p2tr_key_path.rs @@ -0,0 +1,98 @@ +mod common; + +use common::{hex, MINER_FEE, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_sign_input_p2pkh_output_p2tr_key_path() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("12ce558df23528f1aa86f1f51ac7e13a197a06bda27610fa89e13b04c40ee999"); + let alice_pubkey = hex("0351e003fdc48e7f31c9bc94996c91f6c3273b7ef4208a1686021bedf7673bb058"); + let bob_private_key = hex("26c2566adcc030a1799213bfd546e615f6ab06f72085ec6806ff1761da48d227"); + let bob_pubkey = hex("02c0938cf377023dfde55e9c96b3cff4ca8894fb6b5d2009006bd43c0bff69cac9"); + + let txid: Vec = hex("c50563913e5a838f937c94232f5a8fc74e58b629fae41dfdffcc9a70f833b53a") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: ONE_BTC * 50, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC * 50 - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2tr_key_path(bob_pubkey.as_slice().into()), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "02000000013ab533f8709accfffd1de4fa29b6584ec78f5a2f23947c938f835a3e916305c5000000006b48304502210086ab2c2192e2738529d6cd9604d8ee75c5b09b0c2f4066a5c5fa3f87a26c0af602202afc7096aaa992235c43e712146057b5ed6a776d82b9129620bc5a21991c0a5301210351e003fdc48e7f31c9bc94996c91f6c3273b7ef4208a1686021bedf7673bb058ffffffff01c0aff62901000000225120e01cfdd05da8fa1d71f987373f3790d45dea9861acb0525c86656fe50f4397a600000000"); + + let txid: Vec = hex("9a582032f6a50cedaff77d3d5604b33adf8bc31bdaef8de977c2187e395860ac") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: ONE_BTC * 50 - MINER_FEE, + sighash_type: UtxoProto::SighashType::UseDefault, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2tr_key_path(Proto::mod_Input::InputTaprootKeyPath { + public_key: bob_pubkey.as_slice().into(), + one_prevout: false, + }), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC * 50 - MINER_FEE - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2tr_key_path(alice_pubkey.as_slice().into()), + }), + }; + + let signing = Proto::SigningInput { + private_key: bob_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + // We enable deterministic Schnorr signatures here + dangerous_use_fixed_schnorr_rng: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "02000000000101ac6058397e18c277e98defda1bc38bdf3ab304563d7df7afed0ca5f63220589a0000000000ffffffff01806de72901000000225120a5c027857e359d19f625e52a106b8ac6ca2d6a8728f6cf2107cd7958ee0787c20140ec2d3910d41506b60aaa20520bb72f15e2d2cbd97e3a8e26ee7bad5f4c56b0f2fb0ceaddac33cb2813a33ba017ba6b1d011bab74a0426f12a2bcf47b4ed5bc8600000000"); +} diff --git a/rust/tw_bitcoin/tests/p2tr_script_path.rs b/rust/tw_bitcoin/tests/p2tr_script_path.rs new file mode 100644 index 00000000000..7671b1dd739 --- /dev/null +++ b/rust/tw_bitcoin/tests/p2tr_script_path.rs @@ -0,0 +1,161 @@ +mod common; + +use bitcoin::taproot::LeafVersion; +use bitcoin::PublicKey; +use common::hex; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_bitcoin::modules::transactions::{BRC20TransferInscription, Brc20Ticker}; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +/// A test for the custom P2TR script-path builders. This test essentially +/// reconstruct the BRC20 transfer tests, but without using the convenience +/// builders. +fn coin_entry_custom_script_path() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); + + let txid: Vec = hex("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 1, + value: 26_400, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + // Build the BRC20 transfer outside the library, only provide essential + // information to the builder. + let ticker = Brc20Ticker::new("oadf".to_string()).unwrap(); + let inscribe_to = PublicKey::from_slice(&alice_pubkey).unwrap(); + let transfer = BRC20TransferInscription::new(inscribe_to, ticker, 20).unwrap(); + let merkle_root = transfer.inscription().spend_info().merkle_root().unwrap(); + + // Provide the public key ("internal key") and the merkle root directly to the builder. + let out1 = Proto::Output { + value: 7_000, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2tr_script_path( + Proto::mod_Output::OutputTaprootScriptPath { + internal_key: alice_pubkey.to_vec().into(), + merkle_root: merkle_root.to_vec().into(), + }, + ), + }), + }; + + // Change/return transaction. + let out2 = Proto::Output { + value: 16_400, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1, out2], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!( + signed.txid, + hex("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + ); + + let encoded = tw_encoding::hex::encode(signed.encoded, false); + let transaction = signed.transaction.unwrap(); + + assert_eq!(transaction.inputs.len(), 1); + assert_eq!(transaction.outputs.len(), 2); + assert_eq!(&encoded, "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + + // https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 + let txid: Vec = hex("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + .into_iter() + .rev() + .collect(); + + // Prepare the BRC20 payload and control block. + let payload = transfer.inscription().taproot_program().to_owned(); + let control_block = transfer + .inscription() + .spend_info() + .control_block(&(payload.to_owned(), LeafVersion::TapScript)) + .unwrap(); + + // Provide the the payload and control block directly to the builder. + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 7_000, + sighash_type: UtxoProto::SighashType::UseDefault, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2tr_script_path( + Proto::mod_Input::InputTaprootScriptPath { + one_prevout: false, + payload: payload.to_vec().into(), + control_block: control_block.serialize().into(), + }, + ), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 546, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + // We enable deterministic Schnorr signatures here + dangerous_use_fixed_schnorr_rng: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + // https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca + assert_eq!( + signed.txid, + hex("7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca") + ); + + let encoded = tw_encoding::hex::encode(signed.encoded, false); + let transaction = signed.transaction.unwrap(); + + assert_eq!(encoded, "02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03406a35548b8fa4620028e021a944c1d3dc6e947243a7bfc901bf63fefae0d2460efa149a6440cab51966aa4f09faef2d1e5efcba23ab4ca6e669da598022dbcfe35b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + assert_eq!(transaction.inputs.len(), 1); + assert_eq!(transaction.outputs.len(), 1); +} diff --git a/rust/tw_bitcoin/tests/p2wpkh.rs b/rust/tw_bitcoin/tests/p2wpkh.rs new file mode 100644 index 00000000000..ac97b68f2d9 --- /dev/null +++ b/rust/tw_bitcoin/tests/p2wpkh.rs @@ -0,0 +1,102 @@ +mod common; + +use common::{hex, MINER_FEE, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_sign_input_p2pkh_output_p2wpkh() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + let bob_private_key = hex("05dead4689ec7d55de654771120866be83bf1b8e25c9a1b77fc58a336e1cd1a3"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + // Create transaction with P2WPKH as output. + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: ONE_BTC * 50, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC * 50 - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(bob_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "020000000111b9f62923af73e297abb69f749e7a1aa2735fbdfd32ac5f6aa89e5c96841c18000000006b483045022100df9ed0b662b759e68b89a42e7144cddf787782a7129d4df05642dd825930e6e6022051a08f577f11cc7390684bbad2951a6374072253ffcf2468d14035ed0d8cd6490121028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28fffffffff01c0aff629010000001600140d0e1cec6c2babe8badde5e9b3dea667da90036d00000000"); + + // Create transaction with P2WPKH as input (claim). + + let txid: Vec = hex("858e450a1da44397bde05ca2f8a78510d74c623cc2f69736a8b3fbfadc161f6e") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: ONE_BTC * 50 - MINER_FEE, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(bob_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: ONE_BTC * 50 - MINER_FEE - MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: bob_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "020000000001016e1f16dcfafbb3a83697f6c23c624cd71085a7f8a25ce0bd9743a41d0a458e850000000000ffffffff01806de7290100000016001460cda7b50f14c152d7401c28ae773c698db9237302483045022100a9b517de5a5e036d7133df499b5b751db6f9a01576a6c5dc38229ec08b6c45cd02200e42c9f8c707c9bf0ceab4f739ec8d683dc1f1f29e195a8da9bc183584d624a60121025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f00000000"); +} diff --git a/rust/tw_bitcoin/tests/p2wsh.rs b/rust/tw_bitcoin/tests/p2wsh.rs new file mode 100644 index 00000000000..15d844e0c7d --- /dev/null +++ b/rust/tw_bitcoin/tests/p2wsh.rs @@ -0,0 +1,142 @@ +mod common; + +use bitcoin::consensus::Encodable; +use bitcoin::{PublicKey, ScriptBuf, Witness}; +use common::{hex, MINER_FEE, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_bitcoin::modules::signer::Signer; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn coin_entry_sign_input_p2pkh_output_p2wsh() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); + let alice_pubkey = hex("036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536"); + let bob_private_key = hex("b7da1ec42b19085fe09fec54b9d9eacd998ae4e6d2ad472be38d8393391b9ead"); + let bob_pubkey = hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); + + // Create transaction with P2WSH as output (spend). + + let txid: Vec = hex("c01007bb55bde4e70278e1154c34db72f34a833687d3f37443bd5c49137ee5fe") + .into_iter() + .rev() + .collect(); + + // We use a simple P2PKH as the redeem script (ie. P2PKH embedded inside P2WSH). + let bob_native_pubkey = PublicKey::from_slice(&bob_pubkey).unwrap(); + let redeem_script = ScriptBuf::new_p2pkh(&bob_native_pubkey.pubkey_hash()); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 50 * ONE_BTC - 2 * MINER_FEE, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 50 * ONE_BTC - 3 * MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wsh(Proto::mod_Output::OutputRedeemScriptOrHash { + variant: ProtoOutputRedeemScriptOrHashBuilder::redeem_script( + redeem_script.as_bytes().into(), + ), + }), + }), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + change_output: Default::default(), + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "0200000001fee57e13495cbd4374f3d38736834af372db344c15e17802e7e4bd55bb0710c0000000006b483045022100edd19b379131b9f7a05ff2a79313ccec181f2e3fd06901e27ffff4fa500dbf95022060ae976cd70b8af956fdf4582bb1289bdf233a5bace82e69ea9b7cfb55c583370121036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536ffffffff01402bd82901000000220020883a539555e537e0498732376a3d4d282e304bce7bfda6876a2b63b08a04f54400000000"); + + // Create transaction with P2WSH as input (claim). + + let txid: Vec = hex("dd9d4ca23532f5c89d016e1aacef1210ab5b9d00527c633969841daca7dd17c7") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 50 * ONE_BTC - 3 * MINER_FEE, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + // The way P2WSH is signed in Bitcoin, we first place the redeem script directly here. + variant: ProtoInputBuilder::p2wsh(redeem_script.to_bytes().into()), + }), + ..Default::default() + }; + + let out1 = Proto::Output { + value: 50 * ONE_BTC - 4 * MINER_FEE, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2pkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let mut signing = Proto::SigningInput { + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + // Generate the sighashes. + let sighashes = BitcoinEntry.preimage_hashes(&coin, signing.clone()); + + // Sign the sighashes. + let signatures = Signer::signatures_from_proto( + &sighashes, + bob_private_key.to_vec(), + Default::default(), + false, + ) + .unwrap(); + + let sig = &signatures[0]; + + // Construc the final redeem witness stack with the necessary witness stack items (signature + pubkey). + let mut finalized = Witness::new(); + finalized.push(sig); + finalized.push(bob_native_pubkey.to_bytes()); + finalized.push(redeem_script); + + // The witness stack must be encoded correctly. + let mut encoded = vec![]; + let _ = finalized.consensus_encode(&mut encoded).unwrap(); + + // Now that we've signed the input, we update the input with the complete, + // finalized redeem witness stack.. + signing.inputs[0].to_recipient = ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wsh(encoded.into()), + }); + + // Compile the final transaction. + let signed = BitcoinEntry.compile(&coin, signing, signatures, vec![]); + let encoded = tw_encoding::hex::encode(signed.encoded, false); + assert_eq!(signed.error, Proto::Error::OK); + assert_eq!(&encoded, "02000000000101c717dda7ac1d846939637c52009d5bab1012efac1a6e019dc8f53235a24c9ddd0000000000ffffffff0100e9c829010000001976a914e4c1ea86373d554b8f4efff2cfb0001ea19124d288ac0347304402201d22810b5580a49a2e73d7c4ea90754b5d70d36adb9a8f0c9cb7393da1d1d28f02207683b2e3d31a5c7e74126681f1f2a7249b7a3a918d5890ef69b94bd3bb4fb9300121037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf1976a9145eaaa4f458f9158f86afcba08dd7448d27045e3d88ac00000000"); +} diff --git a/rust/tw_bitcoin/tests/plan_builder.rs b/rust/tw_bitcoin/tests/plan_builder.rs new file mode 100644 index 00000000000..3d1ea2025f1 --- /dev/null +++ b/rust/tw_bitcoin/tests/plan_builder.rs @@ -0,0 +1,192 @@ +mod common; + +use common::{hex, ONE_BTC}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::modules::plan_builder::PlanBuilder; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn transaction_plan_compose_brc20() { + let _coin = TestCoinContext::default(); + + let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); + + let txid1: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid1.as_slice().into(), + vout: 0, + value: ONE_BTC, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let txid2: Vec = hex("858e450a1da44397bde05ca2f8a78510d74c623cc2f69736a8b3fbfadc161f6e") + .into_iter() + .rev() + .collect(); + + let tx2 = Proto::Input { + txid: txid2.as_slice().into(), + vout: 0, + value: ONE_BTC * 2, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2wpkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + let tagged_output = Proto::Output { + value: 546, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let change_output = Proto::Output { + // Will be set by the library. + value: 0, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(alice_pubkey.as_slice().into()), + }), + }), + }; + + let brc20_inscription = Proto::mod_Input::InputBrc20Inscription { + one_prevout: false, + inscribe_to: alice_pubkey.as_slice().into(), + ticker: "oadf".into(), + transfer_amount: 20, + }; + + let compose = Proto::ComposePlan { + compose: Proto::mod_ComposePlan::OneOfcompose::brc20( + Proto::mod_ComposePlan::ComposeBrc20Plan { + private_key: alice_private_key.clone().into(), + inputs: vec![tx1.clone(), tx2.clone()], + input_selector: UtxoProto::InputSelector::SelectAscending, + tagged_output: Some(tagged_output.clone()), + inscription: Some(brc20_inscription), + fee_per_vb: 25, + change_output: Some(change_output.clone()), + disable_change_output: false, + }, + ), + }; + + // Compute plan + let builder = BitcoinEntry.plan_builder().unwrap(); + let built = builder.plan(&_coin, compose); + assert_eq!(built.error, Proto::Error::OK); + + let Proto::mod_TransactionPlan::OneOfplan::brc20(plan) = built.plan else { panic!() }; + + // Check basics of the COMMIT transaction. + let commit_signing = { + let mut commit = plan.commit.unwrap(); + // One input covers all outputs. + assert_eq!(commit.version, 2); + assert_eq!(commit.private_key, alice_private_key); + assert_eq!(commit.inputs.len(), 1); + // BRC20 inscription output + change. + assert_eq!(commit.outputs.len(), 2); + // Use inputs as provided (already selected by TransactionPlan). + assert_eq!(commit.input_selector, UtxoProto::InputSelector::UseAll); + assert_eq!(commit.fee_per_vb, 0); + // Change output generation is disabled, inclulded in `commit.outputs`. + assert_eq!(commit.change_output, Default::default()); + assert!(commit.disable_change_output); + + // Check first input. + assert_eq!(commit.inputs[0], tx1); + + // Check first output. + let res_out_brc20 = &commit.outputs[0]; + assert_eq!(res_out_brc20.value, 3846); + let Proto::mod_Output::OneOfto_recipient::builder(builder) = &res_out_brc20.to_recipient else { panic!() }; + let Proto::mod_Output::mod_OutputBuilder::OneOfvariant::brc20_inscribe(brc20) = &builder.variant else { panic!() }; + assert_eq!(brc20.inscribe_to, alice_pubkey); + assert_eq!(brc20.ticker, "oadf"); + assert_eq!(brc20.transfer_amount, 20); + + // Check second output (ie. change output). + let res_out_change = &commit.outputs[1]; + assert_eq!(res_out_change.value, ONE_BTC - 3846 - 3175); // Change: tx1 value - out1 value + assert_eq!(res_out_change.to_recipient, change_output.to_recipient); + + commit.private_key = alice_private_key.clone().into(); + commit + }; + + // Check basics of the REVEAL transaction. + let reveal_signing = { + let mut reveal = plan.reveal.unwrap(); + assert_eq!(reveal.version, 2); + assert_eq!(reveal.private_key, alice_private_key); + // One inputs covers all outputs. + assert_eq!(reveal.inputs.len(), 1); + assert_eq!(reveal.outputs.len(), 1); + // Use inputs as provided. + assert_eq!(reveal.input_selector, UtxoProto::InputSelector::UseAll); + assert_eq!(reveal.fee_per_vb, 0); + // Change output generation is disabled. + assert_eq!(reveal.change_output, Default::default()); + assert!(reveal.disable_change_output); + + // Check first and only input. + let res_in_brc20 = &reveal.inputs[0]; + //assert_eq!(plan_input.txid, ) + assert_eq!(res_in_brc20.sequence, u32::MAX); + assert_eq!(res_in_brc20.value, 3846); + assert_eq!( + res_in_brc20.sighash_type, + UtxoProto::SighashType::UseDefault + ); + let Proto::mod_Input::OneOfto_recipient::builder(builder) = &res_in_brc20.to_recipient else { panic!() }; + let Proto::mod_Input::mod_InputBuilder::OneOfvariant::brc20_inscribe(brc20) = &builder.variant else { panic!() }; + assert_eq!(brc20.inscribe_to, alice_pubkey); + assert_eq!(brc20.ticker, "oadf"); + assert_eq!(brc20.transfer_amount, 20); + + // Check first and only output. + assert_eq!(reveal.outputs[0], tagged_output); + + reveal.private_key = alice_private_key.into(); + reveal + }; + + // Signed both transactions from the returned plan. + let commit_signed = BitcoinEntry.sign(&_coin, commit_signing); + assert_eq!(commit_signed.error, Proto::Error::OK); + let reveal_signed = BitcoinEntry.sign(&_coin, reveal_signing); + assert_eq!(reveal_signed.error, Proto::Error::OK); + + // Note that the API returns this in a non-reversed manner, so we need to reverse it first. + let commit_txid = commit_signed.txid.iter().copied().rev().collect::>(); + + // IMPORTANT: The input of the REVEAL transaction must reference the COMMIT transaction (Id). + assert_eq!( + commit_txid, + reveal_signed.transaction.as_ref().unwrap().inputs[0] + .txid + .as_ref() + ); + + //dbg!(&commit_signed); + //dbg!(&reveal_signed); +} diff --git a/rust/tw_bitcoin/tests/send_to_address.rs b/rust/tw_bitcoin/tests/send_to_address.rs new file mode 100644 index 00000000000..f8cddd60cb0 --- /dev/null +++ b/rust/tw_bitcoin/tests/send_to_address.rs @@ -0,0 +1,316 @@ +mod common; + +use bitcoin::{Address, PublicKey, ScriptBuf}; +use common::hex; +use secp256k1::XOnlyPublicKey; +use tw_bitcoin::aliases::*; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry::CoinEntry; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Utxo::Proto as UtxoProto; + +#[test] +fn send_to_p2sh_address() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 10_000, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + // Create the P2SH address. + let recipient = PublicKey::from_slice(&bob_pubkey).unwrap(); + // We use a simple P2PKH as the redeem script. + let redeem_script = ScriptBuf::new_p2pkh(&recipient.pubkey_hash()); + let address = Address::p2sh(&redeem_script, bitcoin::Network::Bitcoin).unwrap(); + + // The output variant is derived from the specified address. + let out1 = Proto::Output { + value: 1_000, + to_recipient: ProtoOutputRecipient::from_address(address.to_string().into()), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + let tx = signed.transaction.as_ref().unwrap(); + assert_eq!(tx.inputs.len(), 1); + assert_eq!(tx.outputs.len(), 1); + + // The expected P2SH scriptPubkey + let expected = ScriptBuf::new_p2sh(&redeem_script.script_hash()); + + assert_eq!(tx.outputs[0].value, 1_000); + assert_eq!(tx.outputs[0].script_pubkey, expected.as_bytes()); + assert!(tx.outputs[0].taproot_payload.is_empty()); + assert!(tx.outputs[0].control_block.is_empty()); +} + +#[test] +fn send_to_p2pkh_address() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 10_000, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + // Create the P2PKH address. + let recipient = PublicKey::from_slice(&bob_pubkey).unwrap(); + let address = Address::p2pkh(&recipient, bitcoin::Network::Bitcoin); + let address_string = address.to_string(); + + // The output variant is derived from the specified address. + let out1 = Proto::Output { + value: 1_000, + to_recipient: ProtoOutputRecipient::from_address(address_string.as_str().into()), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + let tx = signed.transaction.as_ref().unwrap(); + assert_eq!(tx.inputs.len(), 1); + assert_eq!(tx.outputs.len(), 1); + + // The expected P2PKH scriptPubkey + let expected = ScriptBuf::new_p2pkh(&recipient.pubkey_hash()); + + assert_eq!(tx.outputs[0].value, 1_000); + assert_eq!(tx.outputs[0].script_pubkey, expected.as_bytes()); + assert!(tx.outputs[0].taproot_payload.is_empty()); + assert!(tx.outputs[0].control_block.is_empty()); +} + +#[test] +fn send_to_p2wsh_address() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 10_000, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + // Create the P2WSH address. + let recipient = PublicKey::from_slice(&bob_pubkey).unwrap(); + // We use a simple P2PKH as the redeem script. + let redeem_script = ScriptBuf::new_p2pkh(&recipient.pubkey_hash()); + let address = Address::p2wsh(&redeem_script, bitcoin::Network::Bitcoin); + + // The output variant is derived from the specified address. + let out1 = Proto::Output { + value: 1_000, + to_recipient: ProtoOutputRecipient::from_address(address.to_string().into()), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + let tx = signed.transaction.as_ref().unwrap(); + assert_eq!(tx.inputs.len(), 1); + assert_eq!(tx.outputs.len(), 1); + + // The expected Pw2SH scriptPubkey + let expected = ScriptBuf::new_v0_p2wsh(&redeem_script.wscript_hash()); + + assert_eq!(tx.outputs[0].value, 1_000); + assert_eq!(tx.outputs[0].script_pubkey, expected.as_bytes()); + assert!(tx.outputs[0].taproot_payload.is_empty()); + assert!(tx.outputs[0].control_block.is_empty()); +} + +#[test] +fn send_to_p2wpkh_address() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 10_000, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + // Create the P2WPKH address. + let recipient = PublicKey::from_slice(&bob_pubkey).unwrap(); + let address = Address::p2wpkh(&recipient, bitcoin::Network::Bitcoin).unwrap(); + let address_string = address.to_string(); + + // The output variant is derived from the specified address. + let out1 = Proto::Output { + value: 1_000, + to_recipient: ProtoOutputRecipient::from_address(address_string.as_str().into()), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + let tx = signed.transaction.as_ref().unwrap(); + assert_eq!(tx.inputs.len(), 1); + assert_eq!(tx.outputs.len(), 1); + + // The expected P2WPKH scriptPubkey + let expected = ScriptBuf::new_v0_p2wpkh(&recipient.wpubkey_hash().unwrap()); + + assert_eq!(tx.outputs[0].value, 1_000); + assert_eq!(tx.outputs[0].script_pubkey, expected.as_bytes()); + assert!(tx.outputs[0].taproot_payload.is_empty()); + assert!(tx.outputs[0].control_block.is_empty()); +} + +#[test] +fn send_to_p2tr_key_path_address() { + let coin = TestCoinContext::default(); + + let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); + let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); + let bob_pubkey = hex("025a0af1510f0f24d40dd00d7c0e51605ca504bbc177c3e19b065f373a1efdd22f"); + + let txid: Vec = hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911") + .into_iter() + .rev() + .collect(); + + let tx1 = Proto::Input { + txid: txid.as_slice().into(), + vout: 0, + value: 10_000, + sighash_type: UtxoProto::SighashType::All, + to_recipient: ProtoInputRecipient::builder(Proto::mod_Input::InputBuilder { + variant: ProtoInputBuilder::p2pkh(alice_pubkey.as_slice().into()), + }), + ..Default::default() + }; + + // Create the P2TR key-path address. + let secp = secp256k1::Secp256k1::new(); + + let recipient = PublicKey::from_slice(&bob_pubkey).unwrap(); + let xonly = XOnlyPublicKey::from(recipient.inner); + let address = Address::p2tr(&secp, xonly, None, bitcoin::Network::Bitcoin); + let address_string = address.to_string(); + + // The output variant is derived from the specified address. + let out1 = Proto::Output { + value: 1_000, + to_recipient: ProtoOutputRecipient::from_address(address_string.as_str().into()), + }; + + let signing = Proto::SigningInput { + private_key: alice_private_key.as_slice().into(), + inputs: vec![tx1], + outputs: vec![out1], + input_selector: UtxoProto::InputSelector::UseAll, + disable_change_output: true, + ..Default::default() + }; + + let signed = BitcoinEntry.sign(&coin, signing); + assert_eq!(signed.error, Proto::Error::OK); + + let tx = signed.transaction.as_ref().unwrap(); + assert_eq!(tx.inputs.len(), 1); + assert_eq!(tx.outputs.len(), 1); + + // The expected P2TR key-path scriptPubkey + let expected = ScriptBuf::new_v1_p2tr(&secp, xonly, None); + + assert_eq!(tx.outputs[0].value, 1_000); + assert_eq!(tx.outputs[0].script_pubkey, expected.as_bytes()); + assert!(tx.outputs[0].taproot_payload.is_empty()); + assert!(tx.outputs[0].control_block.is_empty()); +} diff --git a/rust/tw_coin_entry/Cargo.toml b/rust/tw_coin_entry/Cargo.toml new file mode 100644 index 00000000000..d3df7dd56da --- /dev/null +++ b/rust/tw_coin_entry/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tw_coin_entry" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde_json = "1.0.95" +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } +tw_number = { path = "../tw_number" } +tw_proto = { path = "../tw_proto" } + +[features] +test-utils = [] diff --git a/rust/tw_coin_entry/src/coin_context.rs b/rust/tw_coin_entry/src/coin_context.rs new file mode 100644 index 00000000000..f6f4a9dbb83 --- /dev/null +++ b/rust/tw_coin_entry/src/coin_context.rs @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_hash::hasher::Hasher; +use tw_keypair::tw::PublicKeyType; + +/// Extend the trait with methods required for blockchain additions. +pub trait CoinContext { + /// Necessary chain property. + fn public_key_type(&self) -> PublicKeyType; + + /// Optional chain property. + fn address_hasher(&self) -> Option; + + /// Optional chain property. + fn hrp(&self) -> Option; +} diff --git a/rust/tw_coin_entry/src/coin_entry.rs b/rust/tw_coin_entry/src/coin_entry.rs new file mode 100644 index 00000000000..785021d409b --- /dev/null +++ b/rust/tw_coin_entry/src/coin_entry.rs @@ -0,0 +1,112 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use crate::derivation::Derivation; +use crate::error::AddressResult; +use crate::modules::json_signer::JsonSigner; +use crate::modules::plan_builder::PlanBuilder; +use crate::prefix::Prefix; +use std::fmt; +use tw_keypair::tw::PublicKey; +use tw_memory::Data; +use tw_proto::{MessageRead, MessageWrite}; + +use crate::modules::message_signer::MessageSigner; +pub use tw_proto::{ProtoError, ProtoResult}; + +pub type PrivateKeyBytes = Data; +pub type SignatureBytes = Data; +pub type PublicKeyBytes = Data; + +pub trait CoinAddress: fmt::Display { + fn data(&self) -> Data; +} + +/// The main coin entry trait. It is responsible for address management and the transaction signing. +/// +/// # Maintaining +/// +/// Please sync them with the code generator template if there is need to make any changes in this trait +/// (e.g adding/deleting a method or an associated type): +/// https://github.com/trustwallet/wallet-core/blob/master/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs +pub trait CoinEntry { + type AddressPrefix: Prefix; + type Address: CoinAddress; + type SigningInput<'a>: MessageRead<'a> + MessageWrite; + type SigningOutput: MessageWrite; + type PreSigningOutput: MessageWrite; + + // Optional modules: + type JsonSigner: JsonSigner; + type PlanBuilder: PlanBuilder; + type MessageSigner: MessageSigner; + + /// Tries to parse `Self::Address` from the given `address` string by `coin` type and address `prefix`. + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult; + + /// Tries to parse `Self::Address` from the given `address` string by `coin` type. + /// Please note that this method does not check if the address belongs to the given chain. + fn parse_address_unchecked( + &self, + coin: &dyn CoinContext, + address: &str, + ) -> AddressResult; + + /// Derives an address associated with the given `public_key` by `coin` context, `derivation` and address `prefix`. + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + derivation: Derivation, + prefix: Option, + ) -> AddressResult; + + /// Signs a transaction declared as the given `input`. + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput; + + /// Returns hash(es) for signing, needed for external signing. + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput; + + /// Compiles a transaction with externally-supplied `signatures` and `public_keys`. + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput; + + /// It is optional, Signing JSON input with private key. + /// Returns `Ok(None)` if the chain doesn't support signing JSON. + #[inline] + fn json_signer(&self) -> Option { + None + } + + /// Planning, for UTXO chains, in preparation for signing. + /// Returns an optional `Plan` builder. Only UTXO chains need it. + #[inline] + fn plan_builder(&self) -> Option { + None + } + + /// It is optional, Signing regular messages. + /// Returns `Ok(None)` if the chain doesn't support regular message signing. + #[inline] + fn message_signer(&self) -> Option { + None + } +} diff --git a/rust/tw_coin_entry/src/coin_entry_ext.rs b/rust/tw_coin_entry/src/coin_entry_ext.rs new file mode 100644 index 00000000000..d3daf007c4a --- /dev/null +++ b/rust/tw_coin_entry/src/coin_entry_ext.rs @@ -0,0 +1,212 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use crate::coin_entry::{CoinAddress, CoinEntry, PublicKeyBytes, SignatureBytes}; +use crate::derivation::Derivation; +use crate::error::SigningResult; +use crate::error::{AddressResult, SigningError, SigningErrorType}; +use crate::modules::json_signer::JsonSigner; +use crate::modules::message_signer::MessageSigner; +use crate::modules::plan_builder::PlanBuilder; +use crate::prefix::AddressPrefix; +use tw_keypair::tw::{PrivateKey, PublicKey}; +use tw_memory::Data; +use tw_proto::{deserialize, serialize, ProtoResult}; + +pub type PrivateKeyBytes = Data; + +/// The [`CoinEntry`] trait extension. +pub trait CoinEntryExt { + fn validate_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult<()>; + + /// Validates and normalizes the given `address`. + fn normalize_address(&self, coin: &dyn CoinContext, address: &str) -> AddressResult; + + /// Derives an address associated with the given `public_key` by `coin` context, `derivation` and address `prefix`. + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + derivation: Derivation, + prefix: Option, + ) -> AddressResult; + + /// Returns underlying data (public key or key hash). + fn address_to_data(&self, coin: &dyn CoinContext, address: &str) -> AddressResult; + + /// Signs a transaction declared as the given `input`. + fn sign(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult; + + /// Returns `true` if the chain supports JSON signing. + fn supports_json_signing(&self) -> bool; + + /// Signs a transaction specified by the JSON representation of signing input and a private key. + /// Returns the JSON representation of the signing output if the blockchain supports JSON signing. + /// Checks [`CoinEntryExt::supports_json_signing`]. + fn sign_json( + &self, + coin: &dyn CoinContext, + input_json: &str, + private_key: PrivateKeyBytes, + ) -> SigningResult; + + /// Returns hash(es) for signing, needed for external signing. + /// It will return a proto object named `PreSigningOutput` which will include hash. + /// We provide a default `PreSigningOutput` in TransactionCompiler.proto. + fn preimage_hashes(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult; + + /// Compiles a transaction with externally-supplied `signatures` and `public_keys`. + fn compile( + &self, + coin: &dyn CoinContext, + input: &[u8], + signatures: Vec, + public_keys: Vec, + ) -> ProtoResult; + + /// Plans a transaction (for UTXO chains only). + fn plan(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult; + + /// Signs a message. + fn sign_message(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult; + + /// Computes preimage hashes of a message. + fn message_preimage_hashes(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult; + + /// Verifies a signature for a message. + fn verify_message(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult; +} + +impl CoinEntryExt for T +where + T: CoinEntry, +{ + fn validate_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult<()> { + let prefix = prefix.map(T::AddressPrefix::try_from).transpose()?; + self.parse_address(coin, address, prefix).map(|_| ()) + } + + fn normalize_address(&self, coin: &dyn CoinContext, address: &str) -> AddressResult { + // Parse the address and display it. + // Please note that `Self::Address::to_string()` returns a normalize address. + ::parse_address_unchecked(self, coin, address) + .map(|addr| addr.to_string()) + } + + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + derivation: Derivation, + prefix: Option, + ) -> AddressResult { + let prefix = prefix + .map(::AddressPrefix::try_from) + .transpose()?; + ::derive_address(self, coin, public_key, derivation, prefix) + .map(|addr| addr.to_string()) + } + + fn address_to_data(&self, coin: &dyn CoinContext, address: &str) -> AddressResult { + self.parse_address_unchecked(coin, address) + .map(|addr| addr.data()) + } + + fn sign(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult { + let input: T::SigningInput<'_> = deserialize(input)?; + let output = ::sign(self, coin, input); + serialize(&output) + } + + fn supports_json_signing(&self) -> bool { + self.json_signer().is_some() + } + + fn sign_json( + &self, + coin: &dyn CoinContext, + input_json: &str, + private_key: PrivateKeyBytes, + ) -> SigningResult { + let Some(json_signer) = self.json_signer() else { + return Err(SigningError(SigningErrorType::Error_not_supported)); + }; + + let private_key = PrivateKey::new(private_key)?; + json_signer.sign_json(coin, input_json, &private_key) + } + + fn preimage_hashes(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult { + let input: T::SigningInput<'_> = deserialize(input)?; + let output = ::preimage_hashes(self, coin, input); + serialize(&output) + } + + fn compile( + &self, + coin: &dyn CoinContext, + input: &[u8], + signatures: Vec, + public_keys: Vec, + ) -> ProtoResult { + let input: T::SigningInput<'_> = deserialize(input)?; + let output = self.compile(coin, input, signatures, public_keys); + serialize(&output) + } + + fn plan(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult { + let Some(plan_builder) = self.plan_builder() else { + return Err(SigningError(SigningErrorType::Error_not_supported)); + }; + + let input: ::SigningInput<'_> = deserialize(input)?; + let output = plan_builder.plan(coin, input); + serialize(&output).map_err(SigningError::from) + } + + fn sign_message(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult { + let Some(message_signer) = self.message_signer() else { + return Err(SigningError(SigningErrorType::Error_not_supported)); + }; + + let input: ::MessageSigningInput<'_> = + deserialize(input)?; + let output = message_signer.sign_message(coin, input); + serialize(&output).map_err(SigningError::from) + } + + fn message_preimage_hashes(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult { + let Some(message_signer) = self.message_signer() else { + return Err(SigningError(SigningErrorType::Error_not_supported)); + }; + + let input: ::MessageSigningInput<'_> = + deserialize(input)?; + let output = message_signer.message_preimage_hashes(coin, input); + serialize(&output).map_err(SigningError::from) + } + + fn verify_message(&self, coin: &dyn CoinContext, input: &[u8]) -> SigningResult { + let Some(message_signer) = self.message_signer() else { + return Err(SigningError(SigningErrorType::Error_not_supported)); + }; + + let input: ::MessageVerifyingInput<'_> = + deserialize(input)?; + Ok(message_signer.verify_message(coin, input)) + } +} diff --git a/rust/tw_coin_entry/src/common/compile_input.rs b/rust/tw_coin_entry/src/common/compile_input.rs new file mode 100644 index 00000000000..fc3dfa5f5fb --- /dev/null +++ b/rust/tw_coin_entry/src/common/compile_input.rs @@ -0,0 +1,38 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_entry::{PublicKeyBytes, SignatureBytes}; +use crate::error::{SigningError, SigningErrorType, SigningResult}; + +pub struct SingleSignaturePubkey { + pub signature: SignatureBytes, + pub public_key: PublicKeyBytes, +} + +impl SingleSignaturePubkey { + pub fn from_sign_pubkey_list( + signatures: Vec, + public_keys: Vec, + ) -> SigningResult { + if signatures.len() > 1 || public_keys.len() > 1 { + return Err(SigningError(SigningErrorType::Error_no_support_n2n)); + } + + let signature = signatures + .into_iter() + .next() + .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; + let public_key = public_keys + .into_iter() + .next() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + Ok(SingleSignaturePubkey { + signature, + public_key, + }) + } +} diff --git a/rust/tw_coin_entry/src/common/mod.rs b/rust/tw_coin_entry/src/common/mod.rs new file mode 100644 index 00000000000..59492cc4690 --- /dev/null +++ b/rust/tw_coin_entry/src/common/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod compile_input; diff --git a/rust/tw_coin_entry/src/derivation.rs b/rust/tw_coin_entry/src/derivation.rs new file mode 100644 index 00000000000..29378ffa176 --- /dev/null +++ b/rust/tw_coin_entry/src/derivation.rs @@ -0,0 +1,24 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +/// Extend this enum. +#[derive(Default)] +#[repr(u32)] +pub enum Derivation { + /// Default derivation. + #[default] + Default = 0, +} + +impl Derivation { + #[inline] + pub fn from_raw(derivation: u32) -> Option { + match derivation { + 0 => Some(Derivation::Default), + _ => None, + } + } +} diff --git a/rust/tw_coin_entry/src/error.rs b/rust/tw_coin_entry/src/error.rs new file mode 100644 index 00000000000..179e8b73b3f --- /dev/null +++ b/rust/tw_coin_entry/src/error.rs @@ -0,0 +1,129 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::fmt; +use std::fmt::Formatter; +use tw_encoding::EncodingError; +use tw_keypair::KeyPairError; +use tw_number::NumberError; +use tw_proto::Common::Proto; +use tw_proto::ProtoError; + +#[macro_export] +macro_rules! signing_output_error { + ($output:ty, $error:expr) => {{ + let err = $error; + + let mut output = <$output>::default(); + output.error = err.0; + output.error_message = std::borrow::Cow::from(err.to_string()); + + output + }}; +} + +pub type AddressResult = Result; + +#[derive(Debug, Eq, PartialEq)] +pub enum AddressError { + UnknownCoinType, + MissingPrefix, + FromHexError, + PublicKeyTypeMismatch, + UnexpectedAddressPrefix, + UnexpectedHasher, + InvalidHrp, + InvalidInput, +} + +pub type SigningResult = Result; +pub type SigningErrorType = Proto::SigningError; + +/// The wrapper over the `Common::SigningErrorType` error for convenient use. +#[derive(Debug)] +pub struct SigningError(pub SigningErrorType); + +impl From for SigningError { + #[inline] + fn from(_err: NumberError) -> Self { + SigningError(SigningErrorType::Error_invalid_params) + } +} + +impl From for SigningError { + #[inline] + fn from(_err: AddressError) -> Self { + SigningError(SigningErrorType::Error_invalid_address) + } +} + +impl From for SigningError { + fn from(_value: serde_json::Error) -> Self { + SigningError(SigningErrorType::Error_input_parse) + } +} + +impl From for SigningError { + fn from(_e: EncodingError) -> Self { + SigningError(SigningErrorType::Error_input_parse) + } +} + +impl From for SigningError { + fn from(err: KeyPairError) -> Self { + match err { + KeyPairError::InvalidSecretKey => { + SigningError(SigningErrorType::Error_invalid_private_key) + }, + KeyPairError::InvalidPublicKey + | KeyPairError::InvalidSignature + | KeyPairError::InvalidSignMessage + | KeyPairError::SignatureVerifyError => { + SigningError(SigningErrorType::Error_invalid_params) + }, + KeyPairError::SigningError => SigningError(SigningErrorType::Error_signing), + } + } +} + +impl From for SigningError { + fn from(_e: ProtoError) -> Self { + SigningError(SigningErrorType::Error_input_parse) + } +} + +impl fmt::Display for SigningError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let str = match self.0 { + SigningErrorType::OK => "", + SigningErrorType::Error_general => "Unknown error", + SigningErrorType::Error_internal => "Internal error", + SigningErrorType::Error_low_balance => "Low balance: the sender balance is not enough to cover the send and other auxiliary amount such as fee, deposit, or minimal balance", + SigningErrorType::Error_zero_amount_requested => "Requested amount is zero, send of 0 makes no sense", + SigningErrorType::Error_missing_private_key => "One required key is missing (too few or wrong keys are provided)", + SigningErrorType::Error_invalid_private_key => "A private key provided is invalid (e.g. wrong size, usually should be 32 bytes)", + SigningErrorType::Error_invalid_address => "A provided address (e.g. destination address) is invalid", + SigningErrorType::Error_invalid_utxo => "A provided input UTXO is invalid", + SigningErrorType::Error_invalid_utxo_amount => "The amount of an input UTXO is invalid", + SigningErrorType::Error_wrong_fee => "Wrong fee is given, probably it is too low to cover minimal fee for the transaction", + SigningErrorType::Error_signing => "General signing error", + SigningErrorType::Error_tx_too_big => "Resulting transaction is too large", + SigningErrorType::Error_missing_input_utxos => "No input UTXOs provided", + SigningErrorType::Error_not_enough_utxos => "Not enough non-dust input UTXOs to cover requested amount (dust UTXOs are filtered out)", + SigningErrorType::Error_script_redeem => "Missing required redeem script", + SigningErrorType::Error_script_output => "Invalid required output script", + SigningErrorType::Error_script_witness_program => "Unrecognized witness program", + SigningErrorType::Error_invalid_memo => "Invalid memo", + SigningErrorType::Error_input_parse => "Some input field cannot be parsed", + SigningErrorType::Error_no_support_n2n => "Multi-input and multi-output transaction not supported", + SigningErrorType::Error_signatures_count => "Incorrect count of signatures passed to compile", + SigningErrorType::Error_invalid_params => "Incorrect input parameter", + SigningErrorType::Error_invalid_requested_token_amount => "Invalid input token amount", + SigningErrorType::Error_not_supported => "Operation not supported for the chain", + }; + write!(f, "{str}") + } +} diff --git a/rust/tw_coin_entry/src/lib.rs b/rust/tw_coin_entry/src/lib.rs new file mode 100644 index 00000000000..fd45ec0f96b --- /dev/null +++ b/rust/tw_coin_entry/src/lib.rs @@ -0,0 +1,17 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod coin_context; +pub mod coin_entry; +pub mod coin_entry_ext; +pub mod common; +pub mod derivation; +pub mod error; +pub mod modules; +pub mod prefix; + +#[cfg(feature = "test-utils")] +pub mod test_utils; diff --git a/rust/tw_coin_entry/src/modules/json_signer.rs b/rust/tw_coin_entry/src/modules/json_signer.rs new file mode 100644 index 00000000000..8757c5ed40d --- /dev/null +++ b/rust/tw_coin_entry/src/modules/json_signer.rs @@ -0,0 +1,33 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use crate::error::SigningResult; +use tw_keypair::tw::PrivateKey; + +pub trait JsonSigner { + /// Signs the given JSON input with the private key. + fn sign_json( + &self, + coin: &dyn CoinContext, + input_json: &str, + key: &PrivateKey, + ) -> SigningResult; +} + +/// `NoJsonSigner` can't be created since there are no enum variants. +pub enum NoJsonSigner {} + +impl JsonSigner for NoJsonSigner { + fn sign_json( + &self, + _coin: &dyn CoinContext, + _input_json: &str, + _key: &PrivateKey, + ) -> SigningResult { + panic!("`NoJsonSigner` should never be constructed and used") + } +} diff --git a/rust/tw_coin_entry/src/modules/message_signer.rs b/rust/tw_coin_entry/src/modules/message_signer.rs new file mode 100644 index 00000000000..141c30352f1 --- /dev/null +++ b/rust/tw_coin_entry/src/modules/message_signer.rs @@ -0,0 +1,70 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use tw_proto::{DummyMessage, MessageRead, MessageWrite, NoMessage}; + +pub trait MessageSigner { + type MessageSigningInput<'a>: MessageRead<'a>; + type MessagePreSigningOutput: MessageWrite; + type MessageSigningOutput: MessageWrite; + type MessageVerifyingInput<'a>: MessageRead<'a>; + + /// Returns hash(es) for signing, needed for external signing. + fn message_preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::MessageSigningInput<'_>, + ) -> Self::MessagePreSigningOutput; + + /// Signs a message. + fn sign_message( + &self, + coin: &dyn CoinContext, + input: Self::MessageSigningInput<'_>, + ) -> Self::MessageSigningOutput; + + /// Verifies signature. + fn verify_message( + &self, + _coin: &dyn CoinContext, + input: Self::MessageVerifyingInput<'_>, + ) -> bool; +} + +/// `NoMessageSigner` can't be created since there are no enum variants. +pub enum NoMessageSigner {} + +impl MessageSigner for NoMessageSigner { + type MessageSigningInput<'a> = DummyMessage; + type MessagePreSigningOutput = NoMessage; + type MessageSigningOutput = NoMessage; + type MessageVerifyingInput<'a> = DummyMessage; + + fn message_preimage_hashes( + &self, + _coin: &dyn CoinContext, + _input: Self::MessageSigningInput<'_>, + ) -> Self::MessagePreSigningOutput { + panic!("`NoMessageSigner` should never be constructed and used") + } + + fn sign_message( + &self, + _coin: &dyn CoinContext, + _input: Self::MessageSigningInput<'_>, + ) -> Self::MessageSigningOutput { + panic!("`NoMessageSigner` should never be constructed and used") + } + + fn verify_message( + &self, + _coin: &dyn CoinContext, + _input: Self::MessageVerifyingInput<'_>, + ) -> bool { + panic!("`NoMessageSigner` should never be constructed and used") + } +} diff --git a/rust/tw_coin_entry/src/modules/mod.rs b/rust/tw_coin_entry/src/modules/mod.rs new file mode 100644 index 00000000000..6f8b8b891bf --- /dev/null +++ b/rust/tw_coin_entry/src/modules/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 directory contains optional modules that some chain may or may not implement. + +pub mod json_signer; +pub mod message_signer; +pub mod plan_builder; diff --git a/rust/tw_coin_entry/src/modules/plan_builder.rs b/rust/tw_coin_entry/src/modules/plan_builder.rs new file mode 100644 index 00000000000..fd5b9f1b828 --- /dev/null +++ b/rust/tw_coin_entry/src/modules/plan_builder.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use tw_proto::{DummyMessage, MessageRead, MessageWrite, NoMessage}; + +pub trait PlanBuilder { + type SigningInput<'a>: MessageRead<'a>; + type Plan: MessageWrite; + + /// Planning, for UTXO chains, in preparation for signing. + fn plan(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::Plan; +} + +/// `NoInputBuilder` can't be created since there are no enum variants. +pub enum NoPlanBuilder {} + +impl PlanBuilder for NoPlanBuilder { + type SigningInput<'a> = DummyMessage; + type Plan = NoMessage; + + /// [`PlanBuilder::plan`] should never be called. + fn plan(&self, _coin: &dyn CoinContext, _input: Self::SigningInput<'_>) -> Self::Plan { + panic!("`NoPlanBuilder` should never be constructed and used") + } +} diff --git a/rust/tw_coin_entry/src/prefix.rs b/rust/tw_coin_entry/src/prefix.rs new file mode 100644 index 00000000000..52898e19ee5 --- /dev/null +++ b/rust/tw_coin_entry/src/prefix.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::error::AddressError; + +/// Extend when adding new blockchains. +#[derive(Clone)] +pub enum AddressPrefix { + Hrp(String), +} + +pub trait Prefix: TryFrom {} + +impl Prefix for T where T: TryFrom {} + +/// There is no way to create an instance of the `NoPrefix` enum as it doesn't has variants. +#[derive(Debug)] +pub enum NoPrefix {} + +impl TryFrom for NoPrefix { + type Error = AddressError; + + #[inline] + fn try_from(_: AddressPrefix) -> Result { + Err(AddressError::UnexpectedAddressPrefix) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_no_prefix() { + NoPrefix::try_from(AddressPrefix::Hrp("".to_string())).unwrap_err(); + } +} diff --git a/rust/tw_coin_entry/src/test_utils/mod.rs b/rust/tw_coin_entry/src/test_utils/mod.rs new file mode 100644 index 00000000000..4cbcc23d463 --- /dev/null +++ b/rust/tw_coin_entry/src/test_utils/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod test_context; diff --git a/rust/tw_coin_entry/src/test_utils/test_context.rs b/rust/tw_coin_entry/src/test_utils/test_context.rs new file mode 100644 index 00000000000..50d31585a22 --- /dev/null +++ b/rust/tw_coin_entry/src/test_utils/test_context.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::PublicKeyType; + +/// Test coin context that panics on any `CoinContext` method call. +#[derive(Default)] +pub struct TestCoinContext { + pub public_key_type: Option, + pub address_hasher: Option, + pub hrp: Option, +} + +impl TestCoinContext { + pub fn with_public_key_type(mut self, public_key_type: PublicKeyType) -> TestCoinContext { + self.public_key_type = Some(public_key_type); + self + } + + pub fn with_hrp(mut self, hrp: &str) -> TestCoinContext { + self.hrp = Some(hrp.to_string()); + self + } +} + +impl CoinContext for TestCoinContext { + fn public_key_type(&self) -> PublicKeyType { + self.public_key_type + .expect("EmptyCoinContext::public_key_type was not set") + } + + fn address_hasher(&self) -> Option { + self.address_hasher + } + + fn hrp(&self) -> Option { + self.hrp.clone() + } +} diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml new file mode 100644 index 00000000000..a63ba08b267 --- /dev/null +++ b/rust/tw_coin_registry/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tw_coin_registry" +version = "0.1.0" +edition = "2021" + +[dependencies] +lazy_static = "1.4.0" +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +strum = "0.25" +strum_macros = "0.25" +tw_aptos = { path = "../tw_aptos" } +tw_bitcoin = { path = "../tw_bitcoin" } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_cosmos = { path = "../chains/tw_cosmos" } +tw_ethereum = { path = "../tw_ethereum" } +tw_evm = { path = "../tw_evm" } +tw_hash = { path = "../tw_hash" } +tw_internet_computer = { path = "../tw_internet_computer" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } +tw_native_evmos = { path = "../chains/tw_native_evmos" } +tw_native_injective = { path = "../chains/tw_native_injective" } +tw_ronin = { path = "../tw_ronin" } +tw_thorchain = { path = "../chains/tw_thorchain" } + +[build-dependencies] +itertools = "0.10.5" +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" diff --git a/rust/tw_coin_registry/build.rs b/rust/tw_coin_registry/build.rs new file mode 100644 index 00000000000..4c79c1d01f4 --- /dev/null +++ b/rust/tw_coin_registry/build.rs @@ -0,0 +1,105 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use itertools::Itertools; +use serde::Deserialize; +use std::io::Write; +use std::path::PathBuf; +use std::{env, fs}; + +/// We're only interested in `id` and `name` of the coin to generate `CoinType` enum. +#[derive(Deserialize)] +struct CoinItem { + #[serde(rename = "coinId")] + coin_id: u64, + name: String, +} + +/// Transforms a coin name to a Rust name. +/// https://github.com/trustwallet/wallet-core/blob/3769f31b7d0c75126b2f426bb065364429aaa379/codegen/lib/coin_skeleton_gen.rb#L15-L22 +fn format_name(name: &str) -> String { + name.replace([' ', '.', '-'], "") +} + +fn generate_coin_type(coins: &[CoinItem]) -> String { + const RAW_TYPE: &str = "u32"; + const ENUM_NAME: &str = "CoinType"; + + let coin_types_variants = coins + .iter() + .map(|coin| format!("\t{} = {},\n", format_name(&coin.name), coin.coin_id)) + .join(""); + + format!( + r#"#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(strum_macros::EnumIter, strum_macros::FromRepr)] +#[repr({RAW_TYPE})] +pub enum CoinType {{ +{coin_types_variants} +}} + +impl {ENUM_NAME} {{ + pub fn iter() -> impl IntoIterator {{ + use strum::IntoEnumIterator; + + ::iter() + }} +}} + +impl TryFrom<{RAW_TYPE}> for {ENUM_NAME} {{ + type Error = (); + + fn try_from(num: {RAW_TYPE}) -> Result<{ENUM_NAME}, ()> {{ + {ENUM_NAME}::from_repr(num).ok_or(()) + }} +}} + +impl<'de> serde::Deserialize<'de> for {ENUM_NAME} {{ + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + {{ + let num_value: {RAW_TYPE} = {RAW_TYPE}::deserialize(deserializer)?; + {ENUM_NAME}::try_from(num_value).map_err(|_| serde::de::Error::custom("Invalid `CoinType` value")) + }} +}} +"# + ) +} + +fn generate_and_write_coin_type(coins: &[CoinItem]) { + let coin_type_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("coin_type.rs"); + + let coin_type_content = generate_coin_type(coins); + + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(coin_type_path) + .expect("Error creating/opening coin_type.rs"); + file.write_all(coin_type_content.as_bytes()) + .expect("Error writing coin_type.rs"); +} + +fn main() { + let registry_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("..") + .join("registry.json"); + let registry_path_str = registry_path.to_str().unwrap(); + + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={registry_path_str}"); + + let registry_bytes = fs::read(registry_path).expect("Error reading registry.json"); + + let coins: Vec = + serde_json::from_slice(®istry_bytes).expect("registry.json expected to be valid"); + + generate_and_write_coin_type(&coins); +} diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs new file mode 100644 index 00000000000..357697cd83c --- /dev/null +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -0,0 +1,32 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; + +/// Blockchain implementation type. +/// Extend this enum when adding new blockchains. +#[derive(Copy, Clone, Debug, Deserialize, PartialEq)] +pub enum BlockchainType { + // start_of_blockchain_type - USED TO GENERATE CODE + Aptos, + Bitcoin, + Cosmos, + Ethereum, + InternetComputer, + NativeEvmos, + NativeInjective, + Ronin, + Thorchain, + // end_of_blockchain_type - USED TO GENERATE CODE + #[serde(other)] + Unsupported, +} + +impl BlockchainType { + pub fn is_supported(&self) -> bool { + !matches!(self, BlockchainType::Unsupported) + } +} diff --git a/rust/tw_coin_registry/src/coin_context.rs b/rust/tw_coin_registry/src/coin_context.rs new file mode 100644 index 00000000000..2185e7615e6 --- /dev/null +++ b/rust/tw_coin_registry/src/coin_context.rs @@ -0,0 +1,38 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::registry::CoinItem; +use tw_coin_entry::coin_context::CoinContext; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::PublicKeyType; + +pub struct CoinRegistryContext { + item: &'static CoinItem, +} + +impl CoinRegistryContext { + #[inline] + pub fn with_coin_item(item: &'static CoinItem) -> CoinRegistryContext { + CoinRegistryContext { item } + } +} + +impl CoinContext for CoinRegistryContext { + #[inline] + fn public_key_type(&self) -> PublicKeyType { + self.item.public_key_type + } + + #[inline] + fn address_hasher(&self) -> Option { + self.item.address_hasher + } + + #[inline] + fn hrp(&self) -> Option { + self.item.hrp.clone() + } +} diff --git a/rust/tw_coin_registry/src/coin_type.rs b/rust/tw_coin_registry/src/coin_type.rs new file mode 100644 index 00000000000..d03c00f87ca --- /dev/null +++ b/rust/tw_coin_registry/src/coin_type.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +/// TODO make this enum generated from `registry.json`. +pub type CoinType = u32; diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs new file mode 100644 index 00000000000..61e6f0d5998 --- /dev/null +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -0,0 +1,72 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::blockchain_type::BlockchainType; +use crate::coin_context::CoinRegistryContext; +use crate::coin_type::CoinType; +use crate::error::{RegistryError, RegistryResult}; +use crate::registry::get_coin_item; +use tw_aptos::entry::AptosEntry; +use tw_bitcoin::entry::BitcoinEntry; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_cosmos::entry::CosmosEntry; +use tw_ethereum::entry::EthereumEntry; +use tw_evm::evm_entry::EvmEntryExt; +use tw_internet_computer::entry::InternetComputerEntry; +use tw_native_evmos::entry::NativeEvmosEntry; +use tw_native_injective::entry::NativeInjectiveEntry; +use tw_ronin::entry::RoninEntry; +use tw_thorchain::entry::ThorchainEntry; + +pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; +pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; + +// start_of_blockchain_entries - USED TO GENERATE CODE +const APTOS: AptosEntry = AptosEntry; +const BITCOIN: BitcoinEntry = BitcoinEntry; +const COSMOS: CosmosEntry = CosmosEntry; +const ETHEREUM: EthereumEntry = EthereumEntry; +const INTERNET_COMPUTER: InternetComputerEntry = InternetComputerEntry; +const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; +const NATIVE_INJECTIVE: NativeInjectiveEntry = NativeInjectiveEntry; +const RONIN: RoninEntry = RoninEntry; +const THORCHAIN: ThorchainEntry = ThorchainEntry; +// end_of_blockchain_entries - USED TO GENERATE CODE + +pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { + match blockchain { + // start_of_blockchain_dispatcher - USED TO GENERATE CODE + BlockchainType::Aptos => Ok(&APTOS), + BlockchainType::Bitcoin => Ok(&BITCOIN), + BlockchainType::Cosmos => Ok(&COSMOS), + BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), + BlockchainType::NativeEvmos => Ok(&NATIVE_EVMOS), + BlockchainType::NativeInjective => Ok(&NATIVE_INJECTIVE), + BlockchainType::Ronin => Ok(&RONIN), + BlockchainType::Thorchain => Ok(&THORCHAIN), + // end_of_blockchain_dispatcher - USED TO GENERATE CODE + BlockchainType::Unsupported => Err(RegistryError::Unsupported), + } +} + +pub fn coin_dispatcher( + coin: CoinType, +) -> RegistryResult<(CoinRegistryContext, CoinEntryExtStaticRef)> { + let item = get_coin_item(coin)?; + let coin_entry = blockchain_dispatcher(item.blockchain)?; + let coin_context = CoinRegistryContext::with_coin_item(item); + Ok((coin_context, coin_entry)) +} + +pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { + let item = get_coin_item(coin)?; + match item.blockchain { + BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::Ronin => Ok(&RONIN), + _ => Err(RegistryError::Unsupported), + } +} diff --git a/rust/tw_coin_registry/src/error.rs b/rust/tw_coin_registry/src/error.rs new file mode 100644 index 00000000000..52857b83564 --- /dev/null +++ b/rust/tw_coin_registry/src/error.rs @@ -0,0 +1,25 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::{SigningError, SigningErrorType}; + +pub type RegistryResult = Result; + +#[derive(Debug)] +pub enum RegistryError { + UnknownCoinType, + Unsupported, +} + +impl From for SigningError { + #[inline] + fn from(e: RegistryError) -> Self { + match e { + RegistryError::UnknownCoinType => SigningError(SigningErrorType::Error_invalid_params), + RegistryError::Unsupported => SigningError(SigningErrorType::Error_internal), + } + } +} diff --git a/rust/tw_coin_registry/src/lib.rs b/rust/tw_coin_registry/src/lib.rs new file mode 100644 index 00000000000..d8e50a8f1c6 --- /dev/null +++ b/rust/tw_coin_registry/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod blockchain_type; +pub mod coin_context; +pub mod dispatcher; +pub mod error; +pub mod registry; + +pub mod coin_type { + include!(concat!(env!("OUT_DIR"), "/coin_type.rs")); +} diff --git a/rust/tw_coin_registry/src/registry.rs b/rust/tw_coin_registry/src/registry.rs new file mode 100644 index 00000000000..58b6fef8cc7 --- /dev/null +++ b/rust/tw_coin_registry/src/registry.rs @@ -0,0 +1,65 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::blockchain_type::BlockchainType; +use crate::coin_type::CoinType; +use crate::error::{RegistryError, RegistryResult}; +use lazy_static::lazy_static; +use serde::Deserialize; +use std::collections::HashMap; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::PublicKeyType; + +type RegistryMap = HashMap; + +/// cbindgen:ignore +const REGISTRY_JSON: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../registry.json")); + +lazy_static! { + static ref REGISTRY: RegistryMap = parse_registry_json(); +} + +/// Extend this structure according to `registry.json`. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CoinItem { + pub id: String, + pub name: String, + pub coin_id: CoinType, + pub blockchain: BlockchainType, + pub public_key_type: PublicKeyType, + pub address_hasher: Option, + pub hrp: Option, +} + +#[inline] +pub fn get_coin_item(coin: CoinType) -> RegistryResult<&'static CoinItem> { + REGISTRY.get(&coin).ok_or(RegistryError::UnknownCoinType) +} + +#[inline] +pub fn registry_iter() -> impl Iterator { + REGISTRY.iter().map(|(_coin_type, item)| item) +} + +#[inline] +pub fn supported_coin_items() -> impl Iterator { + registry_iter().filter(|item| !matches!(item.blockchain, BlockchainType::Unsupported)) +} + +#[inline] +pub fn coin_items_by_blockchain( + blockchain: BlockchainType, +) -> impl Iterator { + registry_iter().filter(move |item| item.blockchain == blockchain) +} + +fn parse_registry_json() -> RegistryMap { + let items: Vec = + serde_json::from_str(REGISTRY_JSON).expect("registry.json expected to be valid"); + items.into_iter().map(|item| (item.coin_id, item)).collect() +} diff --git a/rust/tw_cosmos_sdk/Cargo.toml b/rust/tw_cosmos_sdk/Cargo.toml new file mode 100644 index 00000000000..c2b1be4fd94 --- /dev/null +++ b/rust/tw_cosmos_sdk/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "tw_cosmos_sdk" +version = "0.1.0" +edition = "2021" + +[features] +test-utils = [] + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +tw_bech32_address = { path = "../tw_bech32_address" } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } +tw_number = { path = "../tw_number", features = ["serde"] } +tw_proto = { path = "../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_cosmos_sdk = { path = "./", features = ["test-utils"] } +tw_misc = { path = "../tw_misc", features = ["test-utils"] } + +[build-dependencies] +pb-rs = "0.10.0" diff --git a/rust/tw_cosmos_sdk/build.rs b/rust/tw_cosmos_sdk/build.rs new file mode 100644 index 00000000000..a434de70fb2 --- /dev/null +++ b/rust/tw_cosmos_sdk/build.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use pb_rs::types::FileDescriptor; +use pb_rs::ConfigBuilder; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +fn main() { + let cargo_manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let proto_ext = Some(Path::new("proto").as_os_str()); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + + let proto_dir = cargo_manifest_dir + .join("..") + .join("..") + .join("src") + .join("Cosmos") + .join("Protobuf"); + let proto_dir_str = proto_dir.to_str().expect("Invalid proto directory path"); + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={}", proto_dir_str); + + let protos: Vec<_> = fs::read_dir(&proto_dir) + .expect("Expected a valid directory with proto files") + .filter_map(|file| { + let file = file.ok()?; + if file.path().extension() != proto_ext { + return None; + } + + let path = file.path(); + let path_str = path.to_str().expect("Invalid Proto file name"); + println!("cargo:rerun-if-changed={}", path_str); + Some(path) + }) + .collect(); + + // Delete all old generated files before re-generating new ones + if out_dir.exists() { + fs::remove_dir_all(&out_dir).expect("Error removing out directory"); + } + fs::DirBuilder::new() + .create(&out_dir) + .expect("Error creating out directory"); + + // `tw_proto/common_proto` contains google.protobuf proto files that are used in Cosmos protocol. + let common_proto_dir = cargo_manifest_dir + .join("..") + .join("tw_proto") + .join("src") + .join("common") + .canonicalize() + .expect("Cannot find common proto directory"); + + let out_protos = ConfigBuilder::new( + &protos, + None, + Some(&out_dir), + &[common_proto_dir, proto_dir], + ) + .expect("Error configuring pb-rs builder") + .gen_info(true) + .dont_use_cow(true) + .build(); + FileDescriptor::run(&out_protos).expect("Error generating proto files"); +} diff --git a/rust/tw_cosmos_sdk/fuzz/.gitignore b/rust/tw_cosmos_sdk/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_cosmos_sdk/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_cosmos_sdk/fuzz/Cargo.toml b/rust/tw_cosmos_sdk/fuzz/Cargo.toml new file mode 100644 index 00000000000..519510a0a49 --- /dev/null +++ b/rust/tw_cosmos_sdk/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_cosmos_sdk-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_cosmos_sdk] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/tw_cosmos_sdk/fuzz/fuzz_targets/sign.rs b/rust/tw_cosmos_sdk/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..af6f1bca403 --- /dev/null +++ b/rust/tw_cosmos_sdk/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,16 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Cosmos::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let _ = TWSigner::::sign(&coin, input); +}); diff --git a/rust/tw_cosmos_sdk/src/address.rs b/rust/tw_cosmos_sdk/src/address.rs new file mode 100644 index 00000000000..4f141b2a1dd --- /dev/null +++ b/rust/tw_cosmos_sdk/src/address.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Serialize; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{AddressError, AddressResult}; + +pub type Address = tw_bech32_address::Bech32Address; +pub type Bech32Prefix = tw_bech32_address::bech32_prefix::Bech32Prefix; + +pub trait CosmosAddress: FromStr + Serialize + ToString { + fn from_str_with_coin(coin: &dyn CoinContext, addr: &str) -> AddressResult + where + Self: Sized; +} + +impl CosmosAddress for Address { + fn from_str_with_coin(coin: &dyn CoinContext, addr: &str) -> AddressResult + where + Self: Sized, + { + let prefix = None; + Address::from_str_with_coin_and_prefix(coin, addr.to_string(), prefix) + } +} diff --git a/rust/tw_cosmos_sdk/src/context.rs b/rust/tw_cosmos_sdk/src/context.rs new file mode 100644 index 00000000000..f7da7218f25 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/context.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::{Address, CosmosAddress}; +use crate::hasher::sha256_hasher::Sha256Hasher; +use crate::hasher::CosmosHasher; +use crate::private_key::secp256k1::Secp256PrivateKey; +use crate::private_key::CosmosPrivateKey; +use crate::public_key::secp256k1::Secp256PublicKey; +use crate::public_key::CosmosPublicKey; +use crate::signature::secp256k1::Secp256k1Signature; +use crate::signature::CosmosSignature; + +pub trait CosmosContext { + type Address: CosmosAddress; + type TxHasher: CosmosHasher; + type PrivateKey: CosmosPrivateKey; + type PublicKey: CosmosPublicKey; + type Signature: CosmosSignature; +} + +#[derive(Default)] +pub struct StandardCosmosContext; + +impl CosmosContext for StandardCosmosContext { + type Address = Address; + type TxHasher = Sha256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = Secp256PublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs new file mode 100644 index 00000000000..21ca81fb087 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs @@ -0,0 +1,21 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::hasher::CosmosHasher; +use tw_hash::sha3::keccak256; +use tw_memory::Data; + +pub struct Keccak256Hasher; + +impl CosmosHasher for Keccak256Hasher { + fn hash_sign_doc(sign_doc: &[u8]) -> Data { + keccak256(sign_doc) + } + + fn hash_json_tx(json: &str) -> Data { + keccak256(json.as_bytes()) + } +} diff --git a/src/Ethereum/ABI/ParamBase.cpp b/rust/tw_cosmos_sdk/src/hasher/mod.rs similarity index 50% rename from src/Ethereum/ABI/ParamBase.cpp rename to rust/tw_cosmos_sdk/src/hasher/mod.rs index 294bb09a452..7422d204961 100644 --- a/src/Ethereum/ABI/ParamBase.cpp +++ b/rust/tw_cosmos_sdk/src/hasher/mod.rs @@ -4,15 +4,13 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "ParamBase.h" +use tw_memory::Data; -namespace TW::Ethereum::ABI { +pub mod keccak256_hasher; +pub mod sha256_hasher; -// Default implementation for simple types: return encoded value (32 bytes) -Data ParamBase::hashStruct() const { - Data encoded; - encode(encoded); - return encoded; -} +pub trait CosmosHasher { + fn hash_sign_doc(sign_doc: &[u8]) -> Data; -} // namespace TW::Ethereum::ABI + fn hash_json_tx(json: &str) -> Data; +} diff --git a/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs b/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs new file mode 100644 index 00000000000..3b2310cdbec --- /dev/null +++ b/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs @@ -0,0 +1,21 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::hasher::CosmosHasher; +use tw_hash::sha2::sha256; +use tw_memory::Data; + +pub struct Sha256Hasher; + +impl CosmosHasher for Sha256Hasher { + fn hash_sign_doc(sign_doc: &[u8]) -> Data { + sha256(sign_doc) + } + + fn hash_json_tx(json: &str) -> Data { + sha256(json.as_bytes()) + } +} diff --git a/rust/tw_cosmos_sdk/src/lib.rs b/rust/tw_cosmos_sdk/src/lib.rs new file mode 100644 index 00000000000..52cba46ef65 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod context; +pub mod hasher; +pub mod modules; +pub mod private_key; +pub mod public_key; +pub mod signature; +pub mod transaction; + +#[cfg(feature = "test-utils")] +pub mod test_utils; + +#[allow(non_snake_case)] +#[rustfmt::skip] +pub mod proto { + use tw_proto::google; + + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); +} diff --git a/rust/tw_cosmos_sdk/src/modules/broadcast_msg.rs b/rust/tw_cosmos_sdk/src/modules/broadcast_msg.rs new file mode 100644 index 00000000000..3c2b636ea0a --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/broadcast_msg.rs @@ -0,0 +1,60 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use quick_protobuf::MessageWrite; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_encoding::base64::Base64Encoded; +use tw_proto::serialize; + +pub enum BroadcastMode { + Block, + Async, + Sync, +} + +#[derive(Serialize)] +#[serde(untagged)] +pub enum BroadcastMsg { + /// Binary representation of the transaction. + Raw { + mode: String, + tx_bytes: Base64Encoded, + }, + /// JSON encoded transaction. + Json { mode: String, tx: Json }, +} + +impl BroadcastMsg { + pub fn raw(mode: BroadcastMode, tx: &Tx) -> BroadcastMsg { + let mode = match mode { + BroadcastMode::Block => "BROADCAST_MODE_BLOCK", + BroadcastMode::Async => "BROADCAST_MODE_ASYNC", + BroadcastMode::Sync => "BROADCAST_MODE_SYNC", + } + .to_string(); + let tx_bytes = Base64Encoded(serialize(tx).expect("Error on serializing transaction")); + BroadcastMsg::Raw { mode, tx_bytes } + } + + pub fn json(mode: BroadcastMode, tx: Tx) -> SigningResult { + let mode = match mode { + BroadcastMode::Block => "block", + BroadcastMode::Async => "async", + BroadcastMode::Sync => "sync", + } + .to_string(); + let tx = + serde_json::to_value(tx).map_err(|_| SigningError(SigningErrorType::Error_internal))?; + Ok(BroadcastMsg::Json { mode, tx }) + } + + pub fn to_json_string(&self) -> String { + // It's safe to unwrap here because `BroadcastMsg` consists of checked fields only. + serde_json::to_string(self).expect("Unexpected error on serializing a BroadcastMsg") + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs new file mode 100644 index 00000000000..188e4c3d377 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::hasher::CosmosHasher; +use crate::modules::serializer::json_serializer::JsonSerializer; +use crate::public_key::JsonPublicKey; +use crate::transaction::UnsignedTransaction; +use std::marker::PhantomData; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_memory::Data; + +pub struct JsonTxPreimage { + pub encoded_tx: String, + pub tx_hash: Data, +} + +pub struct JsonPreimager { + _phantom: PhantomData, +} + +impl JsonPreimager +where + Context::PublicKey: JsonPublicKey, +{ + pub fn preimage_hash(unsigned: &UnsignedTransaction) -> SigningResult { + let tx_to_sign = JsonSerializer::build_unsigned_tx(unsigned)?; + let encoded_tx = serde_json::to_string(&tx_to_sign) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + let tx_hash = Context::TxHasher::hash_json_tx(&encoded_tx); + + Ok(JsonTxPreimage { + encoded_tx, + tx_hash, + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/mod.rs b/rust/tw_cosmos_sdk/src/modules/compiler/mod.rs new file mode 100644 index 00000000000..a5d5ef61389 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/mod.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod json_preimager; +pub mod protobuf_preimager; +pub mod tw_compiler; diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs new file mode 100644 index 00000000000..591d56a6aba --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs @@ -0,0 +1,49 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::hasher::CosmosHasher; +use crate::modules::serializer::protobuf_serializer::{ProtobufSerializer, SignDirectArgs}; +use crate::transaction::UnsignedTransaction; +use std::marker::PhantomData; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::serialize; + +pub struct ProtobufTxPreimage { + pub encoded_tx: Data, + pub tx_hash: Data, +} + +pub struct ProtobufPreimager { + _phantom: PhantomData, +} + +impl ProtobufPreimager { + pub fn preimage_hash( + unsigned: &UnsignedTransaction, + ) -> SigningResult { + let tx_to_sign = ProtobufSerializer::build_sign_doc(unsigned)?; + let encoded_tx = serialize(&tx_to_sign)?; + let tx_hash = Context::TxHasher::hash_sign_doc(&encoded_tx); + + Ok(ProtobufTxPreimage { + encoded_tx, + tx_hash, + }) + } + + pub fn preimage_hash_direct(args: &SignDirectArgs) -> SigningResult { + let tx_to_sign = ProtobufSerializer::::build_direct_sign_doc(args); + let encoded_tx = serialize(&tx_to_sign)?; + let tx_hash = Context::TxHasher::hash_sign_doc(&encoded_tx); + + Ok(ProtobufTxPreimage { + encoded_tx, + tx_hash, + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs new file mode 100644 index 00000000000..897276b8c90 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs @@ -0,0 +1,185 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::broadcast_msg::{BroadcastMode, BroadcastMsg}; +use crate::modules::compiler::json_preimager::JsonPreimager; +use crate::modules::compiler::protobuf_preimager::ProtobufPreimager; +use crate::modules::serializer::json_serializer::JsonSerializer; +use crate::modules::serializer::protobuf_serializer::ProtobufSerializer; +use crate::modules::tx_builder::TxBuilder; +use crate::public_key::CosmosPublicKey; +use crate::signature::CosmosSignature; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct TWTransactionCompiler { + _phantom: PhantomData, +} + +impl TWTransactionCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + match input.signing_mode { + Proto::SigningMode::JSON => Self::preimage_hashes_as_json(coin, input), + Proto::SigningMode::Protobuf => Self::preimage_hashes_as_protobuf(coin, input), + } + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + match input.signing_mode { + Proto::SigningMode::JSON => Self::compile_as_json(coin, input, signatures, public_keys), + Proto::SigningMode::Protobuf => { + Self::compile_as_protobuf(coin, input, signatures, public_keys) + }, + } + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + pub fn preimage_hashes_as_protobuf( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let preimage = match TxBuilder::::try_sign_direct_args(&input) { + // If there was a `SignDirect` message in the signing input, generate the tx preimage directly. + Ok(Some(sign_direct_args)) => { + ProtobufPreimager::::preimage_hash_direct(&sign_direct_args)? + }, + // Otherwise, generate the tx preimage by using `TxBuilder`. + _ => { + // Please note the [`Proto::SigningInput::public_key`] should be set already. + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + ProtobufPreimager::::preimage_hash(&unsigned_tx)? + }, + }; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage.encoded_tx), + data_hash: Cow::from(preimage.tx_hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + pub fn preimage_hashes_as_json( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + // Please note the [`Proto::SigningInput::public_key`] should be set already. + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let preimage = JsonPreimager::preimage_hash(&unsigned_tx)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage.encoded_tx.as_bytes().to_vec()), + data_hash: Cow::from(preimage.tx_hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + pub fn compile_as_protobuf( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = Context::Signature::from_bytes(&signature)?; + let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + let signed_tx_raw = match TxBuilder::::try_sign_direct_args(&input) { + // If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly. + Ok(Some(sign_direct_args)) => ProtobufSerializer::::build_direct_signed_tx( + &sign_direct_args, + signature.to_bytes(), + ), + // Otherwise, generate the `TxRaw` by using `TxBuilder`. + _ => { + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(signature.to_bytes()); + + ProtobufSerializer::build_signed_tx(&signed_tx)? + }, + }; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::raw(broadcast_mode, &signed_tx_raw).to_json_string(); + + let signature_json = + JsonSerializer::::serialize_signature(&public_key, signature.to_bytes()); + let signature_json = serde_json::to_string(&[signature_json]) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature.to_bytes()), + signature_json: Cow::from(signature_json), + serialized: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + pub fn compile_as_json( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = Context::Signature::from_bytes(&signature)?; + let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(signature.to_bytes()); + + let signed_tx_json = JsonSerializer::build_signed_tx(&signed_tx)?; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::json(broadcast_mode, &signed_tx_json)?.to_json_string(); + + let signature_json = serde_json::to_string(&signed_tx_json.signatures) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature.to_bytes()), + signature_json: Cow::from(signature_json), + json: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + fn broadcast_mode(input: Proto::BroadcastMode) -> BroadcastMode { + match input { + Proto::BroadcastMode::BLOCK => BroadcastMode::Block, + Proto::BroadcastMode::SYNC => BroadcastMode::Sync, + Proto::BroadcastMode::ASYNC => BroadcastMode::Async, + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/mod.rs b/rust/tw_cosmos_sdk/src/modules/mod.rs new file mode 100644 index 00000000000..2a667107a85 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod broadcast_msg; +pub mod compiler; +pub mod serializer; +pub mod signer; +pub mod tx_builder; diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/json_serializer.rs b/rust/tw_cosmos_sdk/src/modules/serializer/json_serializer.rs new file mode 100644 index 00000000000..073f7de68cb --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/serializer/json_serializer.rs @@ -0,0 +1,125 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::private_key::SignatureData; +use crate::public_key::{CosmosPublicKey, JsonPublicKey}; +use crate::transaction::{Coin, Fee, SignedTransaction, UnsignedTransaction}; +use serde::Serialize; +use serde_json::Value as Json; +use std::marker::PhantomData; +use tw_coin_entry::error::SigningResult; +use tw_encoding::base64::Base64Encoded; + +#[derive(Serialize)] +pub struct SignedTxJson { + pub fee: FeeJson, + pub memo: String, + pub msg: Vec>, + pub signatures: Vec, +} + +#[derive(Serialize)] +pub struct UnsignedTxJson { + pub account_number: String, + pub chain_id: String, + pub fee: FeeJson, + pub memo: String, + pub msgs: Vec>, + pub sequence: String, +} + +#[derive(Serialize)] +pub struct FeeJson { + pub amount: Vec, + pub gas: String, +} + +#[derive(Clone, Serialize)] +pub struct AnyMsg { + #[serde(rename = "type")] + pub msg_type: String, + pub value: Value, +} + +#[derive(Clone, Serialize)] +pub struct SignatureJson { + pub pub_key: AnyMsg, + pub signature: Base64Encoded, +} + +/// `JsonSerializer` serializes transaction to JSON in Cosmos specific way. +pub struct JsonSerializer { + _phantom: PhantomData, +} + +impl JsonSerializer +where + Context: CosmosContext, + Context::PublicKey: JsonPublicKey, +{ + pub fn build_signed_tx(signed: &SignedTransaction) -> SigningResult { + let msg = signed + .tx_body + .messages + .iter() + .map(|msg| msg.to_json()) + .collect::>()?; + let signature = + Self::serialize_signature(&signed.signer.public_key, signed.signature.clone()); + + Ok(SignedTxJson { + fee: Self::build_fee(&signed.fee), + memo: signed.tx_body.memo.clone(), + msg, + signatures: vec![signature], + }) + } + + pub fn build_unsigned_tx( + unsigned: &UnsignedTransaction, + ) -> SigningResult { + let msgs = unsigned + .tx_body + .messages + .iter() + .map(|msg| msg.to_json()) + .collect::>()?; + + Ok(UnsignedTxJson { + account_number: unsigned.account_number.to_string(), + chain_id: unsigned.chain_id.clone(), + fee: Self::build_fee(&unsigned.fee), + memo: unsigned.tx_body.memo.clone(), + msgs, + sequence: unsigned.signer.sequence.to_string(), + }) + } + + pub fn serialize_signature( + public_key: &Context::PublicKey, + signature: SignatureData, + ) -> SignatureJson { + SignatureJson { + pub_key: Self::serialize_public_key(public_key), + signature: Base64Encoded(signature), + } + } + + pub fn serialize_public_key(public_key: &Context::PublicKey) -> AnyMsg { + AnyMsg { + msg_type: public_key.public_key_type(), + value: Base64Encoded(public_key.to_bytes()), + } + } + + pub fn build_fee(fee: &Fee) -> FeeJson { + FeeJson { + gas: fee.gas_limit.to_string(), + amount: fee.amounts.clone(), + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/mod.rs b/rust/tw_cosmos_sdk/src/modules/serializer/mod.rs new file mode 100644 index 00000000000..7361a31450a --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/serializer/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod json_serializer; +pub mod protobuf_serializer; diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs new file mode 100644 index 00000000000..ee67300b747 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs @@ -0,0 +1,165 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::proto::cosmos::base::v1beta1 as base_proto; +use crate::proto::cosmos::signing::v1beta1 as signing_proto; +use crate::proto::cosmos::tx::v1beta1 as tx_proto; +use crate::public_key::ProtobufPublicKey; +use crate::transaction::{ + Coin, Fee, SignMode, SignedTransaction, SignerInfo, TxBody, UnsignedTransaction, +}; +use std::marker::PhantomData; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::serialize; + +pub fn build_coin(coin: &Coin) -> base_proto::Coin { + base_proto::Coin { + amount: coin.amount.to_string(), + denom: coin.denom.clone(), + } +} + +/// `ProtobufSerializer` serializes Cosmos specific Protobuf messages. +pub struct ProtobufSerializer { + _phantom: PhantomData, +} + +pub struct SignDirectArgs { + pub tx_body: Data, + pub auth_info: Data, + pub chain_id: String, + pub account_number: u64, +} + +impl ProtobufSerializer { + /// Serializes a signed transaction into the Cosmos [`tx_proto::TxRaw`] message. + /// [`tx_proto::TxRaw`] can be broadcasted to the network. + pub fn build_signed_tx(signed: &SignedTransaction) -> SigningResult { + let tx_body = Self::build_tx_body(&signed.tx_body)?; + let body_bytes = serialize(&tx_body).expect("Unexpected error on tx_body serialization"); + + let auth_info = Self::build_auth_info(&signed.signer, &signed.fee); + let auth_info_bytes = + serialize(&auth_info).expect("Unexpected error on auth_info serialization"); + + Ok(tx_proto::TxRaw { + body_bytes, + auth_info_bytes, + signatures: vec![signed.signature.clone()], + }) + } + + pub fn build_direct_signed_tx(args: &SignDirectArgs, signature: Data) -> tx_proto::TxRaw { + tx_proto::TxRaw { + body_bytes: args.tx_body.clone(), + auth_info_bytes: args.auth_info.clone(), + signatures: vec![signature], + } + } + + /// Serializes an unsigned transaction into the Cosmos [`tx_proto::SignDoc`] message. + /// [`tx_proto::SignDoc`] is used to generate a transaction prehash and sign it. + pub fn build_sign_doc( + unsigned: &UnsignedTransaction, + ) -> SigningResult { + let tx_body = Self::build_tx_body(&unsigned.tx_body)?; + let body_bytes = serialize(&tx_body).expect("Unexpected error on tx_body serialization"); + + let auth_info = Self::build_auth_info(&unsigned.signer, &unsigned.fee); + let auth_info_bytes = + serialize(&auth_info).expect("Unexpected error on auth_info serialization"); + + Ok(tx_proto::SignDoc { + body_bytes, + auth_info_bytes, + chain_id: unsigned.chain_id.clone(), + account_number: unsigned.account_number, + }) + } + + pub fn build_direct_sign_doc(args: &SignDirectArgs) -> tx_proto::SignDoc { + tx_proto::SignDoc { + body_bytes: args.tx_body.clone(), + auth_info_bytes: args.auth_info.clone(), + chain_id: args.chain_id.clone(), + account_number: args.account_number, + } + } + + pub fn build_auth_info( + signer: &SignerInfo, + fee: &Fee, + ) -> tx_proto::AuthInfo { + tx_proto::AuthInfo { + signer_infos: vec![Self::build_signer_info(signer)], + fee: Some(Self::build_fee(fee)), + // At this moment, we do not support transaction tip. + tip: None, + } + } + + pub fn build_tx_body(tx_body: &TxBody) -> SigningResult { + let messages: Vec<_> = tx_body + .messages + .iter() + .map(|msg| msg.to_proto()) + .collect::>()?; + + Ok(tx_proto::TxBody { + messages, + memo: tx_body.memo.clone(), + timeout_height: tx_body.timeout_height, + extension_options: Vec::default(), + non_critical_extension_options: Vec::default(), + }) + } + + pub fn build_signer_info(signer: &SignerInfo) -> tx_proto::SignerInfo { + use tx_proto::mod_ModeInfo::{self as mode_info, OneOfsum as SumEnum}; + + // Single is the mode info for a single signer. It is structured as a message + // to allow for additional fields such as locale for SIGN_MODE_TEXTUAL in the future. + let mode_info = tx_proto::ModeInfo { + sum: SumEnum::single(mode_info::Single { + mode: Self::build_sign_mode(signer.sign_mode), + }), + }; + + tx_proto::SignerInfo { + public_key: Some(signer.public_key.to_proto()), + mode_info: Some(mode_info), + sequence: signer.sequence, + } + } + + fn build_fee(fee: &Fee) -> tx_proto::Fee { + let payer = fee + .payer + .as_ref() + .map(Context::Address::to_string) + .unwrap_or_default(); + let granter = fee + .granter + .as_ref() + .map(Context::Address::to_string) + .unwrap_or_default(); + + tx_proto::Fee { + amount: fee.amounts.iter().map(build_coin).collect(), + gas_limit: fee.gas_limit, + payer, + granter, + } + } + + fn build_sign_mode(sign_mode: SignMode) -> signing_proto::SignMode { + match sign_mode { + SignMode::Direct => signing_proto::SignMode::SIGN_MODE_DIRECT, + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/signer/mod.rs b/rust/tw_cosmos_sdk/src/modules/signer/mod.rs new file mode 100644 index 00000000000..c4071fb11ff --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/signer/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod tw_signer; diff --git a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs new file mode 100644 index 00000000000..8f63310be5a --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs @@ -0,0 +1,61 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::compiler::tw_compiler::TWTransactionCompiler; +use crate::private_key::CosmosPrivateKey; +use crate::public_key::CosmosPublicKey; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Cosmos::Proto; + +pub struct TWSigner { + _phantom: PhantomData, +} + +impl TWSigner { + #[inline] + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let private_key = Context::PrivateKey::try_from(&input.private_key)?; + let public_key = Context::PublicKey::from_private_key(coin, private_key.as_ref())?; + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + + let preimage_output = + TWTransactionCompiler::::preimage_hashes(coin, input.clone()); + if preimage_output.error != SigningErrorType::OK { + return Err(SigningError(preimage_output.error)); + } + + let signature_data = private_key.sign_tx_hash(&preimage_output.data_hash)?; + let compile_output = TWTransactionCompiler::::compile( + coin, + input, + vec![signature_data], + vec![public_key.to_bytes()], + ); + + if compile_output.error != SigningErrorType::OK { + return Err(SigningError(preimage_output.error)); + } + + Ok(compile_output) + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs new file mode 100644 index 00000000000..da5e87a94d1 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -0,0 +1,655 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::{Address, CosmosAddress}; +use crate::context::CosmosContext; +use crate::modules::serializer::protobuf_serializer::SignDirectArgs; +use crate::public_key::CosmosPublicKey; +use crate::transaction::message::cosmos_generic_message::JsonRawMessage; +use crate::transaction::message::{CosmosMessage, CosmosMessageBox}; +use crate::transaction::{Coin, Fee, SignMode, SignerInfo, TxBody, UnsignedTransaction}; +use std::marker::PhantomData; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_misc::traits::ToBytesVec; +use tw_number::U256; +use tw_proto::Cosmos::Proto; +use tw_proto::{google, serialize}; + +const DEFAULT_TIMEOUT_HEIGHT: u64 = 0; + +pub struct TxBuilder { + _phantom: PhantomData, +} + +impl TxBuilder +where + Context: CosmosContext, +{ + /// Please note that [`Proto::SigningInput::public_key`] must be set. + /// If the public key should be derived from a private key, please do it before this method is called. + pub fn unsigned_tx_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult> { + let fee = input + .fee + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_wrong_fee))?; + let signer = Self::signer_info_from_proto(coin, input)?; + + Ok(UnsignedTransaction { + signer, + fee: Self::fee_from_proto(fee)?, + chain_id: input.chain_id.to_string(), + account_number: input.account_number, + tx_body: Self::tx_body_from_proto(coin, input)?, + }) + } + + pub fn signer_info_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + ) -> SigningResult> { + let public_key = Context::PublicKey::from_bytes(coin, &input.public_key)?; + Ok(SignerInfo { + public_key, + sequence: input.sequence, + // At this moment, we support the Direct signing mode only. + sign_mode: SignMode::Direct, + }) + } + + fn fee_from_proto(input: &Proto::Fee) -> SigningResult> { + let amounts = input + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + Ok(Fee { + amounts, + gas_limit: input.gas, + payer: None, + granter: None, + }) + } + + fn coin_from_proto(input: &Proto::Amount<'_>) -> SigningResult { + let amount = U256::from_str(&input.amount)?; + Ok(Coin { + amount, + denom: input.denom.to_string(), + }) + } + + fn tx_body_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + if input.messages.is_empty() { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + } + + let messages = input + .messages + .iter() + .map(|msg| Self::tx_message(coin, msg)) + .collect::>()?; + + Ok(TxBody { + messages, + memo: input.memo.to_string(), + timeout_height: DEFAULT_TIMEOUT_HEIGHT, + }) + } + + pub fn try_sign_direct_args( + input: &Proto::SigningInput<'_>, + ) -> SigningResult> { + use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + let Some(msg) = input.messages.first() else { + return Ok(None); + }; + + match msg.message_oneof { + MessageEnum::sign_direct_message(ref direct) => Ok(Some(SignDirectArgs { + tx_body: direct.body_bytes.to_vec(), + auth_info: direct.auth_info_bytes.to_vec(), + chain_id: input.chain_id.to_string(), + account_number: input.account_number, + })), + _ => Ok(None), + } + } + + pub fn tx_message( + coin: &dyn CoinContext, + input: &Proto::Message, + ) -> SigningResult { + use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + match input.message_oneof { + MessageEnum::send_coins_message(ref send) => Self::send_msg_from_proto(coin, send), + MessageEnum::transfer_tokens_message(ref transfer) => { + Self::transfer_tokens_msg_from_proto(coin, transfer) + }, + MessageEnum::stake_message(ref delegate) => { + Self::delegate_msg_from_proto(coin, delegate) + }, + MessageEnum::unstake_message(ref undelegate) => { + Self::undelegate_msg_from_proto(coin, undelegate) + }, + MessageEnum::withdraw_stake_reward_message(ref withdraw) => { + Self::withdraw_reward_msg_from_proto(coin, withdraw) + }, + MessageEnum::set_withdraw_address_message(ref set) => { + Self::set_withdraw_address_msg_from_proto(coin, set) + }, + MessageEnum::restake_message(ref redelegate) => { + Self::redelegate_msg_from_proto(coin, redelegate) + }, + MessageEnum::raw_json_message(ref raw_json) => { + Self::wasm_raw_msg_from_proto(coin, raw_json) + }, + MessageEnum::wasm_terra_execute_contract_transfer_message(ref transfer) => { + Self::wasm_terra_execute_contract_transfer_msg_from_proto(coin, transfer) + }, + MessageEnum::wasm_terra_execute_contract_send_message(ref send) => { + Self::wasm_terra_execute_contract_send_msg_from_proto(coin, send) + }, + MessageEnum::thorchain_send_message(ref send) => { + Self::thorchain_send_msg_from_proto(coin, send) + }, + MessageEnum::wasm_terra_execute_contract_generic(ref generic) => { + Self::wasm_terra_execute_contract_generic_msg_from_proto(coin, generic) + }, + MessageEnum::wasm_execute_contract_transfer_message(ref transfer) => { + Self::wasm_execute_contract_transfer_msg_from_proto(coin, transfer) + }, + MessageEnum::wasm_execute_contract_send_message(ref send) => { + Self::wasm_execute_contract_send_msg_from_proto(coin, send) + }, + MessageEnum::wasm_execute_contract_generic(ref generic) => { + Self::wasm_execute_contract_generic_msg_from_proto(coin, generic) + }, + MessageEnum::sign_direct_message(ref _sign) => { + // `SignDirect` message must be handled before this function is called. + // Consider using `Self::try_sign_direct_args` instead. + Err(SigningError(SigningErrorType::Error_not_supported)) + }, + MessageEnum::auth_grant(ref grant) => Self::auth_grant_msg_from_proto(coin, grant), + MessageEnum::auth_revoke(ref revoke) => Self::auth_revoke_msg_from_proto(coin, revoke), + MessageEnum::msg_vote(ref vote) => Self::vote_msg_from_proto(coin, vote), + MessageEnum::msg_stride_liquid_staking_stake(ref stake) => { + Self::stride_stake_msg_from_proto(coin, stake) + }, + MessageEnum::msg_stride_liquid_staking_redeem(ref redeem) => { + Self::stride_redeem_msg_from_proto(coin, redeem) + }, + MessageEnum::thorchain_deposit_message(ref deposit) => { + Self::thorchain_deposit_msg_from_proto(coin, deposit) + }, + MessageEnum::None => Err(SigningError(SigningErrorType::Error_invalid_params)), + } + } + + pub fn send_msg_from_proto( + coin: &dyn CoinContext, + send: &Proto::mod_Message::Send<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_bank_message::SendMessage; + + let amounts = send + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + let msg = SendMessage { + custom_type_prefix: Self::custom_msg_type(&send.type_prefix), + from_address: Address::from_str_with_coin(coin, &send.from_address)?, + to_address: Address::from_str_with_coin(coin, &send.to_address)?, + amount: amounts, + }; + Ok(msg.into_boxed()) + } + + pub fn transfer_tokens_msg_from_proto( + coin: &dyn CoinContext, + transfer: &Proto::mod_Message::Transfer<'_>, + ) -> SigningResult { + use crate::transaction::message::ibc_message::{Height, TransferTokensMessage}; + + let token = transfer + .token + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let token = Self::coin_from_proto(token)?; + let height = transfer + .timeout_height + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + let msg = TransferTokensMessage { + source_port: transfer.source_port.to_string(), + source_channel: transfer.source_channel.to_string(), + token, + sender: Address::from_str_with_coin(coin, &transfer.sender)?, + // Don't use `Address::from_str_with_coin` as the recipient address can belong to another Cosmos chain. + receiver: Address::from_str(&transfer.receiver)?, + timeout_height: Height { + revision_number: height.revision_number, + revision_height: height.revision_height, + }, + timeout_timestamp: transfer.timeout_timestamp, + }; + Ok(msg.into_boxed()) + } + + pub fn delegate_msg_from_proto( + coin: &dyn CoinContext, + delegate: &Proto::mod_Message::Delegate<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::DelegateMessage; + + let amount = delegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let amount = Self::coin_from_proto(amount)?; + let msg = DelegateMessage { + custom_type_prefix: Self::custom_msg_type(&delegate.type_prefix), + amount, + delegator_address: Address::from_str_with_coin(coin, &delegate.delegator_address)?, + validator_address: Address::from_str_with_coin(coin, &delegate.validator_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn undelegate_msg_from_proto( + coin: &dyn CoinContext, + undelegate: &Proto::mod_Message::Undelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::UndelegateMessage; + + let amount = undelegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let amount = Self::coin_from_proto(amount)?; + + let msg = UndelegateMessage { + custom_type_prefix: Self::custom_msg_type(&undelegate.type_prefix), + amount, + delegator_address: Address::from_str_with_coin(coin, &undelegate.delegator_address)?, + validator_address: Address::from_str_with_coin(coin, &undelegate.validator_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn withdraw_reward_msg_from_proto( + coin: &dyn CoinContext, + withdraw: &Proto::mod_Message::WithdrawDelegationReward<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::WithdrawDelegationRewardMessage; + + let msg = WithdrawDelegationRewardMessage { + custom_type_prefix: Self::custom_msg_type(&withdraw.type_prefix), + delegator_address: Address::from_str_with_coin(coin, &withdraw.delegator_address)?, + validator_address: Address::from_str_with_coin(coin, &withdraw.validator_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn set_withdraw_address_msg_from_proto( + coin: &dyn CoinContext, + set: &Proto::mod_Message::SetWithdrawAddress<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::SetWithdrawAddressMessage; + + let msg = SetWithdrawAddressMessage { + custom_type_prefix: Self::custom_msg_type(&set.type_prefix), + delegator_address: Address::from_str_with_coin(coin, &set.delegator_address)?, + withdraw_address: Address::from_str_with_coin(coin, &set.withdraw_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn redelegate_msg_from_proto( + coin: &dyn CoinContext, + redelegate: &Proto::mod_Message::BeginRedelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::BeginRedelegateMessage; + + let amount = redelegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let amount = Self::coin_from_proto(amount)?; + let validator_src_address = + Address::from_str_with_coin(coin, &redelegate.validator_src_address)?; + let validator_dst_address = + Address::from_str_with_coin(coin, &redelegate.validator_dst_address)?; + + let msg = BeginRedelegateMessage { + custom_type_prefix: Self::custom_msg_type(&redelegate.type_prefix), + amount, + delegator_address: Address::from_str_with_coin(coin, &redelegate.delegator_address)?, + validator_src_address, + validator_dst_address, + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_raw_msg_from_proto( + _coin: &dyn CoinContext, + raw: &Proto::mod_Message::RawJSON<'_>, + ) -> SigningResult { + let value = serde_json::from_str(&raw.value) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + let msg = JsonRawMessage { + msg_type: raw.type_pb.to_string(), + value, + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_terra_execute_contract_transfer_msg_from_proto( + coin: &dyn CoinContext, + transfer: &Proto::mod_Message::WasmTerraExecuteContractTransfer<'_>, + ) -> SigningResult { + use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; + use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecutePayload}; + + let execute_payload = WasmExecutePayload::Transfer { + amount: U256::from_big_endian_slice(&transfer.amount)?, + recipient: transfer.recipient_address.to_string(), + }; + + let msg = TerraExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &transfer.sender_address)?, + contract: Address::from_str_with_coin(coin, &transfer.contract_address)?, + execute_msg: ExecuteMsg::json(execute_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_terra_execute_contract_send_msg_from_proto( + coin: &dyn CoinContext, + send: &Proto::mod_Message::WasmTerraExecuteContractSend<'_>, + ) -> SigningResult { + use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; + use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecutePayload}; + + let execute_payload = WasmExecutePayload::Send { + amount: U256::from_big_endian_slice(&send.amount)?, + contract: send.recipient_contract_address.to_string(), + msg: send.msg.to_string(), + }; + + let msg = TerraExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &send.sender_address)?, + contract: Address::from_str_with_coin(coin, &send.contract_address)?, + execute_msg: ExecuteMsg::json(execute_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_terra_execute_contract_generic_msg_from_proto( + coin: &dyn CoinContext, + generic: &Proto::mod_Message::WasmTerraExecuteContractGeneric<'_>, + ) -> SigningResult { + use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; + use crate::transaction::message::wasm_message::ExecuteMsg; + + let coins = generic + .coins + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let msg = TerraExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &generic.sender_address)?, + contract: Address::from_str_with_coin(coin, &generic.contract_address)?, + execute_msg: ExecuteMsg::String(generic.execute_msg.to_string()), + coins, + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_execute_contract_transfer_msg_from_proto( + coin: &dyn CoinContext, + transfer: &Proto::mod_Message::WasmExecuteContractTransfer<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message::{ + ExecuteMsg, WasmExecuteContractMessage, WasmExecutePayload, + }; + + let transfer_payload = WasmExecutePayload::Transfer { + amount: U256::from_big_endian_slice(&transfer.amount)?, + recipient: transfer.recipient_address.to_string(), + }; + + let msg = WasmExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &transfer.sender_address)?, + contract: Address::from_str_with_coin(coin, &transfer.contract_address)?, + msg: ExecuteMsg::json(transfer_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_execute_contract_send_msg_from_proto( + coin: &dyn CoinContext, + send: &Proto::mod_Message::WasmExecuteContractSend<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message::{ + ExecuteMsg, WasmExecuteContractMessage, WasmExecutePayload, + }; + + let execute_payload = WasmExecutePayload::Send { + amount: U256::from_big_endian_slice(&send.amount)?, + contract: send.recipient_contract_address.to_string(), + msg: send.msg.to_string(), + }; + + let msg = WasmExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &send.sender_address)?, + contract: Address::from_str_with_coin(coin, &send.contract_address)?, + msg: ExecuteMsg::json(execute_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_execute_contract_generic_msg_from_proto( + coin: &dyn CoinContext, + generic: &Proto::mod_Message::WasmExecuteContractGeneric<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecuteContractMessage}; + + let coins = generic + .coins + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let msg = WasmExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &generic.sender_address)?, + contract: Address::from_str_with_coin(coin, &generic.contract_address)?, + msg: ExecuteMsg::String(generic.execute_msg.to_string()), + coins, + }; + Ok(msg.into_boxed()) + } + + pub fn thorchain_send_msg_from_proto( + _coin: &dyn CoinContext, + send: &Proto::mod_Message::THORChainSend<'_>, + ) -> SigningResult { + use crate::transaction::message::thorchain_message::ThorchainSendMessage; + + let amount = send + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let msg = ThorchainSendMessage { + from_address: send.from_address.to_vec(), + to_address: send.to_address.to_vec(), + amount, + }; + Ok(msg.into_boxed()) + } + + pub fn auth_grant_msg_from_proto( + coin: &dyn CoinContext, + auth: &Proto::mod_Message::AuthGrant<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_auth_message::AuthGrantMessage; + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + + const STAKE_AUTHORIZATION_MSG_TYPE: &str = "/cosmos.staking.v1beta1.StakeAuthorization"; + + let grant_msg = match auth.grant_type { + ProtoGrantType::grant_stake(ref stake) => google::protobuf::Any { + type_url: STAKE_AUTHORIZATION_MSG_TYPE.to_string(), + value: serialize(stake) + .map_err(|_| SigningError(SigningErrorType::Error_invalid_params))?, + }, + ProtoGrantType::None => { + return Err(SigningError(SigningErrorType::Error_invalid_params)) + }, + }; + + let msg = AuthGrantMessage { + granter: Address::from_str_with_coin(coin, &auth.granter)?, + grantee: Address::from_str_with_coin(coin, &auth.grantee)?, + grant_msg, + expiration_secs: auth.expiration, + }; + Ok(msg.into_boxed()) + } + + pub fn auth_revoke_msg_from_proto( + coin: &dyn CoinContext, + auth: &Proto::mod_Message::AuthRevoke<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_auth_message::AuthRevokeMessage; + + let msg = AuthRevokeMessage { + granter: Address::from_str_with_coin(coin, &auth.granter)?, + grantee: Address::from_str_with_coin(coin, &auth.grantee)?, + msg_type_url: auth.msg_type_url.to_string(), + }; + Ok(msg.into_boxed()) + } + + pub fn vote_msg_from_proto( + coin: &dyn CoinContext, + vote: &Proto::mod_Message::MsgVote<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_gov_message::{VoteMessage, VoteOption}; + use Proto::mod_Message::VoteOption as ProtoVoteOption; + + let option = match vote.option { + ProtoVoteOption::_UNSPECIFIED => VoteOption::Unspecified, + ProtoVoteOption::YES => VoteOption::Yes, + ProtoVoteOption::ABSTAIN => VoteOption::Abstain, + ProtoVoteOption::NO => VoteOption::No, + ProtoVoteOption::NO_WITH_VETO => VoteOption::NoWithVeto, + }; + + let msg = VoteMessage { + proposal_id: vote.proposal_id, + voter: Address::from_str_with_coin(coin, &vote.voter)?, + option, + }; + Ok(msg.into_boxed()) + } + + pub fn stride_stake_msg_from_proto( + coin: &dyn CoinContext, + stake: &Proto::mod_Message::MsgStrideLiquidStakingStake<'_>, + ) -> SigningResult { + use crate::transaction::message::stride_message::StrideLiquidStakeMessage; + + let msg = StrideLiquidStakeMessage { + creator: Address::from_str_with_coin(coin, &stake.creator)?, + amount: U256::from_str(&stake.amount)?, + host_denom: stake.host_denom.to_string(), + }; + Ok(msg.into_boxed()) + } + + pub fn stride_redeem_msg_from_proto( + _coin: &dyn CoinContext, + redeem: &Proto::mod_Message::MsgStrideLiquidStakingRedeem<'_>, + ) -> SigningResult { + use crate::transaction::message::stride_message::StrideLiquidRedeemMessage; + + let msg = StrideLiquidRedeemMessage { + creator: redeem.creator.to_string(), + amount: U256::from_str(&redeem.amount)?, + receiver: redeem.receiver.to_string(), + host_zone: redeem.host_zone.to_string(), + }; + Ok(msg.into_boxed()) + } + + pub fn thorchain_deposit_msg_from_proto( + _coin: &dyn CoinContext, + deposit: &Proto::mod_Message::THORChainDeposit<'_>, + ) -> SigningResult { + use crate::transaction::message::thorchain_message::{ + ThorchainAsset, ThorchainCoin, ThorchainDepositMessage, + }; + + let mut coins = Vec::with_capacity(deposit.coins.len()); + for coin_proto in deposit.coins.iter() { + let asset_proto = coin_proto + .asset + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + let asset = ThorchainAsset { + chain: asset_proto.chain.to_string(), + symbol: asset_proto.symbol.to_string(), + ticker: asset_proto.ticker.to_string(), + synth: asset_proto.synth, + }; + coins.push(ThorchainCoin { + asset, + amount: U256::from_str(&coin_proto.amount)?, + decimals: coin_proto.decimals, + }); + } + + let msg = ThorchainDepositMessage { + coins, + memo: deposit.memo.to_string(), + signer: deposit.signer.to_vec(), + }; + Ok(msg.into_boxed()) + } + + fn custom_msg_type(type_prefix: &str) -> Option { + if type_prefix.is_empty() { + None + } else { + Some(type_prefix.to_string()) + } + } +} diff --git a/rust/tw_cosmos_sdk/src/private_key/mod.rs b/rust/tw_cosmos_sdk/src/private_key/mod.rs new file mode 100644 index 00000000000..30045cc6549 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/private_key/mod.rs @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::SigningResult; +use tw_keypair::{tw, KeyPairError}; +use tw_memory::Data; +use tw_misc::traits::FromSlice; + +pub mod secp256k1; + +pub type SignatureData = Data; + +pub trait CosmosPrivateKey: AsRef + FromSlice { + fn sign_tx_hash(&self, hash: &[u8]) -> SigningResult; +} diff --git a/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs new file mode 100644 index 00000000000..921409f4cd4 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::private_key::CosmosPrivateKey; +use tw_coin_entry::error::{SigningError, SigningResult}; +use tw_keypair::tw; +use tw_keypair::tw::Curve; +use tw_keypair::KeyPairError; +use tw_memory::Data; + +pub struct Secp256PrivateKey(tw::PrivateKey); + +impl AsRef for Secp256PrivateKey { + fn as_ref(&self) -> &tw::PrivateKey { + &self.0 + } +} + +impl<'a> TryFrom<&'a [u8]> for Secp256PrivateKey { + type Error = KeyPairError; + + fn try_from(value: &'a [u8]) -> Result { + tw::PrivateKey::new(value.to_vec()).map(Secp256PrivateKey) + } +} + +impl CosmosPrivateKey for Secp256PrivateKey { + fn sign_tx_hash(&self, hash: &[u8]) -> SigningResult { + self.0 + .sign(hash, Curve::Secp256k1) + .map_err(SigningError::from) + } +} diff --git a/rust/tw_cosmos_sdk/src/public_key/mod.rs b/rust/tw_cosmos_sdk/src/public_key/mod.rs new file mode 100644 index 00000000000..d3a4e20f5e8 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/public_key/mod.rs @@ -0,0 +1,32 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_keypair::{tw, KeyPairResult}; +use tw_memory::Data; +use tw_proto::google; + +pub mod secp256k1; + +pub trait CosmosPublicKey: JsonPublicKey + ProtobufPublicKey + Sized { + fn from_private_key( + coin: &dyn CoinContext, + private_key: &tw::PrivateKey, + ) -> KeyPairResult; + + fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult; + + fn to_bytes(&self) -> Data; +} + +pub trait ProtobufPublicKey { + fn to_proto(&self) -> google::protobuf::Any; +} + +pub trait JsonPublicKey { + /// In most cases, [`JsonPublicKey::public_key_type`] returns a corresponding protobuf message type. + fn public_key_type(&self) -> String; +} diff --git a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs new file mode 100644 index 00000000000..3558aba8626 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::proto::cosmos; +use crate::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_coin_entry::coin_context::CoinContext; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::tw::{self, PublicKeyType}; +use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; +use tw_proto::{google, to_any}; + +pub struct Secp256PublicKey { + public_key: Data, +} + +impl CosmosPublicKey for Secp256PublicKey { + fn from_private_key(coin: &dyn CoinContext, private_key: &tw::PrivateKey) -> KeyPairResult + where + Self: Sized, + { + let public_key = private_key.get_public_key_by_type(coin.public_key_type())?; + Ok(Secp256PublicKey { + public_key: public_key.to_bytes(), + }) + } + + fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; + Ok(Secp256PublicKey { public_key }) + } + + fn to_bytes(&self) -> Data { + self.public_key.clone() + } +} + +impl ProtobufPublicKey for Secp256PublicKey { + fn to_proto(&self) -> google::protobuf::Any { + let proto = cosmos::crypto::secp256k1::PubKey { + key: self.public_key.clone(), + }; + to_any(&proto) + } +} + +impl JsonPublicKey for Secp256PublicKey { + fn public_key_type(&self) -> String { + "tendermint/PubKeySecp256k1".to_string() + } +} + +pub fn prepare_secp256k1_public_key( + coin: &dyn CoinContext, + public_key_bytes: &[u8], +) -> KeyPairResult { + let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; + match coin.public_key_type() { + PublicKeyType::Secp256k1 => Ok(public_key.compressed().to_vec()), + PublicKeyType::Secp256k1Extended => Ok(public_key.uncompressed().to_vec()), + _ => Err(KeyPairError::InvalidPublicKey), + } +} diff --git a/rust/tw_cosmos_sdk/src/signature/mod.rs b/rust/tw_cosmos_sdk/src/signature/mod.rs new file mode 100644 index 00000000000..d2c5ba2169e --- /dev/null +++ b/rust/tw_cosmos_sdk/src/signature/mod.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_keypair::KeyPairResult; +use tw_memory::Data; + +pub mod secp256k1; + +pub trait CosmosSignature: Sized { + fn from_bytes(signature_bytes: &[u8]) -> KeyPairResult; + + fn to_bytes(&self) -> Data; +} diff --git a/rust/tw_cosmos_sdk/src/signature/secp256k1.rs b/rust/tw_cosmos_sdk/src/signature/secp256k1.rs new file mode 100644 index 00000000000..f07ec5957ff --- /dev/null +++ b/rust/tw_cosmos_sdk/src/signature/secp256k1.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signature::CosmosSignature; +use tw_hash::{H512, H520}; +use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; + +pub struct Secp256k1Signature { + signature: H512, +} + +impl CosmosSignature for Secp256k1Signature { + fn from_bytes(signature_bytes: &[u8]) -> KeyPairResult { + let signature_slice = if signature_bytes.len() == H520::len() { + // Discard the last `v` recovery byte. + &signature_bytes[0..H512::len()] + } else { + signature_bytes + }; + + let signature = + H512::try_from(signature_slice).map_err(|_| KeyPairError::InvalidSignature)?; + Ok(Secp256k1Signature { signature }) + } + + fn to_bytes(&self) -> Data { + self.signature.to_vec() + } +} diff --git a/rust/tw_cosmos_sdk/src/test_utils/mod.rs b/rust/tw_cosmos_sdk/src/test_utils/mod.rs new file mode 100644 index 00000000000..334b4aa821d --- /dev/null +++ b/rust/tw_cosmos_sdk/src/test_utils/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod proto_utils; +pub mod sign_utils; diff --git a/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs new file mode 100644 index 00000000000..5bef94cc9eb --- /dev/null +++ b/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs @@ -0,0 +1,33 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +pub fn make_fee(gas: u64, amount: Proto::Amount<'_>) -> Proto::Fee<'_> { + Proto::Fee { + amounts: vec![amount], + gas, + } +} + +pub fn make_fee_none(gas: u64) -> Proto::Fee<'static> { + Proto::Fee { + amounts: Vec::default(), + gas, + } +} + +pub fn make_message(message_oneof: MessageEnum) -> Proto::Message { + Proto::Message { message_oneof } +} + +pub fn make_amount<'a>(denom: &'a str, amount: &'a str) -> Proto::Amount<'a> { + Proto::Amount { + denom: denom.into(), + amount: amount.into(), + } +} diff --git a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs new file mode 100644 index 00000000000..3e439ad4dd1 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs @@ -0,0 +1,167 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::compiler::tw_compiler::TWTransactionCompiler; +use crate::modules::signer::tw_signer::TWSigner; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; + +#[derive(Clone)] +pub struct TestInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + /// Stringified JSON object. + pub tx: &'a str, + /// Signature hex-encoded. + pub signature: &'a str, + /// Stringified signature JSON object. + pub signature_json: &'a str, +} + +#[derive(Clone)] +pub struct TestCompileInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + /// Either a stringified JSON object or a hex-encoded serialzied `SignDoc`. + pub tx_preimage: &'a str, + /// Expected transaction preimage hash. + pub tx_prehash: &'a str, + /// Stringified JSON object. + pub tx: &'a str, + /// Signature hex-encoded. + pub signature: &'a str, + /// Stringified signature JSON object. + pub signature_json: &'a str, +} + +#[derive(Clone)] +pub struct TestErrorInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + pub error: SigningErrorType, +} + +#[track_caller] +pub fn test_compile_protobuf(mut test_input: TestCompileInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + test_compile_impl::(test_input); +} + +#[track_caller] +pub fn test_compile_json(mut test_input: TestCompileInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + test_compile_impl::(test_input); +} + +#[track_caller] +pub fn test_sign_protobuf(mut test_input: TestInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + test_sign_impl::(test_input); +} + +#[track_caller] +pub fn test_sign_protobuf_error(mut test_input: TestErrorInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + let output = TWSigner::::sign(test_input.coin, test_input.input); + assert_eq!(output.error, test_input.error); +} + +#[track_caller] +pub fn test_sign_json(mut test_input: TestInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + test_sign_impl::(test_input); +} + +#[track_caller] +pub fn test_sign_json_error(mut test_input: TestErrorInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + let output = TWSigner::::sign(test_input.coin, test_input.input); + assert_eq!(output.error, test_input.error); +} + +#[track_caller] +fn test_sign_impl(test_input: TestInput<'_>) { + let output = TWSigner::::sign(test_input.coin, test_input.input); + assert_eq!(output.error, SigningError::OK); + assert!(output.error_message.is_empty()); + + let result = if output.serialized.is_empty() { + output.json + } else { + output.serialized + }; + assert_eq!(result, test_input.tx, "Unexpected result transaction"); + + assert_eq!( + output.signature.to_hex(), + test_input.signature, + "Unexpected signature" + ); + assert_eq!( + output.signature_json, test_input.signature_json, + "Unexpected signature JSON" + ); +} + +#[track_caller] +fn test_compile_impl(test_input: TestCompileInput<'_>) { + // First step - generate the preimage hashes. + let preimage_output = TWTransactionCompiler::::preimage_hashes( + test_input.coin, + test_input.input.clone(), + ); + assert_eq!(preimage_output.error, SigningError::OK); + assert!(preimage_output.error_message.is_empty()); + + let actual_str = match String::from_utf8(preimage_output.data.to_vec()) { + Ok(actual_tx) => actual_tx, + Err(_) => preimage_output.data.to_hex(), + }; + + assert_eq!( + actual_str, test_input.tx_preimage, + "Unexpected preimage transaction" + ); + assert_eq!( + preimage_output.data_hash.to_hex(), + test_input.tx_prehash, + "Unexpected preimage hash" + ); + + // Second step - Compile the transaction. + + let public_key = test_input.input.public_key.to_vec(); + let compile_output = TWTransactionCompiler::::compile( + test_input.coin, + test_input.input, + vec![test_input.signature.decode_hex().unwrap()], + vec![public_key], + ); + + assert_eq!(compile_output.error, SigningError::OK); + assert!(compile_output.error_message.is_empty()); + + let result_tx = if compile_output.serialized.is_empty() { + compile_output.json + } else { + compile_output.serialized + }; + assert_eq!(result_tx, test_input.tx, "Unexpected result transaction"); + + assert_eq!( + compile_output.signature.to_hex(), + test_input.signature, + "Unexpected signature" + ); + assert_eq!( + compile_output.signature_json, test_input.signature_json, + "Unexpected signature JSON" + ); +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs new file mode 100644 index 00000000000..7e892dd7f33 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs @@ -0,0 +1,57 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::proto::cosmos; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use tw_coin_entry::error::SigningResult; +use tw_proto::{google, to_any}; + +/// Supports Protobuf serialization only. +pub struct AuthGrantMessage { + pub granter: Address, + pub grantee: Address, + pub grant_msg: google::protobuf::Any, + pub expiration_secs: i64, +} + +impl CosmosMessage for AuthGrantMessage
{ + fn to_proto(&self) -> SigningResult { + let expiration = google::protobuf::Timestamp { + seconds: self.expiration_secs, + ..google::protobuf::Timestamp::default() + }; + let grant = cosmos::authz::v1beta1::Grant { + authorization: Some(self.grant_msg.clone()), + expiration: Some(expiration), + }; + + let proto_msg = cosmos::authz::v1beta1::MsgGrant { + granter: self.granter.to_string(), + grantee: self.grantee.to_string(), + grant: Some(grant), + }; + Ok(to_any(&proto_msg)) + } +} + +/// Supports Protobuf serialization only. +pub struct AuthRevokeMessage { + pub granter: Address, + pub grantee: Address, + pub msg_type_url: String, +} + +impl CosmosMessage for AuthRevokeMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::authz::v1beta1::MsgRevoke { + granter: self.granter.to_string(), + grantee: self.grantee.to_string(), + msg_type_url: self.msg_type_url.clone(), + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs new file mode 100644 index 00000000000..dddfb065839 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmos; +use crate::transaction::message::{message_to_json, CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +const DEFAULT_JSON_SEND_TYPE: &str = "cosmos-sdk/MsgSend"; + +/// cosmos-sdk/MsgSend +#[derive(Serialize)] +pub struct SendMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub from_address: Address, + pub to_address: Address, + pub amount: Vec, +} + +impl CosmosMessage for SendMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::bank::v1beta1::MsgSend { + from_address: self.from_address.to_string(), + to_address: self.to_address.to_string(), + amount: self.amount.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_SEND_TYPE); + message_to_json(msg_type, self) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_generic_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_generic_message.rs new file mode 100644 index 00000000000..6d64926930e --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_generic_message.rs @@ -0,0 +1,25 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::transaction::message::{CosmosMessage, JsonMessage}; +use serde_json::Value as Json; +use tw_coin_entry::error::SigningResult; + +/// Any raw JSON message. +/// Supports JSON serialization only. +pub struct JsonRawMessage { + pub msg_type: String, + pub value: Json, +} + +impl CosmosMessage for JsonRawMessage { + fn to_json(&self) -> SigningResult { + Ok(JsonMessage { + msg_type: self.msg_type.clone(), + value: self.value.clone(), + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs new file mode 100644 index 00000000000..2054761a6b5 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs @@ -0,0 +1,46 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::proto::cosmos; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +pub enum VoteOption { + Unspecified, + Yes, + Abstain, + No, + NoWithVeto, +} + +pub struct VoteMessage { + pub proposal_id: u64, + pub voter: Address, + pub option: VoteOption, +} + +impl CosmosMessage for VoteMessage
{ + fn to_proto(&self) -> SigningResult { + use cosmos::gov::v1beta1::VoteOption as ProtoVoteOption; + + let option = match self.option { + VoteOption::Unspecified => ProtoVoteOption::VOTE_OPTION_UNSPECIFIED, + VoteOption::Yes => ProtoVoteOption::VOTE_OPTION_YES, + VoteOption::Abstain => ProtoVoteOption::VOTE_OPTION_ABSTAIN, + VoteOption::No => ProtoVoteOption::VOTE_OPTION_NO, + VoteOption::NoWithVeto => ProtoVoteOption::VOTE_OPTION_NO_WITH_VETO, + }; + + let proto_msg = cosmos::gov::v1beta1::MsgVote { + proposal_id: self.proposal_id, + voter: self.voter.to_string(), + option, + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs new file mode 100644 index 00000000000..353fd780547 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs @@ -0,0 +1,163 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmos; +use crate::transaction::message::{message_to_json, CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +const DEFAULT_JSON_SET_WITHDRAW_ADDRESS_TYPE: &str = "cosmos-sdk/MsgSetWithdrawAddress"; +const DEFAULT_JSON_WITHDRAW_REWARDS_TYPE: &str = "cosmos-sdk/MsgWithdrawDelegationReward"; +const DEFAULT_JSON_BEGIN_REDELEGATE_TYPE: &str = "cosmos-sdk/MsgBeginRedelegate"; +const DEFAULT_JSON_UNDELEGATE_TYPE: &str = "cosmos-sdk/MsgUndelegate"; +const DEFAULT_JSON_DELEGATE_TYPE: &str = "cosmos-sdk/MsgDelegate"; + +/// cosmos-sdk/MsgDelegate +#[derive(Serialize)] +pub struct DelegateMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub delegator_address: Address, + pub validator_address: Address, +} + +impl CosmosMessage for DelegateMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::staking::v1beta1::MsgDelegate { + amount: Some(build_coin(&self.amount)), + delegator_address: self.delegator_address.to_string(), + validator_address: self.validator_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_DELEGATE_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgUndelegate +#[derive(Serialize)] +pub struct UndelegateMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub delegator_address: Address, + pub validator_address: Address, +} + +impl CosmosMessage for UndelegateMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::staking::v1beta1::MsgUndelegate { + amount: Some(build_coin(&self.amount)), + delegator_address: self.delegator_address.to_string(), + validator_address: self.validator_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_UNDELEGATE_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgBeginRedelegate +#[derive(Serialize)] +pub struct BeginRedelegateMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub delegator_address: Address, + pub validator_src_address: Address, + pub validator_dst_address: Address, +} + +impl CosmosMessage for BeginRedelegateMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::staking::v1beta1::MsgBeginRedelegate { + amount: Some(build_coin(&self.amount)), + delegator_address: self.delegator_address.to_string(), + validator_src_address: self.validator_src_address.to_string(), + validator_dst_address: self.validator_dst_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_BEGIN_REDELEGATE_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgWithdrawDelegationReward +#[derive(Serialize)] +pub struct WithdrawDelegationRewardMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub delegator_address: Address, + pub validator_address: Address, +} + +impl CosmosMessage for WithdrawDelegationRewardMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward { + delegator_address: self.delegator_address.to_string(), + validator_address: self.validator_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_WITHDRAW_REWARDS_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgSetWithdrawAddress +#[derive(Serialize)] +pub struct SetWithdrawAddressMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub delegator_address: Address, + pub withdraw_address: Address, +} + +impl CosmosMessage for SetWithdrawAddressMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::distribution::v1beta1::MsgSetWithdrawAddress { + delegator_address: self.delegator_address.to_string(), + withdraw_address: self.withdraw_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_SET_WITHDRAW_ADDRESS_TYPE); + message_to_json(msg_type, self) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs new file mode 100644 index 00000000000..71d51e4b9fe --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs @@ -0,0 +1,53 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::ibc; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use crate::transaction::Coin; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +pub struct Height { + pub revision_number: u64, + pub revision_height: u64, +} + +pub struct TransferTokensMessage { + /// IBC port, e.g. "transfer". + pub source_port: String, + /// IBC connection channel, e.g. "channel-141", see apis /ibc/applications/transfer/v1beta1/denom_traces (connections) or /node_info (own channel). + pub source_channel: String, + pub token: Coin, + pub sender: Address, + pub receiver: Address, + /// Timeout block height. Either timeout height or timestamp should be set. + /// Recommendation is to set height, to rev. 1 and block current + 1000 (see api /blocks/latest). + pub timeout_height: Height, + // Timeout timestamp (in nanoseconds) relative to the current block timestamp. Either timeout height or timestamp should be set. + pub timeout_timestamp: u64, +} + +impl CosmosMessage for TransferTokensMessage
{ + fn to_proto(&self) -> SigningResult { + let height = ibc::core::client::v1::Height { + revision_number: self.timeout_height.revision_number, + revision_height: self.timeout_height.revision_height, + }; + + let proto_msg = ibc::applications::transfer::v1::MsgTransfer { + source_port: self.source_port.clone(), + source_channel: self.source_channel.clone(), + token: Some(build_coin(&self.token)), + sender: self.sender.to_string(), + receiver: self.receiver.to_string(), + timeout_height: Some(height), + timeout_timestamp: self.timeout_timestamp, + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/mod.rs b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs new file mode 100644 index 00000000000..bcc1d103bee --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs @@ -0,0 +1,56 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::modules::serializer::json_serializer::AnyMsg; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_proto::google; + +pub mod cosmos_auth_message; +pub mod cosmos_bank_message; +pub mod cosmos_generic_message; +pub mod cosmos_gov_message; +pub mod cosmos_staking_message; +pub mod ibc_message; +pub mod stride_message; +pub mod terra_wasm_message; +pub mod thorchain_message; +pub mod wasm_message; + +pub type ProtobufMessage = google::protobuf::Any; +pub type CosmosMessageBox = Box; +pub type JsonMessage = AnyMsg; + +pub trait CosmosMessage { + fn into_boxed(self) -> CosmosMessageBox + where + Self: 'static + Sized, + { + Box::new(self) + } + + /// Override the method if the message can be represented as a Protobuf message. + fn to_proto(&self) -> SigningResult { + Err(SigningError(SigningErrorType::Error_not_supported)) + } + + /// Override the method if the message can be represented as a JSON object. + fn to_json(&self) -> SigningResult { + Err(SigningError(SigningErrorType::Error_not_supported)) + } +} + +/// A standard implementation of the [`CosmosMessage::to_json`] method. +/// This suits any message type that implements the `serialize` trait. +pub fn message_to_json(msg_type: &str, msg: &T) -> SigningResult { + let value = + serde_json::to_value(msg).map_err(|_| SigningError(SigningErrorType::Error_internal))?; + Ok(JsonMessage { + msg_type: msg_type.to_string(), + value, + }) +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs new file mode 100644 index 00000000000..fe99e32c155 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::proto::stride; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use tw_coin_entry::error::SigningResult; +use tw_number::U256; +use tw_proto::to_any; + +pub struct StrideLiquidStakeMessage { + pub creator: Address, + pub amount: U256, + pub host_denom: String, +} + +impl CosmosMessage for StrideLiquidStakeMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = stride::stakeibc::MsgLiquidStake { + creator: self.creator.to_string(), + amount: self.amount.to_string(), + host_denom: self.host_denom.clone(), + }; + Ok(to_any(&proto_msg)) + } +} + +pub struct StrideLiquidRedeemMessage { + pub creator: String, + pub amount: U256, + pub receiver: String, + pub host_zone: String, +} + +impl CosmosMessage for StrideLiquidRedeemMessage { + fn to_proto(&self) -> SigningResult { + let proto_msg = stride::stakeibc::MsgRedeemStake { + creator: self.creator.clone(), + amount: self.amount.to_string(), + receiver: self.receiver.clone(), + host_zone: self.host_zone.clone(), + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs new file mode 100644 index 00000000000..617f563d930 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs @@ -0,0 +1,54 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::terra; +use crate::transaction::message::wasm_message::ExecuteMsg; +use crate::transaction::message::{CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use serde_json::json; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +const DEFAULT_JSON_MSG_TYPE: &str = "wasm/MsgExecuteContract"; + +/// This method not only support token transfer, but also support all other types of contract call. +/// https://docs.terra.money/Tutorials/Smart-contracts/Manage-CW20-tokens.html#interacting-with-cw20-contract +#[derive(Serialize)] +pub struct TerraExecuteContractMessage { + pub sender: Address, + pub contract: Address, + pub execute_msg: ExecuteMsg, + pub coins: Vec, +} + +impl CosmosMessage for TerraExecuteContractMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = terra::wasm::v1beta1::MsgExecuteContract { + sender: self.sender.to_string(), + contract: self.contract.to_string(), + execute_msg: self.execute_msg.to_bytes(), + coins: self.coins.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + // Don't use `message_to_json` because we need to try to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`] if possible. + let value = json!({ + "coins": self.coins, + "contract": self.contract, + "execute_msg": self.execute_msg.try_to_json(), + "sender": self.sender, + }); + Ok(JsonMessage { + msg_type: DEFAULT_JSON_MSG_TYPE.to_string(), + value, + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs new file mode 100644 index 00000000000..2d33174bcee --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs @@ -0,0 +1,82 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::types; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use crate::transaction::Coin; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::to_any; + +pub struct ThorchainAsset { + pub chain: String, + pub symbol: String, + pub ticker: String, + pub synth: bool, +} + +impl ThorchainAsset { + pub fn to_proto(&self) -> types::Asset { + types::Asset { + chain: self.chain.clone(), + symbol: self.symbol.clone(), + ticker: self.ticker.clone(), + synth: self.synth, + } + } +} + +pub struct ThorchainCoin { + pub asset: ThorchainAsset, + pub amount: U256, + pub decimals: i64, +} + +impl ThorchainCoin { + pub fn to_proto(&self) -> types::Coin { + types::Coin { + asset: Some(self.asset.to_proto()), + amount: self.amount.to_string(), + decimals: self.decimals, + } + } +} + +pub struct ThorchainSendMessage { + pub from_address: Data, + pub to_address: Data, + pub amount: Vec, +} + +impl CosmosMessage for ThorchainSendMessage { + fn to_proto(&self) -> SigningResult { + let proto_msg = types::MsgSend { + from_address: self.from_address.clone(), + to_address: self.to_address.clone(), + amount: self.amount.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } +} + +pub struct ThorchainDepositMessage { + pub coins: Vec, + pub memo: String, + pub signer: Data, +} + +impl CosmosMessage for ThorchainDepositMessage { + fn to_proto(&self) -> SigningResult { + let proto_msg = types::MsgDeposit { + coins: self.coins.iter().map(ThorchainCoin::to_proto).collect(), + memo: self.memo.clone(), + signer: self.signer.clone(), + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs new file mode 100644 index 00000000000..fc24afc2b1b --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs @@ -0,0 +1,105 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmwasm; +use crate::transaction::message::{CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use serde_json::{json, Value as Json}; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::to_any; + +const DEFAULT_JSON_MSG_TYPE: &str = "wasm/MsgExecuteContract"; + +#[derive(Clone, Serialize)] +#[serde(untagged)] +pub enum ExecuteMsg { + /// Either a regular string or a stringified JSON object. + String(String), + /// JSON object with a type. + Json(Json), +} + +impl ExecuteMsg { + /// Tries to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`], otherwise returns the same object. + pub fn try_to_json(&self) -> ExecuteMsg { + if let ExecuteMsg::String(s) = self { + if let Ok(json) = serde_json::from_str(s) { + return ExecuteMsg::Json(json); + } + } + self.clone() + } + + pub fn json(payload: Payload) -> SigningResult { + let payload = serde_json::to_value(payload) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + Ok(ExecuteMsg::Json(payload)) + } + + pub fn to_bytes(&self) -> Data { + match self { + ExecuteMsg::String(ref s) => s.as_bytes().to_vec(), + ExecuteMsg::Json(ref j) => j.to_string().as_bytes().to_vec(), + } + } +} + +/// This method not only support token transfer, but also support all other types of contract call. +#[derive(Serialize)] +pub struct WasmExecuteContractMessage { + pub sender: Address, + pub contract: Address, + pub msg: ExecuteMsg, + pub coins: Vec, +} + +impl CosmosMessage for WasmExecuteContractMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmwasm::wasm::v1::MsgExecuteContract { + sender: self.sender.to_string(), + contract: self.contract.to_string(), + msg: self.msg.to_bytes(), + funds: self.coins.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + // Don't use `message_to_json` because we need to try to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`] if possible. + let value = json!({ + "coins": self.coins, + "contract": self.contract, + "msg": self.msg.try_to_json(), + "sender": self.sender, + }); + Ok(JsonMessage { + msg_type: DEFAULT_JSON_MSG_TYPE.to_string(), + value, + }) + } +} + +#[derive(Serialize)] +pub enum WasmExecutePayload { + #[serde(rename = "transfer")] + Transfer { + #[serde(serialize_with = "U256::as_decimal_str")] + amount: U256, + recipient: String, + }, + #[serde(rename = "send")] + Send { + #[serde(serialize_with = "U256::as_decimal_str")] + amount: U256, + contract: String, + msg: String, + }, +} diff --git a/rust/tw_cosmos_sdk/src/transaction/mod.rs b/rust/tw_cosmos_sdk/src/transaction/mod.rs new file mode 100644 index 00000000000..0d3d02812de --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/mod.rs @@ -0,0 +1,72 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::private_key::SignatureData; +use serde::Serialize; +use tw_number::U256; + +pub mod message; + +use message::CosmosMessageBox; + +/// At this moment, TW only supports the Direct signing mode. +#[derive(Clone, Copy)] +pub enum SignMode { + Direct, +} + +pub struct Fee
{ + pub amounts: Vec, + pub gas_limit: u64, + pub payer: Option
, + pub granter: Option
, +} + +#[derive(Clone, Serialize)] +pub struct Coin { + #[serde(serialize_with = "U256::as_decimal_str")] + pub amount: U256, + pub denom: String, +} + +pub struct SignerInfo { + pub public_key: PublicKey, + pub sequence: u64, + pub sign_mode: SignMode, +} + +pub struct TxBody { + pub messages: Vec, + pub memo: String, + pub timeout_height: u64, +} + +pub struct UnsignedTransaction { + pub signer: SignerInfo, + pub fee: Fee, + pub chain_id: String, + pub account_number: u64, + pub tx_body: TxBody, +} + +impl UnsignedTransaction { + pub fn into_signed(self, signature: SignatureData) -> SignedTransaction { + SignedTransaction { + signer: self.signer, + fee: self.fee, + tx_body: self.tx_body, + signature, + } + } +} + +pub struct SignedTransaction { + pub signer: SignerInfo, + pub fee: Fee, + pub tx_body: TxBody, + pub signature: SignatureData, +} diff --git a/rust/tw_cosmos_sdk/tests/compile.rs b/rust/tw_cosmos_sdk/tests/compile.rs new file mode 100644 index 00000000000..78dd632c4a8 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/compile.rs @@ -0,0 +1,101 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_compile_json, test_compile_protobuf, TestCompileInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +#[test] +fn test_compile_with_signatures() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), + amounts: vec![make_amount("uatom", "400000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(200000, make_amount("uatom", "1000"))), + public_key: "02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649" + .decode_hex() + .unwrap() + .into(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + test_compile_protobuf::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: "0a92010a8f010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126f0a2d636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78122d636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a64701a0f0a057561746f6d120634303030303012650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2102ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d56364912040a02080112130a0d0a057561746f6d12043130303010c09a0c1a0b636f736d6f736875622d342083ab21", + tx_prehash: "fa7990e1814c900efaedf1bdbedba22c22336675befe0ae39974130fc204f3de", + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#, + signature: "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"r71ROndvT99HDvf5Z18hrp1jD8TWNdjbqg3ApxZDTNB+AlEHZdRnPfqICCW66OZ8s2c5b/a5dvxrGaMfyV6Alw=="}]"#, + }); + + test_compile_json::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: r#"{"account_number":"546179","chain_id":"cosmoshub-4","fee":{"amount":[{"amount":"1000","denom":"uatom"}],"gas":"200000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"400000","denom":"uatom"}],"from_address":"cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx","to_address":"cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"}}],"sequence":"0"}"#, + tx_prehash: "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37", + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"uatom"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"400000","denom":"uatom"}],"from_address":"cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx","to_address":"cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="}]}}"#, + signature: "b53c8eadbbabac71076b5e2a8b0efc7bd4ada19cb21a6a24bbdf08c58ad60a6b4df10a3999378a6b404a2837e01b82e698d77061cad7b6a410ec5e1d2f7736ea", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="}]"#, + }); +} + +#[test] +fn test_compile_with_signatures_direct() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let body_bytes = "0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131".decode_hex().unwrap(); + let auth_info_bytes = "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c".decode_hex().unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + public_key: "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5" + .decode_hex() + .unwrap() + .into(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_compile_protobuf::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: "0a8c010a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e12013112650a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c1a0a676169612d3133303033208d08", + tx_prehash: "8a6e6f74625fd39707843360120874853cc0c1d730b087f3939f4b187c75b907", + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign.rs b/rust/tw_cosmos_sdk/tests/sign.rs new file mode 100644 index 00000000000..3be2015cc9c --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign.rs @@ -0,0 +1,361 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::tx_builder::TxBuilder; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json, test_sign_json_error, test_sign_protobuf, test_sign_protobuf_error, + TestErrorInput, TestInput, +}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_546179_private_key() -> Cow<'static, [u8]> { + "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af" + .decode_hex() + .unwrap() + .into() +} + +fn account_1366160_private_key() -> Cow<'static, [u8]> { + "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433" + .decode_hex() + .unwrap() + .into() +} + +fn make_fee(gas: u64, amount: Proto::Amount<'_>) -> Proto::Fee<'_> { + Proto::Fee { + amounts: vec![amount], + gas, + } +} + +fn make_message(message_oneof: MessageEnum) -> Proto::Message { + Proto::Message { message_oneof } +} + +fn make_amount<'a>(denom: &'a str, amount: &'a str) -> Proto::Amount<'a> { + Proto::Amount { + denom: denom.into(), + amount: amount.into(), + } +} + +fn make_height(revision_number: u64, revision_height: u64) -> Proto::Height { + Proto::Height { + revision_number, + revision_height, + } +} + +#[test] +fn test_sign_coin_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + to_address: "cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573".into(), + amounts: vec![make_amount("muon", "1")], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}}"#, + signature: "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]"#, + }); +} + +#[test] +fn test_sign_raw_json() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let raw_json_msg = Proto::mod_Message::RawJSON { + type_pb: "test".into(), + value: r#"{"test":"hello"}"#.into(), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::raw_json_message(raw_json_msg))], + ..Proto::SigningInput::default() + }; + + // `RawJSON` doesn't support Protobuf serialization and signing. + test_sign_protobuf_error::(TestErrorInput { + coin: &coin, + input: input.clone(), + error: SigningError::Error_not_supported, + }); + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"test","value":{"test":"hello"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA=="}]}}"#, + signature: "aa1c7108e3225613fb7bb331fbdf07519234b790cd3855f0cc8a8d433f9f4fa843291fde6d6d2ea1cb189c4e428813446a598172ce6048825e80d907860c8498", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA=="}]"#, + }); +} + +#[test] +fn test_sign_ibc_transfer() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let transfer_tokens = Proto::mod_Message::Transfer { + source_port: "transfer".into(), + source_channel: "channel-141".into(), + token: Some(make_amount("uatom", "100000")), + sender: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + receiver: "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn".into(), + timeout_height: Some(make_height(1, 8800000)), + ..Proto::mod_Message::Transfer::default() + }; + let input = Proto::SigningInput { + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 2, + fee: Some(make_fee(500000, make_amount("uatom", "12500"))), + private_key: account_546179_private_key(), + messages: vec![make_message(MessageEnum::transfer_tokens_message( + transfer_tokens, + ))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU="}"#, + signature: "ad07216745324f82e868b5286fef10b7b57df71cd5a116508ab3dadfd9870a8d347c54512a76ef0b5e8ae80710c55310838186cf38b76808713fe70ffd7228e5", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"rQchZ0UyT4LoaLUob+8Qt7V99xzVoRZQirPa39mHCo00fFRRKnbvC16K6AcQxVMQg4GGzzi3aAhxP+cP/XIo5Q=="}]"#, + }); + // `Transfer` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_sign_direct() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let body_bytes = "0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131".decode_hex().unwrap(); + let auth_info_bytes = "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c".decode_hex().unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let mut input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); + + // `Transfer` doesn't support JSON serialization and signing. + // Set the default fee to get `SigningError::Error_not_supported`. + input.fee = Some(Proto::Fee::default()); + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_sign_direct_0a90010a() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + // Transaction body has the following content: + // https://github.com/trustwallet/wallet-core/blob/1382e3c8ac6d8e956e25c0475039f6c3988f9355/tests/chains/Cosmos/SignerTests.cpp#L327-L340 + let body_bytes = "0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637".decode_hex().unwrap(); + let auth_info_bytes = "0a0a0a0012040a020801180112130a0d0a0575636f736d12043230303010c09a0c" + .decode_hex() + .unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let mut input = Proto::SigningInput { + account_number: 1, + chain_id: "cosmoshub-4".into(), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y="}"#, + signature: "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"SBeZICWbgzlvP45fUa2+9nd8FXbCtmungD8iZ3nkozsBKK8H+yJgl6CZiI4rNDza7CgSYVqU2cbYGsccPANHZg=="}]"#, + }); + + // `Transfer` doesn't support JSON serialization and signing. + // Set the default fee to get `SigningError::Error_not_supported`. + input.fee = Some(Proto::Fee::default()); + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_sign_vote() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let vote_msg = Proto::mod_Message::MsgVote { + proposal_id: 77, + voter: "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4".into(), + option: Proto::mod_Message::VoteOption::YES, + }; + let input = Proto::SigningInput { + account_number: 1366160, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(97681, make_amount("uatom", "2418"))), + private_key: account_1366160_private_key(), + messages: vec![make_message(MessageEnum::msg_vote(vote_msg))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted https://www.mintscan.io/cosmos/txs/2EFA054B842B1641B131137B13360F95164C6C1D51BB4A4AC6DE8F75F504AA4C + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"}"#, + signature: "3e35bdcd50b737f2ab860b5c7ef195ebcb8c3d98c5f7200ef052315ba13bfb9153b03e595e21cf14a88b2356f2935059d4b96f1d946433351d4a92f6ef675340", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"PjW9zVC3N/KrhgtcfvGV68uMPZjF9yAO8FIxW6E7+5FTsD5ZXiHPFKiLI1byk1BZ1LlvHZRkMzUdSpL272dTQA=="}]"#, + }); + + // `MsgVote` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_vote_payload() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let mut vote_msg = Proto::mod_Message::MsgVote { + proposal_id: 123, + voter: "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4".into(), + option: Proto::mod_Message::VoteOption::_UNSPECIFIED, + }; + + let tests = [ + (Proto::mod_Message::VoteOption::ABSTAIN, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b6471303770737775341802"), + (Proto::mod_Message::VoteOption::NO, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b6471303770737775341803"), + (Proto::mod_Message::VoteOption::NO_WITH_VETO, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b6471303770737775341804"), + (Proto::mod_Message::VoteOption::_UNSPECIFIED, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b647130377073777534"), + ]; + + for (option, expected) in tests { + vote_msg.option = option; + let payload = + TxBuilder::::vote_msg_from_proto(&coin, &vote_msg).unwrap(); + let actual = payload.to_proto().unwrap(); + assert_eq!(actual.value.to_hex(), expected); + } +} + +#[test] +fn test_error_missing_message() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: Vec::default(), + ..Proto::SigningInput::default() + }; + + test_sign_protobuf_error::(TestErrorInput { + coin: &coin, + input: input.clone(), + error: SigningError::Error_invalid_params, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_invalid_params, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_staking.rs b/rust/tw_cosmos_sdk/tests/sign_staking.rs new file mode 100644 index 00000000000..deaf265a136 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_staking.rs @@ -0,0 +1,418 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json, test_sign_json_error, test_sign_protobuf, TestErrorInput, TestInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_1290826_private_key() -> Cow<'static, [u8]> { + "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc" + .decode_hex() + .unwrap() + .into() +} + +fn account_1932898_private_key() -> Cow<'static, [u8]> { + "d142e036ceebe70c4e61e3909d6c16bab518edfeac8bdf91000463ce0b4a6156" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_staking_compounding_authz() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::DELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd".into(), + grantee: "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1692309600, + }; + let input = Proto::SigningInput { + account_number: 1290826, + chain_id: "cosmoshub-4".into(), + sequence: 5, + fee: Some(make_fee(96681, make_amount("uatom", "2418"))), + private_key: account_1290826_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + // Original test: https://github.com/trustwallet/wallet-core/blob/a60033f797e33628e557af7c66be539c8d78bc61/tests/chains/Cosmos/StakingTests.cpp#L18-L52 + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + // Previous: CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI= + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI="}"#, + // Previous: 81727ee8a318a7fcec7cfad59ab16ac7cb2353d99cd81ee110eafed47207fb5923aa7cd22191a8779ca15ebef3811091cf464e535140e4a5029442b22bd3f572 + signature: "400dd6721f0dac251e77a9d9b3449b5e30fa0eed6326c9253de1b5c1954bab3d0fbbdadde77539410893d2d9a1cdc6fff55e9c9f8a2f0dba491541691ba48152", + // Previous: gXJ+6KMYp/zsfPrVmrFqx8sjU9mc2B7hEOr+1HIH+1kjqnzSIZGod5yhXr7zgRCRz0ZOU1FA5KUClEKyK9P1cg== + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1K"},"signature":"QA3Wch8NrCUed6nZs0SbXjD6Du1jJsklPeG1wZVLqz0Pu9rd53U5QQiT0tmhzcb/9V6cn4ovDbpJFUFpG6SBUg=="}]"#, + }); + + // `AuthGrant` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_staking_compounding_authz_f355e659() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::DELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "cosmos1wd0hdkzq68nmwzpprcugx82msj3l2y3wh8g5vv".into(), + grantee: "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1733011200, + }; + let input = Proto::SigningInput { + account_number: 1932898, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(96681, make_amount("uatom", "2418"))), + private_key: account_1932898_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted https://www.mintscan.io/cosmos/tx/F355E659CBB8C0191213415E8F3EC6FD0AD1541F96FF192855147F6C0872A98B?height=17879293 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input, + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczF3ZDBoZGt6cTY4bm13enBwcmN1Z3g4Mm1zajNsMnkzd2g4ZzV2dhItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYIgM6uugYSZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA1yVadHrd1uQDhwasOzMBbg6zarM8PjyhRmDVY97HiX5EgQKAggBEhMKDQoFdWF0b20SBDI0MTgQqfMFGkCcDzlVwE+RUkWhjH0PiBrKDzqGgIczmj9fuMI0umrOFTqmKm/IQGol0eo4XZOIcahSYlqJ+1MOptAZM8Csqoay"}"#, + signature: "9c0f3955c04f915245a18c7d0f881aca0f3a868087339a3f5fb8c234ba6ace153aa62a6fc8406a25d1ea385d938871a852625a89fb530ea6d01933c0acaa86b2", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A1yVadHrd1uQDhwasOzMBbg6zarM8PjyhRmDVY97HiX5"},"signature":"nA85VcBPkVJFoYx9D4gayg86hoCHM5o/X7jCNLpqzhU6pipvyEBqJdHqOF2TiHGoUmJaiftTDqbQGTPArKqGsg=="}]"#, + }); +} + +#[test] +fn test_staking_remove_compounding_authz() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let auth_revoke = Proto::mod_Message::AuthRevoke { + granter: "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd".into(), + grantee: "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf".into(), + msg_type_url: "/cosmos.staking.v1beta1.MsgDelegate".into(), + }; + let input = Proto::SigningInput { + account_number: 1290826, + chain_id: "cosmoshub-4".into(), + sequence: 4, + fee: Some(make_fee(87735, make_amount("uatom", "2194"))), + private_key: account_1290826_private_key(), + messages: vec![make_message(MessageEnum::auth_revoke(auth_revoke))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://www.mintscan.io/cosmos/txs/E3218F634BB6A1BE256545EBE38275D5B02D41E88F504A43F97CD9CD2B624D44 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo="}"#, + signature: "8ecaf96ecc301a03e856c5671441aacf554e312bced8d64f2e7bf9fbeb2da4c2219f9e06cec2b9a2aa7d465d48df28b63b9b4875534080f793d0e93c05f4c83a", + // Previous: gXJ+6KMYp/zsfPrVmrFqx8sjU9mc2B7hEOr+1HIH+1kjqnzSIZGod5yhXr7zgRCRz0ZOU1FA5KUClEKyK9P1cg== + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1K"},"signature":"jsr5bswwGgPoVsVnFEGqz1VOMSvO2NZPLnv5++stpMIhn54GzsK5oqp9Rl1I3yi2O5tIdVNAgPeT0Ok8BfTIOg=="}]"#, + }); + + // `AuthRevoke` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_staking_delegate() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let delegate = Proto::mod_Message::Delegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + amount: Some(make_amount("muon", "10")), + ..Proto::mod_Message::Delegate::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::stake_message(delegate))], + mode: Proto::BroadcastMode::ASYNC, + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_ASYNC","tx_bytes":"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}"#, + signature: "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"async","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgDelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ=="}]}}"#, + signature: "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ=="}]"#, + }); +} + +#[test] +fn test_staking_delegate_custom_msg_type() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let delegate = Proto::mod_Message::Delegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + amount: Some(make_amount("muon", "10")), + type_prefix: "unreal/MsgDelegate".into(), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::stake_message(delegate))], + mode: Proto::BroadcastMode::ASYNC, + ..Proto::SigningInput::default() + }; + + // This transaction hasn't been broadcasted. + // Check if the custom type_prefix doesn't affect Protobuf serialization. + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_ASYNC","tx_bytes":"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}"#, + signature: "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"async","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"unreal/MsgDelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"D9OufIm3RGkoGU/OO7eNHe17Yg4q0OBU0pHnqtULWXIm3J1eSUxsVI6OCbGAMYEqHUB9i5b1YGrueaDYFOH9xQ=="}]}}"#, + signature: "0fd3ae7c89b7446928194fce3bb78d1ded7b620e2ad0e054d291e7aad50b597226dc9d5e494c6c548e8e09b18031812a1d407d8b96f5606aee79a0d814e1fdc5", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"D9OufIm3RGkoGU/OO7eNHe17Yg4q0OBU0pHnqtULWXIm3J1eSUxsVI6OCbGAMYEqHUB9i5b1YGrueaDYFOH9xQ=="}]"#, + }); +} + +#[test] +fn test_staking_undelegate() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let undelegate = Proto::mod_Message::Undelegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + amount: Some(make_amount("muon", "10")), + ..Proto::mod_Message::Undelegate::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::unstake_message(undelegate))], + mode: Proto::BroadcastMode::SYNC, + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ"}"#, + signature: "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"YZcRxZ4wREcS7Yy2zAil3Kw3LWkmfXY7Vgmt3pctW6VepvumgBY6a/GhGR/qtgLYyUhHYKUEScT/vf4ELEU52Q=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"sync","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgUndelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"j4WpUVohGIHa6/s0bCvuyjq1wtQGqbOtQCz92qPQjisTN44Tz++Ozx1lAP6F0M4+eTA03XerqQ8hZCeAfL/3nw=="}]}}"#, + signature: "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"j4WpUVohGIHa6/s0bCvuyjq1wtQGqbOtQCz92qPQjisTN44Tz++Ozx1lAP6F0M4+eTA03XerqQ8hZCeAfL/3nw=="}]"#, + }); +} + +#[test] +fn test_staking_restake() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let redelegate = Proto::mod_Message::BeginRedelegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_src_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + validator_dst_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + amount: Some(make_amount("muon", "10")), + ..Proto::mod_Message::BeginRedelegate::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::restake_message(redelegate))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg="}"#, + signature: "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"nnao7nF20GQ1R4V5auepReSeDIcUoJec488KXJUjQetQt2nmNvAatrZG6MQ6fgGtsUJvckWelzajWK4tdspDGA=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgBeginRedelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_dst_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_src_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"5k03Yb0loovvzagMCg4gjQJP2woriZVRcOZaXF1FSros6B1X4B8MEm3lpZwrWBJMEJVgyYA9ZaF6FLVI3WxQ2w=="}]}}"#, + signature: "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"5k03Yb0loovvzagMCg4gjQJP2woriZVRcOZaXF1FSros6B1X4B8MEm3lpZwrWBJMEJVgyYA9ZaF6FLVI3WxQ2w=="}]"#, + }); +} + +#[test] +fn test_staking_withdraw_rewards() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let withdraw = Proto::mod_Message::WithdrawDelegationReward { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + ..Proto::mod_Message::WithdrawDelegationReward::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::withdraw_stake_reward_message( + withdraw, + ))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05"}"#, + signature: "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"VtQnftKTXzDxFV0LahtVSIow42T9lDog5b1BeO/GsZ8ZRfD2jgLGNEuTnVBN1s+/p7vGFZqy2UEv/Jpai2itOQ=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"VG8NZzVvavlM+1qyK5dOSZwzEj8sLCkvTw5kh44Oco9GQxBf13FVC+s/I3HwiICqo4+o8jNMEDp3nx2C0tuY1g=="}]}}"#, + signature: "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"VG8NZzVvavlM+1qyK5dOSZwzEj8sLCkvTw5kh44Oco9GQxBf13FVC+s/I3HwiICqo4+o8jNMEDp3nx2C0tuY1g=="}]"#, + }); +} + +#[test] +fn test_staking_set_withdraw_address() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let set_address = Proto::mod_Message::SetWithdrawAddress { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + withdraw_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + ..Proto::mod_Message::SetWithdrawAddress::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::set_withdraw_address_message( + set_address, + ))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cp4BCpsBCjIvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1NldFdpdGhkcmF3QWRkcmVzcxJlCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3ASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpAkm2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="}"#, + signature: "926d9324bc3815c2303796e4a956866e60209134877980fcb14908c891cb6bcf5c3ef4e19053bf94a97104c97650032cd3a84be9c61c9783f1f81eaba45741a4", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"km2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSetWithdrawAddress","value":{"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","withdraw_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"Is+87DPQbtQmIyZASdEdb7hlZhA9ViGiOxREAi6xqs46B5ChxGtIwCGGiWFtr5f5mucsNYmWYgXeRbVxlPutog=="}]}}"#, + signature: "22cfbcec33d06ed42623264049d11d6fb86566103d5621a23b1444022eb1aace3a0790a1c46b48c0218689616daf97f99ae72c3589966205de45b57194fbada2", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"Is+87DPQbtQmIyZASdEdb7hlZhA9ViGiOxREAi6xqs46B5ChxGtIwCGGiWFtr5f5mucsNYmWYgXeRbVxlPutog=="}]"#, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_stride.rs b/rust/tw_cosmos_sdk/tests/sign_stride.rs new file mode 100644 index 00000000000..a38706a35c4 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_stride.rs @@ -0,0 +1,104 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json_error, test_sign_protobuf, TestErrorInput, TestInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_136412_private_key() -> Cow<'static, [u8]> { + "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_stride_liquid_staking_stake() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("stride"); + + let stake = Proto::mod_Message::MsgStrideLiquidStakingStake { + creator: "stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge".into(), + amount: "100000".into(), + host_denom: "uatom".into(), + }; + let input = Proto::SigningInput { + account_number: 136412, + chain_id: "stride-1".into(), + sequence: 0, + fee: Some(make_fee(500000, make_amount("ustrd", "0"))), + private_key: account_136412_private_key(), + messages: vec![make_message(MessageEnum::msg_stride_liquid_staking_stake( + stake, + ))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://www.mintscan.io/stride/txs/48E51A2571D99453C4581B30CECA2A1156C0D1EBACCD3619729B5A35AD67CC94?height=3485243 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CmMKYQofL3N0cmlkZS5zdGFrZWliYy5Nc2dMaXF1aWRTdGFrZRI+Ci1zdHJpZGUxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTBhMnNqZ2USBjEwMDAwMBoFdWF0b20SYgpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhAKCgoFdXN0cmQSATAQoMIeGkCDaZHV5/Z3CAQC5DXkaHmF6OKUiS5XKDsl3ZnBaaVuJjlSWV2vA7MPwGbC17P6jbVJt58ZLcxIWFt76UO3y1ix"}"#, + signature: "836991d5e7f677080402e435e4687985e8e294892e57283b25dd99c169a56e263952595daf03b30fc066c2d7b3fa8db549b79f192dcc48585b7be943b7cb58b1", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"g2mR1ef2dwgEAuQ15Gh5hejilIkuVyg7Jd2ZwWmlbiY5UlldrwOzD8Bmwtez+o21SbefGS3MSFhbe+lDt8tYsQ=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_stride_liquid_staking_redeem() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("stride"); + + let redeem = Proto::mod_Message::MsgStrideLiquidStakingRedeem { + creator: "stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge".into(), + amount: "40000".into(), + receiver: "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4".into(), + host_zone: "cosmoshub-4".into(), + }; + let input = Proto::SigningInput { + account_number: 136412, + chain_id: "stride-1".into(), + sequence: 1, + fee: Some(make_fee(1000000, make_amount("ustrd", "0"))), + private_key: account_136412_private_key(), + messages: vec![make_message(MessageEnum::msg_stride_liquid_staking_redeem( + redeem, + ))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://www.mintscan.io/stride/txs/B3D3A92A2FFB92A480A4B547A4303E6932204972A965D687DB4FB6B4E16B2C42?height=3485343 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpgBCpUBCh8vc3RyaWRlLnN0YWtlaWJjLk1zZ1JlZGVlbVN0YWtlEnIKLXN0cmlkZTFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMGEyc2pnZRIFNDAwMDAaC2Nvc21vc2h1Yi00Ii1jb3Ntb3MxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTA3cHN3dTQSZApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBGAESEAoKCgV1c3RyZBIBMBDAhD0aQKf84TYoPqwnXw22r0dok2fYplUFu003TlIfpoT+wqTZF1lHPC+RTAoJob6x50CnfvGlgJFBEQYPD+Ccv659VVA="}"#, + signature: "a7fce136283eac275f0db6af47689367d8a65505bb4d374e521fa684fec2a4d91759473c2f914c0a09a1beb1e740a77ef1a580914111060f0fe09cbfae7d5550", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"p/zhNig+rCdfDbavR2iTZ9imVQW7TTdOUh+mhP7CpNkXWUc8L5FMCgmhvrHnQKd+8aWAkUERBg8P4Jy/rn1VUA=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_terra_wasm.rs b/rust/tw_cosmos_sdk/tests/sign_terra_wasm.rs new file mode 100644 index 00000000000..f3ac72bbf8f --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_terra_wasm.rs @@ -0,0 +1,272 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::json; +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::tx_builder::TxBuilder; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{test_sign_json, test_sign_protobuf, TestInput}; +use tw_encoding::base64; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_number::U256; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_3407705_private_key() -> Cow<'static, [u8]> { + "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_terra_wasm_transfer_protobuf_9ff3f0() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmTerraExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + amount: U256::encode_be_compact(250000), + recipient_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 3, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_transfer_message(transfer), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw"}"#, + signature: "1aa6b20430b3c7d87985770146a1a8a96f6188ca15edea899bcfe58c9c6da6391048ea016dd0cf88ca6620cd9cfac35496e7647ddb6c9a7c39e7ef25dfbad470", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"GqayBDCzx9h5hXcBRqGoqW9hiMoV7eqJm8/ljJxtpjkQSOoBbdDPiMpmIM2c+sNUludkfdtsmnw55+8l37rUcA=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_transfer_json_078e90() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmTerraExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + amount: U256::encode_be_compact(250000), + recipient_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 2, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_transfer_message(transfer), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","execute_msg":{"transfer":{"amount":"250000","recipient":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp"}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg=="}]}}"#, + signature: "06311376d6c0f7b5afd73bdcb02575b4cf9b758282f0edee19393898c46fea9049076cbf0eceeaa12eecff3ae48586a4d580a192541eb47b08df45d15f318892", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_generic() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let generic = Proto::mod_Message::WasmTerraExecuteContractGeneric { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + execute_msg: r#"{"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } }"#.into(), + coins: Vec::default(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 7, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_generic(generic), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw=="}"#, + signature: "90fb12ef19529e0d8b31cf4a883d6ca0de4d2da0dc521f08f6890f9ac74937795ed41ef2c5118d0786907e169197ff19d2be6f8ad453d005aec436ac77cc1c17", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"kPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","execute_msg":{"transfer":{"amount":"250000","recipient":"terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj"}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"2Ph/0CdoT/lUn4fvQUp0mN+uhQ80W/9YWS6B8gmwWP5pgdFWDvkvWB4ytGRQ5M8XxJnLi32qZvsPW3Nbv9YElw=="}]}}"#, + signature: "d8f87fd027684ff9549f87ef414a7498dfae850f345bff58592e81f209b058fe6981d1560ef92f581e32b46450e4cf17c499cb8b7daa66fb0f5b735bbfd60497", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"2Ph/0CdoT/lUn4fvQUp0mN+uhQ80W/9YWS6B8gmwWP5pgdFWDvkvWB4ytGRQ5M8XxJnLi32qZvsPW3Nbv9YElw=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_generic_with_coins() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let generic = Proto::mod_Message::WasmTerraExecuteContractGeneric { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC Market + contract_address: "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s".into(), + execute_msg: r#"{ "deposit_stable": {} }"#.into(), + coins: vec![make_amount("uusd", "1000")], + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 9, + fee: Some(make_fee(600000, make_amount("uluna", "7000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_generic(generic), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg=="}"#, + signature: "1b28bb7f58a863c5d5ea98c5ab5b3ce8e9b8fbe088527777acb1e27f68a8a42718bd7d262e44e543a35411183c9d64d8817723e9f3933bfa6d80fb80b6fd0d5a", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"Gyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"7000","denom":"uluna"}],"gas":"600000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[{"amount":"1000","denom":"uusd"}],"contract":"terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s","execute_msg":{"deposit_stable":{}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"mmfWOFq/9WaQ3PfZZ3C2dgU6S7PH+p/WzVruTpx9UPEHA8dwlTm5rk47YwUbhwKWmpQoUq0fON+GR1VE+rFHqA=="}]}}"#, + signature: "9a67d6385abff56690dcf7d96770b676053a4bb3c7fa9fd6cd5aee4e9c7d50f10703c7709539b9ae4e3b63051b8702969a942852ad1f38df86475544fab147a8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"mmfWOFq/9WaQ3PfZZ3C2dgU6S7PH+p/WzVruTpx9UPEHA8dwlTm5rk47YwUbhwKWmpQoUq0fON+GR1VE+rFHqA=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let encoded_msg = base64::encode(r#"{"some_message":{}}"#.as_bytes(), false); + assert_eq!(encoded_msg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); + + let send = Proto::mod_Message::WasmTerraExecuteContractSend { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + amount: U256::encode_be_compact(250000), + recipient_contract_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + msg: encoded_msg.into(), + coin: Vec::default(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 4, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_send_message(send), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CocCCoQCCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLZAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Gnt7InNlbmQiOnsiYW1vdW50IjoiMjUwMDAwIiwiY29udHJhY3QiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCIsIm1zZyI6ImV5SnpiMjFsWDIxbGMzTmhaMlVpT250OWZRPT0ifX0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAQSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQL6NByKeRZsyq5g6CTMdmPqiM77nOe9uLO8FjpetFgkBFiG3Le7ieZZ+4vCMhD1bcFgMwSHibFI/uPil847U/+g="}"#, + signature: "be8d07229e459b32ab983a09331d98faa233bee739ef6e2cef058e97ad1609011621b72deee279967ee2f08c843d5b70580cc121e26c523fb8f8a5f38ed4ffe8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"vo0HIp5FmzKrmDoJMx2Y+qIzvuc5724s7wWOl60WCQEWIbct7uJ5ln7i8IyEPVtwWAzBIeJsUj+4+KXzjtT/6A=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","execute_msg":{"send":{"amount":"250000","contract":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp","msg":"eyJzb21lX21lc3NhZ2UiOnt9fQ=="}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"17wYg45V0rjwCxMdlvcKliFnPdE7LcBoN83lfZaHB3R4XxaBqUv3IGEl3wrIjGo2OBf5SD1HkQywloPvoI6ENA=="}]}}"#, + signature: "d7bc18838e55d2b8f00b131d96f70a9621673dd13b2dc06837cde57d96870774785f1681a94bf7206125df0ac88c6a363817f9483d47910cb09683efa08e8434", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"17wYg45V0rjwCxMdlvcKliFnPdE7LcBoN83lfZaHB3R4XxaBqUv3IGEl3wrIjGo2OBf5SD1HkQywloPvoI6ENA=="}]"#, + }); +} + +#[test] +fn test_terra_transfer_payload() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmTerraExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_address: "recipient=address".into(), + amount: U256::encode_be_compact(250000), + }; + + let payload = + TxBuilder::::wasm_terra_execute_contract_transfer_msg_from_proto( + &coin, &transfer, + ) + .unwrap(); + let actual = payload.to_json().unwrap(); + let expected = json!({ + "coins": [], + "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "execute_msg": { + "transfer": { + "amount": "250000", + "recipient": "recipient=address" + } + }, + "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" + }); + assert_eq!(actual.value, expected); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_thorchain.rs b/rust/tw_cosmos_sdk/tests/sign_thorchain.rs new file mode 100644 index 00000000000..967ce2e48e9 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_thorchain.rs @@ -0,0 +1,135 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use std::str::FromStr; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_fee_none, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json_error, test_sign_protobuf, TestErrorInput, TestInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_593_private_key() -> Cow<'static, [u8]> { + "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e" + .decode_hex() + .unwrap() + .into() +} + +fn account_75247_private_key() -> Cow<'static, [u8]> { + "2659e41d54ebd449d68b9d58510d8eeeb837ee00d6ecc760b7a731238d8c3113" + .decode_hex() + .unwrap() + .into() +} + +fn address_to_key_hash(addr: &str) -> Cow<'static, [u8]> { + Address::from_str(addr).unwrap().key_hash().to_vec().into() +} + +fn make_thorchain_coin<'a>( + amount: &str, + decimals: i64, + asset: Proto::THORChainAsset<'a>, +) -> Proto::THORChainCoin<'a> { + Proto::THORChainCoin { + amount: amount.to_string().into(), + decimals, + asset: Some(asset), + } +} + +#[test] +fn test_thorchain_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("thor"); + + let send = Proto::mod_Message::THORChainSend { + from_address: address_to_key_hash("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"), + to_address: address_to_key_hash("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"), + amounts: vec![make_amount("rune", "38000000")], + }; + let input = Proto::SigningInput { + account_number: 593, + chain_id: "thorchain-mainnet-v1".into(), + sequence: 21, + fee: Some(make_fee(2500000, make_amount("rune", "200"))), + private_key: account_593_private_key(), + messages: vec![make_message(MessageEnum::thorchain_send_message(send))], + ..Proto::SigningInput::default() + }; + + // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g="}"#, + signature: "a66d4b70136b6e8e386bea74a9b5e196c778d7e43ed21a8d78b93246795da5f649639e5fe62708f67d4f117d263e5c548a3bd66e79c6ae2154bdf20db45ef3d8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"pm1LcBNrbo44a+p0qbXhlsd41+Q+0hqNeLkyRnldpfZJY55f5icI9n1PEX0mPlxUijvWbnnGriFUvfINtF7z2A=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_thorchain_deposit() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("thor"); + + let deposit = Proto::mod_Message::THORChainDeposit { + memo: "=:DOGE.DOGE:DNhRF1h8J4ZnB1bxp9kaqhVLYetkx1nSJ5::tr:0".into(), + signer: address_to_key_hash("thor14j5lwl8ulexrqp5x39kmkctv2937694z3jn2dz"), + coins: vec![make_thorchain_coin( + "150000000", + 0, + Proto::THORChainAsset { + chain: "THOR".into(), + symbol: "RUNE".into(), + ticker: "RUNE".into(), + ..Proto::THORChainAsset::default() + }, + )], + }; + let input = Proto::SigningInput { + account_number: 75247, + chain_id: "thorchain-mainnet-v1".into(), + sequence: 7, + fee: Some(make_fee_none(50000000)), + private_key: account_75247_private_key(), + messages: vec![make_message(MessageEnum::thorchain_deposit_message( + deposit, + ))], + ..Proto::SigningInput::default() + }; + + // https://viewblock.io/thorchain/tx/0162213E7F9D85965B1C57FA3BF9603C655B542F358318303A7B00661AE42510 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUBCoIBChEvdHlwZXMuTXNnRGVwb3NpdBJtCh8KEgoEVEhPUhIEUlVORRoEUlVORRIJMTUwMDAwMDAwEjQ9OkRPR0UuRE9HRTpETmhSRjFoOEo0Wm5CMWJ4cDlrYXFoVkxZZXRreDFuU0o1Ojp0cjowGhSsqfd8/P5MMAaGiW27YWxRY+0WohJZClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDuZVDlIFW3DtSEBa6aUBJ0DrQHlQ+2g7lIt5ekAM25SkSBAoCCAEYBxIFEIDh6xcaQAxKMZMKbM8gdLwn23GDXfbwyCkgqWzFMFlnrqFm0u54F8T32wmsoJQAdoLIyOskYmi7nb1rhryfabeeULwRhiw="}"#, + signature: "0c4a31930a6ccf2074bc27db71835df6f0c82920a96cc5305967aea166d2ee7817c4f7db09aca094007682c8c8eb246268bb9dbd6b86bc9f69b79e50bc11862c", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A7mVQ5SBVtw7UhAWumlASdA60B5UPtoO5SLeXpADNuUp"},"signature":"DEoxkwpszyB0vCfbcYNd9vDIKSCpbMUwWWeuoWbS7ngXxPfbCayglAB2gsjI6yRiaLudvWuGvJ9pt55QvBGGLA=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs b/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs new file mode 100644 index 00000000000..7e0d375ea48 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs @@ -0,0 +1,253 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::json; +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::tx_builder::TxBuilder; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{test_sign_json, test_sign_protobuf, TestInput}; +use tw_encoding::base64; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_number::U256; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_336_private_key() -> Cow<'static, [u8]> { + "37f0af5bc20adb6832d39368a15492cd1e9e0cc1556d4317a5f75f9ccdf525ee" + .decode_hex() + .unwrap() + .into() +} + +fn account_3407705_private_key() -> Cow<'static, [u8]> { + "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616" + .decode_hex() + .unwrap() + .into() +} + +/// Airdrop Neutron +#[test] +fn test_wasm_execute_generic() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("neutron"); + + let execute_msg = r#"{"claim":{"address":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57", "proof":["404ae2093edcca979ccb6ae4a36689cebc9c2c6a2b00b106c5396b079bf6dcf5","282fee30a25a60904f54d4f74aee8fcf8dd2822799c43be733e18e15743d4ece","e10de4202fe6532329d0d463d9669f1b659920868b9ea87d6715bfd223a86a40","564b4122c6f98653153d8e09d5a5f659fa7ebea740aa6b689c94211f8a11cc4b"], "amount":"2000000"}}"#; + let contract = Proto::mod_Message::WasmExecuteContractGeneric { + sender_address: "neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57".into(), + // ANC + contract_address: "neutron1465d8udjudl6cd8kgdlh2s37p7q0cf9x7yveumqwqk6ng94qwnmq7n79qn" + .into(), + execute_msg: execute_msg.into(), + ..Proto::mod_Message::WasmExecuteContractGeneric::default() + }; + + let input = Proto::SigningInput { + account_number: 336, + chain_id: "pion-1".into(), + sequence: 0, + fee: Some(make_fee(666666, make_amount("untrn", "1000"))), + private_key: account_336_private_key(), + messages: vec![make_message(MessageEnum::wasm_execute_contract_generic( + contract, + ))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://explorer.rs-testnet.polypore.xyz/pion-1/tx/28F25164B1E2556844C227819B1D5437960B7E91181B37460EC6792588FF7E4E + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpQECpEECiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS6AMKLm5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTcSQm5ldXRyb24xNDY1ZDh1ZGp1ZGw2Y2Q4a2dkbGgyczM3cDdxMGNmOXg3eXZldW1xd3FrNm5nOTRxd25tcTduNzlxbhrxAnsiY2xhaW0iOnsiYWRkcmVzcyI6Im5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTciLCAicHJvb2YiOlsiNDA0YWUyMDkzZWRjY2E5NzljY2I2YWU0YTM2Njg5Y2ViYzljMmM2YTJiMDBiMTA2YzUzOTZiMDc5YmY2ZGNmNSIsIjI4MmZlZTMwYTI1YTYwOTA0ZjU0ZDRmNzRhZWU4ZmNmOGRkMjgyMjc5OWM0M2JlNzMzZTE4ZTE1NzQzZDRlY2UiLCJlMTBkZTQyMDJmZTY1MzIzMjlkMGQ0NjNkOTY2OWYxYjY1OTkyMDg2OGI5ZWE4N2Q2NzE1YmZkMjIzYTg2YTQwIiwiNTY0YjQxMjJjNmY5ODY1MzE1M2Q4ZTA5ZDVhNWY2NTlmYTdlYmVhNzQwYWE2YjY4OWM5NDIxMWY4YTExY2M0YiJdLCAiYW1vdW50IjoiMjAwMDAwMCJ9fRJlCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECqPwhojhpWpB3vDr8R+qyUnDkcK3BPxS35F8OrHPq5WwSBAoCCAESEwoNCgV1bnRybhIEMTAwMBCq2CgaQMIEXC8zyuuXWuIeX7dZBBzxMjmheOP1ONitBrVZdwmuQUgClmwhOdW0JwRe8CJ5NUKqtDYZjKFAPKGEWQ2veDs="}"#, + signature: "c2045c2f33caeb975ae21e5fb759041cf13239a178e3f538d8ad06b5597709ae414802966c2139d5b427045ef022793542aab436198ca1403ca184590daf783b", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Aqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVs"},"signature":"wgRcLzPK65da4h5ft1kEHPEyOaF44/U42K0GtVl3Ca5BSAKWbCE51bQnBF7wInk1Qqq0NhmMoUA8oYRZDa94Ow=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"untrn"}],"gas":"666666"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"neutron1465d8udjudl6cd8kgdlh2s37p7q0cf9x7yveumqwqk6ng94qwnmq7n79qn","msg":{"claim":{"address":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57","amount":"2000000","proof":["404ae2093edcca979ccb6ae4a36689cebc9c2c6a2b00b106c5396b079bf6dcf5","282fee30a25a60904f54d4f74aee8fcf8dd2822799c43be733e18e15743d4ece","e10de4202fe6532329d0d463d9669f1b659920868b9ea87d6715bfd223a86a40","564b4122c6f98653153d8e09d5a5f659fa7ebea740aa6b689c94211f8a11cc4b"]}},"sender":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Aqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVs"},"signature":"R0zmQ4RCZ+UL+dTxgCHjK3IRklnLDWIRn6ZYDT9CZzUThcJdxlwxog0zCAAWhzH6HDv1T6LvdATlm7p93o+jzA=="}]}}"#, + signature: "474ce643844267e50bf9d4f18021e32b72119259cb0d62119fa6580d3f4267351385c25dc65c31a20d330800168731fa1c3bf54fa2ef7404e59bba7dde8fa3cc", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Aqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVs"},"signature":"R0zmQ4RCZ+UL+dTxgCHjK3IRklnLDWIRn6ZYDT9CZzUThcJdxlwxog0zCAAWhzH6HDv1T6LvdATlm7p93o+jzA=="}]"#, + }); +} + +/// TerraV2 DepositStable +#[test] +fn test_wasm_execute_generic_with_coins() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let execute_msg = r#"{ "deposit_stable": {} }"#; + let contract = Proto::mod_Message::WasmExecuteContractGeneric { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + contract_address: "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s".into(), + execute_msg: execute_msg.into(), + coins: vec![make_amount("uusd", "1000")], + ..Proto::mod_Message::WasmExecuteContractGeneric::default() + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "phoenix-1".into(), + sequence: 9, + fee: Some(make_fee(600000, make_amount("uluna", "7000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message(MessageEnum::wasm_execute_contract_generic( + contract, + ))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CrABCq0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QShAEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTFzZXBmajdzMGFlZzU5Njd1eG5mazR0aHpsZXJyc2t0a3BlbG01cxoYeyAiZGVwb3NpdF9zdGFibGUiOiB7fSB9KgwKBHV1c2QSBDEwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAkSEwoNCgV1bHVuYRIENzAwMBDAzyQaQEDA2foXegF+rslj6o8bX2HPJfn+q/6Ezbq2iAd0SFOTQqS8aAyywQkdZJRToXcaby1HOYL1WvmsMPgrFzChiY4="}"#, + signature: "40c0d9fa177a017eaec963ea8f1b5f61cf25f9feabfe84cdbab688077448539342a4bc680cb2c1091d649453a1771a6f2d473982f55af9ac30f82b1730a1898e", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"QMDZ+hd6AX6uyWPqjxtfYc8l+f6r/oTNuraIB3RIU5NCpLxoDLLBCR1klFOhdxpvLUc5gvVa+aww+CsXMKGJjg=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"7000","denom":"uluna"}],"gas":"600000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[{"amount":"1000","denom":"uusd"}],"contract":"terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s","msg":{"deposit_stable":{}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"ohL3xBbHumPGwz7nCyocMmS9n08bq27bOlV3hRSduNZzwsaxq5IktzizeYTRmv5uLvAhKHsrsMwWvJWU0J0nvw=="}]}}"#, + signature: "a212f7c416c7ba63c6c33ee70b2a1c3264bd9f4f1bab6edb3a557785149db8d673c2c6b1ab9224b738b37984d19afe6e2ef021287b2bb0cc16bc9594d09d27bf", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"ohL3xBbHumPGwz7nCyocMmS9n08bq27bOlV3hRSduNZzwsaxq5IktzizeYTRmv5uLvAhKHsrsMwWvJWU0J0nvw=="}]"#, + }); +} + +/// TerraV2 Transfer +#[test] +fn test_wasm_execute_transfer() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + amount: U256::encode_be_compact(250000), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "phoenix-1".into(), + sequence: 3, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_execute_contract_transfer_message(transfer), + )], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ=="}"#, + signature: "88119b41a8fe8ec5c4ebf16cb03ddf0bbed03b1a658bd1aab0f79af8aa0dc8c2048158fcf478a2fa85356c01104b8a95d12684d95e91372db8b827054c845b71", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"iBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","msg":{"transfer":{"amount":"250000","recipient":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp"}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"fuuH43BvZGyTHb7Eaw0QDSbnOm11qRWj7HljhEmvKbFJztaSpLJ0cDmENksmt4lmPtIE26EZ1SZ6XriGX6LS0A=="}]}}"#, + signature: "7eeb87e3706f646c931dbec46b0d100d26e73a6d75a915a3ec79638449af29b149ced692a4b274703984364b26b789663ed204dba119d5267a5eb8865fa2d2d0", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"fuuH43BvZGyTHb7Eaw0QDSbnOm11qRWj7HljhEmvKbFJztaSpLJ0cDmENksmt4lmPtIE26EZ1SZ6XriGX6LS0A=="}]"#, + }); +} + +/// TerraV2 Transfer +#[test] +fn test_wasm_execute_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let encoded_msg = base64::encode(r#"{"some_message":{}}"#.as_bytes(), false); + let send = Proto::mod_Message::WasmExecuteContractSend { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_contract_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + amount: U256::encode_be_compact(250000), + msg: encoded_msg.into(), + coin: Vec::default(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "phoenix-1".into(), + sequence: 4, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_execute_contract_send_message(send), + )], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUCCoICCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS2QEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3Nhp7eyJzZW5kIjp7ImFtb3VudCI6IjI1MDAwMCIsImNvbnRyYWN0IjoidGVycmExamxnYXF5OW52bjJoZjV0MnNyYTl5Y3o4czc3d25mOWwwa21nY3AiLCJtc2ciOiJleUp6YjIxbFgyMWxjM05oWjJVaU9udDlmUT09In19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgEEhMKDQoFdWx1bmESBDMwMDAQwJoMGkBKJbW1GDrv9j2FIckm7MtpDZzP2RjgDjU84oYmOHNHsxEBPLjtt3YAjsKWBCAsjbnbVoJ3s2XFG08nxQXS9xBK"}"#, + signature: "4a25b5b5183aeff63d8521c926eccb690d9ccfd918e00e353ce28626387347b311013cb8edb776008ec29604202c8db9db568277b365c51b4f27c505d2f7104a", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"SiW1tRg67/Y9hSHJJuzLaQ2cz9kY4A41POKGJjhzR7MRATy47bd2AI7ClgQgLI2521aCd7NlxRtPJ8UF0vcQSg=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","msg":{"send":{"amount":"250000","contract":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp","msg":"eyJzb21lX21lc3NhZ2UiOnt9fQ=="}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"EXQcMfD4507CcIKeOgkPWY92EUpQ7NJ802bf1IoMfgk69iUs6iEYucTo0MJo/Igys0yfbIHiLt4oJMetwNpqmA=="}]}}"#, + signature: "11741c31f0f8e74ec270829e3a090f598f76114a50ecd27cd366dfd48a0c7e093af6252cea2118b9c4e8d0c268fc8832b34c9f6c81e22ede2824c7adc0da6a98", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"EXQcMfD4507CcIKeOgkPWY92EUpQ7NJ802bf1IoMfgk69iUs6iEYucTo0MJo/Igys0yfbIHiLt4oJMetwNpqmA=="}]"#, + }); +} + +#[test] +fn test_terra_transfer_payload() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_address: "recipient=address".into(), + amount: U256::encode_be_compact(250000), + }; + + let payload = + TxBuilder::::wasm_execute_contract_transfer_msg_from_proto( + &coin, &transfer, + ) + .unwrap(); + let actual = payload.to_json().unwrap(); + let expected = json!({ + "coins": [], + "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "msg": { + "transfer": { + "amount": "250000", + "recipient": "recipient=address" + } + }, + "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" + }); + assert_eq!(actual.value, expected); +} diff --git a/rust/tw_encoding/Cargo.toml b/rust/tw_encoding/Cargo.toml index 43798b3ae89..88008908278 100644 --- a/rust/tw_encoding/Cargo.toml +++ b/rust/tw_encoding/Cargo.toml @@ -4,7 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] +arbitrary = { version = "1", features = ["derive"], optional = true } +bcs = "0.1.6" +bech32 = "0.9.1" bs58 = "0.4.0" +ciborium = "0.2.1" data-encoding = "2.3.3" hex = "0.4.3" +serde = { version = "1.0.136", features = ["derive"] } tw_memory = { path = "../tw_memory" } + +[dev-dependencies] +serde_bytes = "0.11.12" \ No newline at end of file diff --git a/rust/tw_encoding/fuzz/.gitignore b/rust/tw_encoding/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_encoding/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_encoding/fuzz/Cargo.toml b/rust/tw_encoding/fuzz/Cargo.toml new file mode 100644 index 00000000000..81d65cd9818 --- /dev/null +++ b/rust/tw_encoding/fuzz/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "tw_encoding-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } +serde = { version = "1.0.136", features = ["derive"] } +serde_bytes = "0.11.12" + +[dependencies.tw_encoding] +path = ".." +features = ["arbitrary"] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "base_encode" +path = "fuzz_targets/base_encode.rs" +test = false +doc = false + +[[bin]] +name = "base_decode" +path = "fuzz_targets/base_decode.rs" +test = false +doc = false + +[[bin]] +name = "cbor_encode_decode" +path = "fuzz_targets/cbor_encode_decode.rs" +test = false +doc = false diff --git a/rust/tw_encoding/fuzz/fuzz_targets/base_decode.rs b/rust/tw_encoding/fuzz/fuzz_targets/base_decode.rs new file mode 100644 index 00000000000..cb98cd89186 --- /dev/null +++ b/rust/tw_encoding/fuzz/fuzz_targets/base_decode.rs @@ -0,0 +1,18 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_encoding::{base32, base58, base64, ffi::Base58Alphabet}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct BaseDecodeInput<'a> { + data: &'a str, + alphabet_base58: Base58Alphabet, + padding: bool, + is_url: bool, +} + +fuzz_target!(|input: BaseDecodeInput<'_>| { + base32::decode(input.data, None, input.padding).ok(); + base58::decode(input.data, input.alphabet_base58.into()).ok(); + base64::decode(input.data, input.is_url).ok(); +}); diff --git a/rust/tw_encoding/fuzz/fuzz_targets/base_encode.rs b/rust/tw_encoding/fuzz/fuzz_targets/base_encode.rs new file mode 100644 index 00000000000..aba1f7d1356 --- /dev/null +++ b/rust/tw_encoding/fuzz/fuzz_targets/base_encode.rs @@ -0,0 +1,18 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_encoding::{base32, base58, base64, ffi::Base58Alphabet}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct BaseEncodeInput<'a> { + data: &'a [u8], + alphabet_base58: Base58Alphabet, + padding: bool, + is_url: bool, +} + +fuzz_target!(|input: BaseEncodeInput<'_>| { + base32::encode(input.data, None, input.padding).ok(); + base58::encode(input.data, input.alphabet_base58.into()); + base64::encode(input.data, input.is_url); +}); diff --git a/rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs b/rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs new file mode 100644 index 00000000000..6c79c61db3c --- /dev/null +++ b/rust/tw_encoding/fuzz/fuzz_targets/cbor_encode_decode.rs @@ -0,0 +1,23 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; + +use serde::{Deserialize, Serialize}; + +use tw_encoding::cbor::{decode, encode}; + +#[derive(arbitrary::Arbitrary, Serialize, Deserialize, PartialEq, Debug)] +struct User { + name: String, + age: u64, + friends: Vec, + #[serde(with = "serde_bytes")] + address: Vec, + key: Vec, +} + +fuzz_target!(|se_user: User| { + let serialized = encode(&se_user).unwrap(); + let de_user = decode::(&serialized).unwrap(); + assert_eq!(se_user, de_user); +}); diff --git a/rust/tw_encoding/src/base32.rs b/rust/tw_encoding/src/base32.rs index 9831437144e..d43c10acaf5 100644 --- a/rust/tw_encoding/src/base32.rs +++ b/rust/tw_encoding/src/base32.rs @@ -10,6 +10,7 @@ use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; +/// cbindgen:ignore const ALPHABET_RFC4648: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; type EncodingMap = HashMap; diff --git a/rust/tw_encoding/src/base58.rs b/rust/tw_encoding/src/base58.rs index 7e047b51ed7..2f0a7cb8694 100644 --- a/rust/tw_encoding/src/base58.rs +++ b/rust/tw_encoding/src/base58.rs @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. use crate::{EncodingError, EncodingResult}; -use bs58::{decode::Error, Alphabet}; +use bs58::decode::Error; +pub use bs58::Alphabet; impl From for EncodingError { fn from(_: Error) -> Self { diff --git a/rust/tw_encoding/src/base64.rs b/rust/tw_encoding/src/base64.rs index dd202dec66d..5b83eda01a6 100644 --- a/rust/tw_encoding/src/base64.rs +++ b/rust/tw_encoding/src/base64.rs @@ -5,6 +5,8 @@ // file LICENSE at the root of the source code distribution tree. use crate::{EncodingError, EncodingResult}; +use serde::{Serialize, Serializer}; +use tw_memory::Data; pub fn encode(data: &[u8], is_url: bool) -> String { if is_url { @@ -22,3 +24,16 @@ pub fn decode(data: &str, is_url: bool) -> EncodingResult> { } .map_err(|_| EncodingError::InvalidInput) } + +#[derive(Clone, Debug)] +pub struct Base64Encoded(pub Data); + +impl Serialize for Base64Encoded { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let is_url = false; + serializer.serialize_str(&encode(&self.0, is_url)) + } +} diff --git a/rust/tw_encoding/src/bcs.rs b/rust/tw_encoding/src/bcs.rs new file mode 100644 index 00000000000..8051ce4ac03 --- /dev/null +++ b/rust/tw_encoding/src/bcs.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{EncodingError, EncodingResult}; +use serde::Serialize; +use tw_memory::Data; + +pub fn encode(value: &T) -> EncodingResult +where + T: ?Sized + Serialize, +{ + bcs::to_bytes(value).map_err(|_| EncodingError::InvalidInput) +} diff --git a/rust/tw_encoding/src/bech32.rs b/rust/tw_encoding/src/bech32.rs new file mode 100644 index 00000000000..397f6e213ee --- /dev/null +++ b/rust/tw_encoding/src/bech32.rs @@ -0,0 +1,31 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bech32::{FromBase32, ToBase32, Variant}; +use tw_memory::Data; + +pub use bech32::Error as Bech32Error; + +pub type Bech32Result = Result; + +pub struct Decoded { + pub hrp: String, + pub bytes: Data, +} + +pub fn encode(hrp: &str, data: &[u8]) -> Bech32Result { + bech32::encode(hrp, data.to_base32(), Variant::Bech32) +} + +pub fn decode(s: &str) -> Bech32Result { + let (hrp, base32_bytes, variant) = bech32::decode(s)?; + let bytes = Data::from_base32(&base32_bytes)?; + + if matches!(variant, Variant::Bech32) { + return Ok(Decoded { hrp, bytes }); + } + Err(Bech32Error::InvalidChecksum) +} diff --git a/rust/tw_encoding/src/cbor.rs b/rust/tw_encoding/src/cbor.rs new file mode 100644 index 00000000000..4f5e954fd62 --- /dev/null +++ b/rust/tw_encoding/src/cbor.rs @@ -0,0 +1,69 @@ +use ciborium::{de, ser}; +use serde::{de::DeserializeOwned, Serialize}; + +type CborResult = Result; + +pub fn encode(message: &S) -> CborResult> { + let mut bytes = vec![]; + ser::into_writer(message, &mut bytes).map_err(|e| e.to_string())?; + Ok(bytes) +} + +pub fn decode(data: &[u8]) -> CborResult { + de::from_reader(data).map_err(|e| e.to_string()) +} + +#[cfg(test)] +mod test { + use serde::{Deserialize, Serialize}; + + use super::{decode, encode}; + + const SERIALIZED: [u8; 70] = [ + 165, 100, 110, 97, 109, 101, 101, 65, 108, 105, 99, 101, 99, 97, 103, 101, 24, 42, 103, + 102, 114, 105, 101, 110, 100, 115, 131, 99, 66, 111, 98, 101, 67, 97, 114, 111, 108, 100, + 68, 97, 118, 101, 103, 97, 100, 100, 114, 101, 115, 115, 68, 0, 1, 2, 3, 99, 107, 101, 121, + 138, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + ]; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct User { + name: String, + age: u64, + friends: Vec, + #[serde(with = "serde_bytes")] + address: Vec, + key: Vec, + } + + fn test_user() -> User { + User { + name: "Alice".to_string(), + age: 42, + friends: vec!["Bob".to_string(), "Carol".to_string(), "Dave".to_string()], + address: vec![0, 1, 2, 3], + key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + } + } + + #[test] + fn test_serialize() { + let user = test_user(); + let serialized = encode(&user).unwrap(); + assert_eq!(serialized, SERIALIZED); + } + + #[test] + fn test_deserialize() { + let user = decode::(&SERIALIZED).unwrap(); + assert_eq!(user, test_user()); + } + + #[test] + fn test_serialize_deserialize() { + let se_user = test_user(); + let serialized = encode(&se_user).unwrap(); + let de_user = decode::(&serialized).unwrap(); + assert_eq!(se_user, de_user); + } +} diff --git a/rust/tw_encoding/src/ffi.rs b/rust/tw_encoding/src/ffi.rs index 1acde6ebcc7..83f830793bd 100644 --- a/rust/tw_encoding/src/ffi.rs +++ b/rust/tw_encoding/src/ffi.rs @@ -41,7 +41,8 @@ impl From for ErrorCode { } #[repr(C)] -#[derive(PartialEq, Debug)] +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Base58Alphabet { Bitcoin = 1, Ripple = 2, @@ -104,7 +105,7 @@ pub unsafe extern "C" fn decode_base32( }; base32::decode(input, alphabet, padding) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } @@ -141,7 +142,7 @@ pub unsafe extern "C" fn decode_base58( }; base58::decode(input, alphabet.into()) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } @@ -172,7 +173,7 @@ pub unsafe extern "C" fn decode_base64(data: *const c_char, is_url: bool) -> CBy Err(_) => return CByteArrayResult::error(CEncodingCode::InvalidInput), }; base64::decode(str_slice, is_url) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } @@ -191,7 +192,7 @@ pub unsafe extern "C" fn decode_hex(data: *const c_char) -> CByteArrayResult { }; hex::decode(hex_string) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index ec0b1d60eaf..38dc8b2e44f 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -5,14 +5,60 @@ // file LICENSE at the root of the source code distribution tree. pub use hex::FromHexError; +use tw_memory::Data; -pub fn decode(data: &str) -> Result, FromHexError> { +pub type FromHexResult = Result; + +pub trait ToHex { + fn to_hex(&self) -> String; + + fn to_hex_prefixed(&self) -> String; +} + +impl ToHex for T +where + T: AsRef<[u8]>, +{ + fn to_hex(&self) -> String { + encode(self, false) + } + + fn to_hex_prefixed(&self) -> String { + encode(self, true) + } +} + +pub trait DecodeHex { + fn decode_hex(&self) -> FromHexResult; +} + +impl<'a> DecodeHex for &'a str { + fn decode_hex(&self) -> FromHexResult { + decode(self) + } +} + +/// Decodes the given hexadecimal string. +pub fn decode(data: &str) -> FromHexResult { let hex_string = data.trim_start_matches("0x"); hex::decode(hex_string) } -pub fn encode(data: &[u8], prefixed: bool) -> String { - let encoded = hex::encode(data); +/// Decodes the given hexadecimal string leniently allowing to pass odd number of chars. +/// For example, `0x0` is extended to `0x00`, `0x123` is extended to `0x0123`. +pub fn decode_lenient(data: &str) -> FromHexResult { + let hex_string = data.trim_start_matches("0x"); + if hex_string.len() % 2 == 0 { + hex::decode(hex_string) + } else { + // Insert a leading 0. + let standard_hex = format!("0{hex_string}"); + hex::decode(standard_hex) + } +} + +pub fn encode>(data: T, prefixed: bool) -> String { + let encoded = hex::encode(data.as_ref()); if prefixed { return format!("0x{encoded}"); } diff --git a/rust/tw_encoding/src/lib.rs b/rust/tw_encoding/src/lib.rs index 648781e8af3..871a6a42887 100644 --- a/rust/tw_encoding/src/lib.rs +++ b/rust/tw_encoding/src/lib.rs @@ -7,6 +7,9 @@ pub mod base32; pub mod base58; pub mod base64; +pub mod bcs; +pub mod bech32; +pub mod cbor; pub mod ffi; pub mod hex; diff --git a/rust/tw_ethereum/Cargo.toml b/rust/tw_ethereum/Cargo.toml new file mode 100644 index 00000000000..2b8f6554a62 --- /dev/null +++ b/rust/tw_ethereum/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tw_ethereum" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../tw_coin_entry" } +tw_evm = { path = "../tw_evm" } +tw_keypair = { path = "../tw_keypair" } +tw_proto = { path = "../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../tw_encoding" } +tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/rust/tw_ethereum/src/entry.rs b/rust/tw_ethereum/src/entry.rs new file mode 100644 index 00000000000..f69893fd63f --- /dev/null +++ b/rust/tw_ethereum/src/entry.rs @@ -0,0 +1,110 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_evm::address::Address; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::evm_entry::EvmEntry; +use tw_evm::modules::compiler::Compiler; +use tw_evm::modules::json_signer::EthJsonSigner; +use tw_evm::modules::message_signer::EthMessageSigner; +use tw_evm::modules::signer::Signer; +use tw_keypair::tw::PublicKey; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct EthereumEntry; + +impl CoinEntry for EthereumEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = EthJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = EthMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Address::with_secp256k1_pubkey(public_key)) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::::compile(input, signatures, public_keys) + } + + #[inline] + fn json_signer(&self) -> Option { + Some(EthJsonSigner::default()) + } + + #[inline] + fn message_signer(&self) -> Option { + Some(EthMessageSigner) + } +} + +impl EvmEntry for EthereumEntry { + type Context = StandardEvmContext; +} diff --git a/rust/tw_ethereum/src/lib.rs b/rust/tw_ethereum/src/lib.rs new file mode 100644 index 00000000000..1afc00798db --- /dev/null +++ b/rust/tw_ethereum/src/lib.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod entry; diff --git a/rust/tw_ethereum/tests/compiler.rs b/rust/tw_ethereum/tests/compiler.rs new file mode 100644 index 00000000000..27cce260850 --- /dev/null +++ b/rust/tw_ethereum/tests/compiler.rs @@ -0,0 +1,98 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex; +use tw_ethereum::entry::EthereumEntry; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::tw; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_external_signature_sign() { + let coin = TestCoinContext::default(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + let input = Proto::SigningInput { + nonce: U256::encode_be_compact(11), + chain_id: U256::encode_be_compact(1), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + ..Proto::SigningInput::default() + }; + + // Step 1: Obtain preimage hash + let input_data = serialize(&input).unwrap(); + let preimage_data = EthereumEntry + .preimage_hashes(&coin, &input_data) + .expect("!preimage_hashes"); + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + hex::encode(&preimage.data_hash, false), + "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217" + ); + + // Simulate signature, normally obtained from signature server + let public_key = secp256k1::PublicKey::try_from("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a").unwrap(); + let public_key = tw::PublicKey::Secp256k1Extended(public_key); + let signature = hex::decode("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900").unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public_key.verify(&signature, &preimage.data_hash)); + + // Step 2: Compile transaction info + let input_data = serialize(&input).unwrap(); + let output_data = EthereumEntry + .compile( + &coin, + &input_data, + vec![signature], + vec![public_key.to_bytes()], + ) + .expect("!compile"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + assert_eq!(hex::encode(output.encoded, false), expected_encoded); + + // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + let input = Proto::SigningInput { + private_key: Cow::from( + hex::decode("4646464646464646464646464646464646464646464646464646464646464646") + .unwrap(), + ), + ..input + }; + + let input_data = serialize(&input).unwrap(); + let output_data = EthereumEntry + .sign(&coin, &input_data) + .expect("!output_data"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + assert_eq!(hex::encode(output.encoded, false), expected_encoded); +} diff --git a/rust/tw_ethereum/tests/signer.rs b/rust/tw_ethereum/tests/signer.rs new file mode 100644 index 00000000000..8e0acb0e2ae --- /dev/null +++ b/rust/tw_ethereum/tests/signer.rs @@ -0,0 +1,26 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::DecodeHex; +use tw_ethereum::entry::EthereumEntry; + +#[test] +fn test_sign_json() { + let coin = TestCoinContext::default(); + + let input_json = r#"{"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","transaction":{"transfer":{"amount":"A0i8paFgAA=="}}})"#; + let private_key = "17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543" + .decode_hex() + .unwrap(); + + EthereumEntry + .sign_json(&coin, input_json, private_key) + .expect_err("'EthEntry::sign_json' is not supported yet"); + + // Expected result - "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10" +} diff --git a/rust/tw_evm/Cargo.toml b/rust/tw_evm/Cargo.toml new file mode 100644 index 00000000000..43c3f334c72 --- /dev/null +++ b/rust/tw_evm/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_evm" +version = "0.1.0" +edition = "2021" + +[dependencies] +itertools = "0.10.5" +lazy_static = "1.4.0" +rlp = "0.5.2" +serde = { version = "1.0.159", features = ["derive"] } +serde_json = "1.0.95" +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } +tw_number = { path = "../tw_number" } +tw_proto = { path = "../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } diff --git a/rust/tw_evm/fuzz/.gitignore b/rust/tw_evm/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_evm/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_evm/fuzz/Cargo.toml b/rust/tw_evm/fuzz/Cargo.toml new file mode 100644 index 00000000000..e1ac2e6c65c --- /dev/null +++ b/rust/tw_evm/fuzz/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "tw_evm-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } +serde_json = "1.0.96" +tw_number = { path = "../../tw_number" } +tw_proto = { path = "../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_evm] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "abi_encode_function" +path = "fuzz_targets/abi_encode_function.rs" +test = false +doc = false + +[[bin]] +name = "abi_decode_value" +path = "fuzz_targets/abi_decode_value.rs" +test = false +doc = false + +[[bin]] +name = "abi_function_decode_input" +path = "fuzz_targets/abi_function_decode_input.rs" +test = false +doc = false + +[[bin]] +name = "rlp_encode" +path = "fuzz_targets/rlp_encode.rs" +test = false +doc = false + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/tw_evm/fuzz/fuzz_targets/abi_decode_value.rs b/rust/tw_evm/fuzz/fuzz_targets/abi_decode_value.rs new file mode 100644 index 00000000000..4da7cf941e9 --- /dev/null +++ b/rust/tw_evm/fuzz/fuzz_targets/abi_decode_value.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::abi_encoder::AbiEncoder; +use tw_proto::EthereumAbi::Proto; + +fuzz_target!(|input: Proto::ValueDecodingInput<'_>| { + let _ = AbiEncoder::::decode_value(input); +}); diff --git a/rust/tw_evm/fuzz/fuzz_targets/abi_encode_function.rs b/rust/tw_evm/fuzz/fuzz_targets/abi_encode_function.rs new file mode 100644 index 00000000000..a8652534169 --- /dev/null +++ b/rust/tw_evm/fuzz/fuzz_targets/abi_encode_function.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::abi_encoder::AbiEncoder; +use tw_proto::EthereumAbi::Proto; + +fuzz_target!(|input: Proto::FunctionEncodingInput<'_>| { + let _ = AbiEncoder::::encode_contract_call(input); +}); diff --git a/rust/tw_evm/fuzz/fuzz_targets/abi_function_decode_input.rs b/rust/tw_evm/fuzz/fuzz_targets/abi_function_decode_input.rs new file mode 100644 index 00000000000..cfba6120348 --- /dev/null +++ b/rust/tw_evm/fuzz/fuzz_targets/abi_function_decode_input.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::abi_encoder::AbiEncoder; +use tw_proto::EthereumAbi::Proto; + +fuzz_target!(|input: Proto::ParamsDecodingInput<'_>| { + let _ = AbiEncoder::::decode_params(input); +}); diff --git a/rust/tw_evm/fuzz/fuzz_targets/rlp_encode.rs b/rust/tw_evm/fuzz/fuzz_targets/rlp_encode.rs new file mode 100644 index 00000000000..2a686c6c553 --- /dev/null +++ b/rust/tw_evm/fuzz/fuzz_targets/rlp_encode.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::rlp_encoder::RlpEncoder; +use tw_proto::EthereumRlp::Proto; + +fuzz_target!(|input: Proto::EncodingInput<'_>| { + let _ = RlpEncoder::::encode_with_proto(input); +}); diff --git a/rust/tw_evm/fuzz/fuzz_targets/sign.rs b/rust/tw_evm/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..dfa541a2d4c --- /dev/null +++ b/rust/tw_evm/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::signer::Signer; +use tw_proto::Ethereum::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let _ = Signer::::sign_proto(input); +}); diff --git a/rust/tw_evm/src/abi/contract.rs b/rust/tw_evm/src/abi/contract.rs new file mode 100644 index 00000000000..117de4c3a87 --- /dev/null +++ b/rust/tw_evm/src/abi/contract.rs @@ -0,0 +1,62 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::function::Function; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use serde::{Deserialize, Deserializer}; +use std::collections::BTreeMap; + +/// API building calls to contracts ABI. +/// Consider adding missing field such as `errors`, `events` etc. +#[derive(Clone, Debug, Default)] +pub struct Contract { + pub functions: BTreeMap>, +} + +impl Contract { + /// Get the function named `name`, the first if there are overloaded versions of the same function. + pub fn function(&self, name: &str) -> AbiResult<&Function> { + self.functions + .get(name) + .into_iter() + .flatten() + .next() + .ok_or(AbiError(AbiErrorKind::Error_abi_mismatch)) + } +} + +impl<'de> Deserialize<'de> for Contract { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + /// Consider adding missing field such as `errors`, `events` etc. + #[derive(Deserialize)] + #[serde(tag = "type", rename_all = "snake_case")] + enum Operation { + Function(Function), + #[serde(other)] + Unsupported, + } + + let operations: Vec = Vec::deserialize(deserializer)?; + + let mut result = Contract { + functions: BTreeMap::default(), + }; + for operation in operations { + match operation { + Operation::Function(fun) => result + .functions + .entry(fun.name.clone()) + .or_default() + .push(fun), + Operation::Unsupported => (), + } + } + Ok(result) + } +} diff --git a/rust/tw_evm/src/abi/decode.rs b/rust/tw_evm/src/abi/decode.rs new file mode 100644 index 00000000000..76de9399a89 --- /dev/null +++ b/rust/tw_evm/src/abi/decode.rs @@ -0,0 +1,873 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::non_empty_array::{NonEmptyArray, NonEmptyBytes}; +use crate::abi::param::Param; +use crate::abi::param_token::NamedToken; +use crate::abi::param_type::ParamType; +use crate::abi::token::Token; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use crate::address::Address; +use lazy_static::lazy_static; +use tw_hash::{H160, H256}; +use tw_number::{I256, U256}; + +const WORD_LEN: usize = 32; + +lazy_static! { + // 0x0000000000000000000000000000000000000000000000000000000000000020 + static ref DEFAULT_DYNAMIC_OFFSET: [u8; U256::BYTES] = U256::from(WORD_LEN).to_big_endian().take(); +} + +pub fn decode_params(params: &[Param], data: &[u8]) -> AbiResult> { + let param_types: Vec<_> = params.iter().map(|param| param.kind.clone()).collect(); + let decoded_tokens = decode_params_impl(¶m_types, data) + .map_err(|_| AbiError(AbiErrorKind::Error_decoding_data))?; + + let named_tokens: Vec<_> = params + .iter() + .zip(decoded_tokens.into_iter()) + .map(|(param, value)| NamedToken::with_param_and_token(param, value)) + .collect(); + Ok(named_tokens) +} + +pub fn decode_value(param_type: &ParamType, data: &[u8]) -> AbiResult { + let offset = 0; + + let decoded = if param_type.is_dynamic() { + // If the token is dynamic, we need to append a dynamic offset that points to the `data`. + let mut encoded = Vec::with_capacity(DEFAULT_DYNAMIC_OFFSET.len() + data.len()); + encoded.extend_from_slice(&DEFAULT_DYNAMIC_OFFSET[..]); + encoded.extend_from_slice(data); + + decode_param(param_type, &encoded, offset)? + } else { + decode_param(param_type, data, offset)? + }; + + Ok(decoded.token) +} + +#[derive(Debug)] +struct DecodeResult { + token: Token, + new_offset: usize, +} + +/// Decodes ABI compliant vector of bytes into vector of tokens described by types param. +/// Returns ok, even if some data left to decode +fn decode_params_impl(types: &[ParamType], data: &[u8]) -> AbiResult> { + decode_offset(types, data).map(|(tokens, _)| tokens) +} + +fn decode_offset(types: &[ParamType], data: &[u8]) -> AbiResult<(Vec, usize)> { + // We don't support empty `FixedBytes` or `FixedArray` collections. + if data.is_empty() { + return Err(AbiError(AbiErrorKind::Error_decoding_data)); + } + + let mut tokens = vec![]; + let mut offset = 0; + + for param in types { + let res = decode_param(param, data, offset)?; + offset = res.new_offset; + tokens.push(res.token); + } + + Ok((tokens, offset)) +} + +fn decode_param(param: &ParamType, data: &[u8], offset: usize) -> AbiResult { + match param { + ParamType::Address => { + let slice = peek_32_bytes(data, offset)?; + let mut address = H160::default(); + address.copy_from_slice(&slice[12..]); + let result = DecodeResult { + token: Token::Address(Address::from_bytes(address)), + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::Int { bits } => { + let slice = peek_32_bytes(data, offset)?; + let result = DecodeResult { + token: Token::Int { + int: I256::from_big_endian(slice), + bits: *bits, + }, + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::Uint { bits } => { + let slice = peek_32_bytes(data, offset)?; + let result = DecodeResult { + token: Token::Uint { + uint: U256::from_big_endian(slice), + bits: *bits, + }, + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::Bool => { + let b = as_bool(&peek_32_bytes(data, offset)?)?; + let result = DecodeResult { + token: Token::Bool(b), + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::FixedBytes { len } => { + // FixedBytes is anything from bytes1 to bytes32. These values + // are padded with trailing zeros to fill 32 bytes. + let bytes = take_bytes(data, offset, len.get())?; + let checked_bytes = NonEmptyBytes::new(bytes)?; + let result = DecodeResult { + token: Token::FixedBytes(checked_bytes), + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::Bytes => { + let dynamic_offset = as_usize(&peek_32_bytes(data, offset)?)?; + let bytes_offset = add_checked(dynamic_offset, WORD_LEN)?; + + let len = as_usize(&peek_32_bytes(data, dynamic_offset)?)?; + let bytes = take_bytes(data, bytes_offset, len)?; + let result = DecodeResult { + token: Token::Bytes(bytes), + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::String => { + let dynamic_offset = as_usize(&peek_32_bytes(data, offset)?)?; + let bytes_offset = add_checked(dynamic_offset, WORD_LEN)?; + + let len = as_usize(&peek_32_bytes(data, dynamic_offset)?)?; + let bytes = take_bytes(data, bytes_offset, len)?; + let result = DecodeResult { + // NOTE: We're decoding strings using lossy UTF-8 decoding to + // prevent invalid strings written into contracts by either users or + // Solidity bugs from causing graph-node to fail decoding event + // data. + token: Token::String(String::from_utf8_lossy(&bytes).into()), + new_offset: offset + WORD_LEN, + }; + Ok(result) + }, + ParamType::Array { kind } => { + let len_offset = as_usize(&peek_32_bytes(data, offset)?)?; + let len = as_usize(&peek_32_bytes(data, len_offset)?)?; + + let tail_offset = add_checked(len_offset, WORD_LEN)?; + let tail = &data[tail_offset..]; + + let mut tokens = vec![]; + let mut new_offset = 0; + + for _ in 0..len { + let res = decode_param(kind, tail, new_offset)?; + new_offset = res.new_offset; + tokens.push(res.token); + } + + let result = DecodeResult { + token: Token::Array { + kind: kind.as_ref().clone(), + arr: tokens, + }, + new_offset: offset + WORD_LEN, + }; + + Ok(result) + }, + ParamType::FixedArray { kind, len } => { + let is_dynamic = param.is_dynamic(); + + let (tail, mut new_offset) = if is_dynamic { + let offset = as_usize(&peek_32_bytes(data, offset)?)?; + if offset > data.len() { + return Err(AbiError(AbiErrorKind::Error_decoding_data)); + } + (&data[offset..], 0) + } else { + (data, offset) + }; + + let mut tokens = vec![]; + + for _ in 0..len.get() { + let res = decode_param(kind, tail, new_offset)?; + new_offset = res.new_offset; + tokens.push(res.token); + } + + let checked_tokens = NonEmptyArray::new(tokens)?; + let result = DecodeResult { + token: Token::FixedArray { + arr: checked_tokens, + kind: kind.as_ref().clone(), + }, + new_offset: if is_dynamic { + offset + WORD_LEN + } else { + new_offset + }, + }; + + Ok(result) + }, + ParamType::Tuple { params } => { + let is_dynamic = param.is_dynamic(); + + // The first element in a dynamic Tuple is an offset to the Tuple's data + // For a static Tuple the data begins right away + let (tail, mut new_offset) = if is_dynamic { + let offset = as_usize(&peek_32_bytes(data, offset)?)?; + if offset > data.len() { + return Err(AbiError(AbiErrorKind::Error_decoding_data)); + } + (&data[offset..], 0) + } else { + (data, offset) + }; + + let mut named_tokens = Vec::with_capacity(params.len()); + for param in params.iter() { + let res = decode_param(¶m.kind, tail, new_offset)?; + new_offset = res.new_offset; + named_tokens.push(NamedToken { + name: param.name.clone(), + value: res.token, + internal_type: param.internal_type.clone(), + }); + } + + // The returned new_offset depends on whether the Tuple is dynamic + // dynamic Tuple -> follows the prefixed Tuple data offset element + // static Tuple -> follows the last data element + let result = DecodeResult { + token: Token::Tuple { + params: named_tokens, + }, + new_offset: if is_dynamic { + offset + WORD_LEN + } else { + new_offset + }, + }; + + Ok(result) + }, + } +} + +fn as_usize(slice: &H256) -> AbiResult { + if !slice[..28].iter().all(|x| *x == 0) { + return Err(AbiError(AbiErrorKind::Error_decoding_data)); + } + + let result = ((slice[28] as usize) << 24) + + ((slice[29] as usize) << 16) + + ((slice[30] as usize) << 8) + + (slice[31] as usize); + + Ok(result) +} + +fn as_bool(slice: &H256) -> AbiResult { + if !slice[..31].iter().all(|x| *x == 0) { + return Err(AbiError(AbiErrorKind::Error_decoding_data)); + } + + Ok(slice[31] == 1) +} + +fn peek(data: &[u8], offset: usize, len: usize) -> AbiResult<&[u8]> { + let end = add_checked(offset, len)?; + if end > data.len() { + Err(AbiError(AbiErrorKind::Error_decoding_data)) + } else { + Ok(&data[offset..end]) + } +} + +fn peek_32_bytes(data: &[u8], offset: usize) -> AbiResult { + peek(data, offset, WORD_LEN).map(|x| { + let mut out = H256::default(); + out.copy_from_slice(&x[0..WORD_LEN]); + out + }) +} + +fn take_bytes(data: &[u8], offset: usize, len: usize) -> AbiResult> { + let end = add_checked(offset, len)?; + if end > data.len() { + Err(AbiError(AbiErrorKind::Error_decoding_data)) + } else { + Ok(data[offset..end].to_vec()) + } +} + +fn add_checked(left: usize, right: usize) -> AbiResult { + left.checked_add(right) + .ok_or(AbiError(AbiErrorKind::Error_decoding_data)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::abi::non_empty_array::NonZeroLen; + use tw_encoding::hex::DecodeHex; + + fn address(byte: u8) -> Token { + let data = H160::from([byte; 20]); + Token::Address(Address::from_bytes(data)) + } + + fn u256(byte: u8) -> Token { + let data = H256::from([byte; WORD_LEN]); + Token::u256(U256::from_big_endian(data)) + } + + #[test] + fn decode_from_empty_byte_slice() { + // these can NOT be decoded from empty byte slice + assert!(decode_params_impl(&[ParamType::Address], &[]).is_err()); + assert!(decode_params_impl(&[ParamType::Bytes], &[]).is_err()); + assert!(decode_params_impl(&[ParamType::i256()], &[]).is_err()); + assert!(decode_params_impl(&[ParamType::Bool], &[]).is_err()); + assert!(decode_params_impl(&[ParamType::String], &[]).is_err()); + let kind = Box::new(ParamType::Bool); + assert!(decode_params_impl(&[ParamType::Array { kind }], &[]).is_err()); + let len = NonZeroLen::new(1).unwrap(); + assert!(decode_params_impl(&[ParamType::FixedBytes { len }], &[]).is_err()); + let kind = Box::new(ParamType::Bool); + let len = NonZeroLen::new(1).unwrap(); + assert!(decode_params_impl(&[ParamType::FixedArray { kind, len }], &[]).is_err()); + } + + #[test] + fn decode_static_tuple_of_addresses_and_uints() { + let encoded = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "1111111111111111111111111111111111111111111111111111111111111111" + ) + .decode_hex() + .unwrap(); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(address(0x11_u8)), + NamedToken::with_token(address(0x22_u8)), + NamedToken::with_token(u256(0x11_u8)), + ], + }; + let expected = vec![tuple]; + + let tuple_type = ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::Address), + Param::with_type(ParamType::Address), + Param::with_type(ParamType::u256()), + ], + }; + let decoded = decode_params_impl(&[tuple_type], &encoded).unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn decode_dynamic_tuple() { + let encoded = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000" + ) + .decode_hex() + .unwrap(); + let string1 = Token::String("gavofyork".to_owned()); + let string2 = Token::String("gavofyork".to_owned()); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string1), + NamedToken::with_token(string2), + ], + }; + let decoded = decode_params_impl( + &[ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::String), + Param::with_type(ParamType::String), + ], + }], + &encoded, + ) + .unwrap(); + let expected = vec![tuple]; + assert_eq!(decoded, expected); + } + + #[test] + fn decode_nested_tuple() { + let encoded = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000001", + "00000000000000000000000000000000000000000000000000000000000000c0", + "0000000000000000000000000000000000000000000000000000000000000100", + "0000000000000000000000000000000000000000000000000000000000000004", + "7465737400000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000006", + "6379626f72670000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000060", + "00000000000000000000000000000000000000000000000000000000000000a0", + "00000000000000000000000000000000000000000000000000000000000000e0", + "0000000000000000000000000000000000000000000000000000000000000005", + "6e69676874000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003", + "6461790000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000004", + "7765656500000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000008", + "66756e7465737473000000000000000000000000000000000000000000000000" + ) + .decode_hex() + .unwrap(); + let string1 = Token::String("test".to_owned()); + let string2 = Token::String("cyborg".to_owned()); + let string3 = Token::String("night".to_owned()); + let string4 = Token::String("day".to_owned()); + let string5 = Token::String("weee".to_owned()); + let string6 = Token::String("funtests".to_owned()); + let bool = Token::Bool(true); + let deep_tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string5), + NamedToken::with_token(string6), + ], + }; + let inner_tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string3), + NamedToken::with_token(string4), + NamedToken::with_token(deep_tuple), + ], + }; + let outer_tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string1), + NamedToken::with_token(bool), + NamedToken::with_token(string2), + NamedToken::with_token(inner_tuple), + ], + }; + let expected = vec![outer_tuple]; + + let inner_tuple_type = ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::String), + Param::with_type(ParamType::String), + Param::with_type(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::String), + Param::with_type(ParamType::String), + ], + }), + ], + }; + let decoded = decode_params_impl( + &[ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::String), + Param::with_type(ParamType::Bool), + Param::with_type(ParamType::String), + Param::with_type(inner_tuple_type), + ], + }], + &encoded, + ) + .unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn decode_complex_tuple_of_dynamic_and_static_types() { + let encoded = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "1111111111111111111111111111111111111111111111111111111111111111", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + let string = Token::String("gavofyork".to_owned()); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(u256(0x11_u8)), + NamedToken::with_token(string), + NamedToken::with_token(address(0x11_u8)), + NamedToken::with_token(address(0x22_u8)), + ], + }; + let expected = vec![tuple]; + + let tuple_type = ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::u256()), + Param::with_type(ParamType::String), + Param::with_type(ParamType::Address), + Param::with_type(ParamType::Address), + ], + }; + let decoded = decode_params_impl(&[tuple_type], &encoded).unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn decode_params_containing_dynamic_tuple() { + let encoded = concat!( + "0000000000000000000000002222222222222222222222222222222222222222", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000060", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000009", + "7370616365736869700000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000006", + "6379626f72670000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + let bool1 = Token::Bool(true); + let string1 = Token::String("spaceship".to_owned()); + let string2 = Token::String("cyborg".to_owned()); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(bool1), + NamedToken::with_token(string1), + NamedToken::with_token(string2), + ], + }; + let bool2 = Token::Bool(false); + let expected = vec![ + address(0x22_u8), + tuple, + address(0x33_u8), + address(0x44_u8), + bool2, + ]; + let decoded = decode_params_impl( + &[ + ParamType::Address, + ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::Bool), + Param::with_type(ParamType::String), + Param::with_type(ParamType::String), + ], + }, + ParamType::Address, + ParamType::Address, + ParamType::Bool, + ], + &encoded, + ) + .unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn decode_params_containing_static_tuple() { + let encoded = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + ) + .decode_hex() + .unwrap(); + let bool1 = Token::Bool(true); + let bool2 = Token::Bool(false); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(address(0x22_u8)), + NamedToken::with_token(bool1), + NamedToken::with_token(bool2), + ], + }; + + let expected = vec![address(0x11_u8), tuple, address(0x33_u8), address(0x44_u8)]; + let decoded = decode_params_impl( + &[ + ParamType::Address, + ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::Address), + Param::with_type(ParamType::Bool), + Param::with_type(ParamType::Bool), + ], + }, + ParamType::Address, + ParamType::Address, + ], + &encoded, + ) + .unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn decode_data_with_size_that_is_not_a_multiple_of_32() { + let encoded = concat!( + "0000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000152", + "0000000000000000000000000000000000000000000000000000000000000001", + "000000000000000000000000000000000000000000000000000000000054840d", + "0000000000000000000000000000000000000000000000000000000000000092", + "3132323033393637623533326130633134633938306235616566666231373034", + "3862646661656632633239336139353039663038656233633662306635663866", + "3039343265376239636337366361353163636132366365353436393230343438", + "6533303866646136383730623565326165313261323430396439343264653432", + "3831313350373230703330667073313678390000000000000000000000000000", + "0000000000000000000000000000000000103933633731376537633061363531", + "3761", + ) + .decode_hex() + .unwrap(); + + let types = [ + ParamType::u256(), + ParamType::String, + ParamType::String, + ParamType::u256(), + ParamType::u256(), + ]; + let expected = [ + Token::u256(U256::default()), + Token::String(String::from("12203967b532a0c14c980b5aeffb17048bdfaef2c293a9509f08eb3c6b0f5f8f0942e7b9cc76ca51cca26ce546920448e308fda6870b5e2ae12a2409d942de428113P720p30fps16x9")), + Token::String(String::from("93c717e7c0a6517a")), + Token::u256(U256::from(1_u64)), + Token::u256(U256::from(5538829_u64)), + ]; + assert_eq!(decode_params_impl(&types, &encoded,).unwrap(), expected); + } + + #[test] + fn decode_after_fixed_bytes_with_less_than_32_bytes() { + let encoded = concat!( + "0000000000000000000000008497afefdc5ac170a664a231f6efb25526ef813f", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000080", + "000000000000000000000000000000000000000000000000000000000000000a", + "3078303030303030314600000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + + let types = [ + ParamType::Address, + ParamType::FixedBytes { + len: NonZeroLen::new(32).unwrap(), + }, + ParamType::FixedBytes { + len: NonZeroLen::new(4).unwrap(), + }, + ParamType::String, + ]; + + let expected = [ + Token::Address(Address::from("0x8497afefdc5ac170a664a231f6efb25526ef813f")), + Token::FixedBytes(NonEmptyBytes::new(vec![0u8; 32]).unwrap()), + Token::FixedBytes(NonEmptyBytes::new(vec![0u8; 4]).unwrap()), + Token::String("0x0000001F".into()), + ]; + assert_eq!(decode_params_impl(&types, &encoded,).unwrap(), &expected); + } + + #[test] + fn decode_broken_utf8() { + let encoded = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000004", + "e4b88de500000000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + + assert_eq!( + decode_params_impl(&[ParamType::String,], &encoded).unwrap(), + &[Token::String("不�".into())] + ); + } + + #[test] + fn decode_corrupted_dynamic_array() { + // line 1 at 0x00 = 0: tail offset of array + // line 2 at 0x20 = 32: length of array + // line 3 at 0x40 = 64: first word + // line 4 at 0x60 = 96: second word + let encoded = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "00000000000000000000000000000000000000000000000000000000ffffffff", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + ) + .decode_hex() + .unwrap(); + + let types = [ParamType::Array { + kind: Box::new(ParamType::u256()), + }]; + assert!(decode_params_impl(&types, &encoded).is_err()); + } + + #[test] + fn decode_corrupted_nested_array_tuple() { + let input = concat!( + "0000000000000000000000000000000000000000000000000000000000000040", + // + "00000000000000000000000000000000000000000000000000000000000002a0", + "0000000000000000000000000000000000000000000000000000000000000009", + // + "00000000000000000000000000000000fffffffffffffffffffffffffffffffe", + "0000000000000000000000000000000000000000000000000000000000000000", + // + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + // + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000000000000000000000000000ffffffffffffffff", + // + "0008000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000020000000000000000", + // + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000001000000000000000000000000000000000000", + // + "000000000000000000000000000000000000000000000000000000000000053a", + "0100000000000000000000000000000000000000000000000000000000000000", + // + "0000000000000010000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + // + "0000000000000000000000000000000000000000000000000000000002000000", + "0000000000000000000000000000000000000000000000000000000000100000", + // + "0000000000000000000000000000000000000000000000000000000000000000", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + // + "0000000000000000000000000000000000000000000000000000000000000006", + "00000000000000000000000000000000000000000000000000000000000000c0", + // + "0000000000000000000000000000000000000000000000000000000000002ce0", + "0000000000000000000000000000000000000000000000000000000000005880", + // + "0000000000000000000000000000000000000000000000000000000000008280", + "000000000000000000000000000000000000000000000000000000000000acc0", + // + "000000000000000000000000000000000000000000000000000000000000d6e0", + "0000000000000000000000000000000000000000020000000000000000000000", + // + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000009", + // + "0000000000000000000000000000000000000000000000000000000000000120", + "0000000000000000000000000000000000000000000000000000000000000720", + // + "0000000000000000000000000000000000000000000000000000000000000b80", + "0000000000000000000000000000000000000000000000000000000000000fe0", + ) + .decode_hex() + .unwrap(); + + let types = [ + ParamType::Array { + kind: Box::new(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::u256()), + Param::with_type(ParamType::u256()), + ], + }), + }, + ParamType::Array { + kind: Box::new(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::u256()), + Param::with_type(ParamType::Array { + kind: Box::new(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::u256()), + Param::with_type(ParamType::Array { + kind: Box::new(ParamType::String), + }), + ], + }), + }), + ], + }), + }, + ]; + + assert!(decode_params_impl(&types, &input).is_err()); + } + + #[test] + fn decode_corrupted_fixed_array_of_strings() { + let input = concat!( + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000001000040", + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000008", + "5445535454455354000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000008", + "5445535454455354000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + + let types = [ + ParamType::u256(), + ParamType::FixedArray { + len: NonZeroLen::new(2).unwrap(), + kind: Box::new(ParamType::String), + }, + ]; + assert!(decode_params_impl(&types, &input).is_err()); + } + + #[test] + fn decode_whole_addresses() { + let input = concat!( + "0000000000000000000000000000000000000000000000000000000000012345", + "0000000000000000000000000000000000000000000000000000000000054321", + ) + .decode_hex() + .unwrap(); + assert!(decode_params_impl(&[ParamType::Address], &input).is_ok()); + } +} diff --git a/rust/tw_evm/src/abi/encode.rs b/rust/tw_evm/src/abi/encode.rs new file mode 100644 index 00000000000..17c2c273c38 --- /dev/null +++ b/rust/tw_evm/src/abi/encode.rs @@ -0,0 +1,1088 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::token::Token; +use tw_hash::H256; +use tw_memory::Data; + +pub fn encode_tokens(tokens: &[Token]) -> Data { + let mediates = tokens.iter().map(mediate_token).collect::>(); + + encode_head_tail(&mediates) + .into_iter() + .flat_map(H256::take) + .collect() +} + +#[derive(Debug)] +enum Mediate<'a> { + // head + Raw(u32, &'a Token), + RawArray(Vec>), + + // head + tail + Prefixed(u32, &'a Token), + PrefixedArray(Vec>), + PrefixedArrayWithLength(Vec>), +} + +impl Mediate<'_> { + fn head_len(&self) -> u32 { + match self { + Mediate::Raw(len, _) => 32 * len, + Mediate::RawArray(ref mediates) => { + mediates.iter().map(|mediate| mediate.head_len()).sum() + }, + Mediate::Prefixed(_, _) + | Mediate::PrefixedArray(_) + | Mediate::PrefixedArrayWithLength(_) => 32, + } + } + + fn tail_len(&self) -> u32 { + match self { + Mediate::Raw(_, _) | Mediate::RawArray(_) => 0, + Mediate::Prefixed(len, _) => 32 * len, + Mediate::PrefixedArray(ref mediates) => mediates + .iter() + .fold(0, |acc, m| acc + m.head_len() + m.tail_len()), + Mediate::PrefixedArrayWithLength(ref mediates) => mediates + .iter() + .fold(32, |acc, m| acc + m.head_len() + m.tail_len()), + } + } + + fn head_append(&self, acc: &mut Vec, suffix_offset: u32) { + match *self { + Mediate::Raw(_, raw) => encode_token_append(acc, raw), + Mediate::RawArray(ref raw) => { + raw.iter().for_each(|mediate| mediate.head_append(acc, 0)) + }, + Mediate::Prefixed(_, _) + | Mediate::PrefixedArray(_) + | Mediate::PrefixedArrayWithLength(_) => acc.push(pad_u32(suffix_offset)), + } + } + + fn tail_append(&self, acc: &mut Vec) { + match *self { + Mediate::Raw(_, _) | Mediate::RawArray(_) => {}, + Mediate::Prefixed(_, raw) => encode_token_append(acc, raw), + Mediate::PrefixedArray(ref mediates) => encode_head_tail_append(acc, mediates), + Mediate::PrefixedArrayWithLength(ref mediates) => { + // + 32 added to offset represents len of the array prepended to tail + acc.push(pad_u32(mediates.len() as u32)); + encode_head_tail_append(acc, mediates); + }, + }; + } +} + +fn encode_head_tail(mediates: &[Mediate]) -> Vec { + let (heads_len, tails_len) = mediates.iter().fold((0, 0), |(head_acc, tail_acc), m| { + (head_acc + m.head_len(), tail_acc + m.tail_len()) + }); + + let mut result = Vec::with_capacity((heads_len + tails_len) as usize); + encode_head_tail_append(&mut result, mediates); + + result +} + +fn encode_head_tail_append(acc: &mut Vec, mediates: &[Mediate]) { + let heads_len = mediates + .iter() + .fold(0, |head_acc, m| head_acc + m.head_len()); + + let mut offset = heads_len; + for mediate in mediates { + mediate.head_append(acc, offset); + offset += mediate.tail_len(); + } + + mediates.iter().for_each(|m| m.tail_append(acc)); +} + +fn mediate_token(token: &Token) -> Mediate { + match token { + Token::Address(_) => Mediate::Raw(1, token), + Token::Bytes(bytes) => Mediate::Prefixed(pad_bytes_len(bytes), token), + Token::String(s) => Mediate::Prefixed(pad_bytes_len(s.as_bytes()), token), + Token::FixedBytes(bytes) => Mediate::Raw(fixed_bytes_len(bytes), token), + Token::Int { .. } | Token::Uint { .. } | Token::Bool(_) => Mediate::Raw(1, token), + Token::Array { arr, .. } => { + let mediates = arr.iter().map(mediate_token).collect(); + Mediate::PrefixedArrayWithLength(mediates) + }, + Token::FixedArray { arr, .. } => { + let mediates = arr.iter().map(mediate_token).collect(); + if token.is_dynamic() { + Mediate::PrefixedArray(mediates) + } else { + Mediate::RawArray(mediates) + } + }, + Token::Tuple { params } => { + let mediates = params + .iter() + .map(|param| mediate_token(¶m.value)) + .collect(); + if token.is_dynamic() { + Mediate::PrefixedArray(mediates) + } else { + Mediate::RawArray(mediates) + } + }, + } +} + +fn encode_token_append(data: &mut Vec, token: &Token) { + match token { + Token::Address(address) => { + let mut padded = H256::default(); + padded[12..].copy_from_slice(address.as_slice()); + data.push(padded); + }, + Token::Bytes(bytes) => pad_bytes_append(data, bytes), + Token::String(s) => pad_bytes_append(data, s.as_bytes()), + Token::FixedBytes(bytes) => fixed_bytes_append(data, bytes), + Token::Int { int, .. } => data.push(int.to_big_endian()), + Token::Uint { uint, .. } => data.push(uint.to_big_endian()), + Token::Bool(b) => { + let mut value = H256::default(); + if *b { + value[31] = 1; + } + data.push(value); + }, + Token::FixedArray { .. } | Token::Array { .. } | Token::Tuple { .. } => { + debug_assert!(false, "Unhandled nested token: {:?}", token) + }, + } +} + +/// Converts a u32 to a right aligned array of 32 bytes. +pub fn pad_u32(value: u32) -> H256 { + let mut padded = H256::default(); + padded[28..32].copy_from_slice(&value.to_be_bytes()); + padded +} + +fn pad_bytes_len(bytes: &[u8]) -> u32 { + // "+ 1" because len is also appended + ((bytes.len() + 31) / 32) as u32 + 1 +} + +fn pad_bytes_append(data: &mut Vec, bytes: &[u8]) { + data.push(pad_u32(bytes.len() as u32)); + fixed_bytes_append(data, bytes); +} + +fn fixed_bytes_len(bytes: &[u8]) -> u32 { + ((bytes.len() + 31) / 32) as u32 +} + +fn fixed_bytes_append(result: &mut Vec, bytes: &[u8]) { + let len = (bytes.len() + 31) / 32; + for i in 0..len { + let mut padded = H256::default(); + + let to_copy = match i == len - 1 { + false => 32, + true => match bytes.len() % 32 { + 0 => 32, + x => x, + }, + }; + + let offset = 32 * i; + padded[..to_copy].copy_from_slice(&bytes[offset..offset + to_copy]); + result.push(padded); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::abi::non_empty_array::{NonEmptyArray, NonEmptyBytes}; + use crate::abi::param::Param; + use crate::abi::param_token::NamedToken; + use crate::abi::param_type::constructor::TypeConstructor; + use crate::abi::param_type::ParamType; + use tw_encoding::hex::DecodeHex; + use tw_number::{I256, U256}; + + #[test] + fn encode_address() { + let address = Token::Address("0x1111111111111111111111111111111111111111".into()); + let encoded = encode_tokens(&[address]); + let expected = "0000000000000000000000001111111111111111111111111111111111111111" + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_array_of_addresses() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let addresses = Token::Array { + arr: vec![address1, address2], + kind: ParamType::Address, + }; + let encoded = encode_tokens(&[addresses]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_fixed_array_of_addresses() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let addresses = { + let arr = NonEmptyArray::new(vec![address1, address2]).unwrap(); + let kind = ParamType::Address; + Token::FixedArray { arr, kind } + }; + + let encoded = encode_tokens(&[addresses]); + let expected = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_two_addresses() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let encoded = encode_tokens(&[address1, address2]); + let expected = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_fixed_array_of_dynamic_array_of_addresses() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let address3 = Token::Address("0x3333333333333333333333333333333333333333".into()); + let address4 = Token::Address("0x4444444444444444444444444444444444444444".into()); + let array0 = Token::Array { + arr: vec![address1, address2], + kind: ParamType::Address, + }; + let array1 = Token::Array { + arr: vec![address3, address4], + kind: ParamType::Address, + }; + let fixed = Token::FixedArray { + arr: NonEmptyArray::new(vec![array0, array1]).unwrap(), + kind: ParamType::array(ParamType::Address), + }; + let encoded = encode_tokens(&[fixed]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000040", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_array_of_fixed_array_of_addresses() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let address3 = Token::Address("0x3333333333333333333333333333333333333333".into()); + let address4 = Token::Address("0x4444444444444444444444444444444444444444".into()); + let array0 = Token::FixedArray { + arr: NonEmptyArray::new(vec![address1, address2]).unwrap(), + kind: ParamType::Address, + }; + let array1 = Token::FixedArray { + arr: NonEmptyArray::new(vec![address3, address4]).unwrap(), + kind: ParamType::Address, + }; + let dynamic = Token::Array { + arr: vec![array0, array1], + kind: ParamType::fixed_array(2, ParamType::Address).unwrap(), + }; + let encoded = encode_tokens(&[dynamic]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_array_of_dynamic_arrays() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let array0 = Token::Array { + arr: vec![address1], + kind: ParamType::Address, + }; + let array1 = Token::Array { + arr: vec![address2], + kind: ParamType::Address, + }; + let dynamic = Token::Array { + arr: vec![array0, array1], + kind: ParamType::array(ParamType::Address), + }; + let encoded = encode_tokens(&[dynamic]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000002222222222222222222222222222222222222222", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_array_of_dynamic_arrays2() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let address3 = Token::Address("0x3333333333333333333333333333333333333333".into()); + let address4 = Token::Address("0x4444444444444444444444444444444444444444".into()); + let array0 = Token::Array { + arr: vec![address1, address2], + kind: ParamType::Address, + }; + let array1 = Token::Array { + arr: vec![address3, address4], + kind: ParamType::Address, + }; + let dynamic = Token::Array { + arr: vec![array0, array1], + kind: ParamType::array(ParamType::Address), + }; + let encoded = encode_tokens(&[dynamic]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000040", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_fixed_array_of_fixed_arrays() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let address3 = Token::Address("0x3333333333333333333333333333333333333333".into()); + let address4 = Token::Address("0x4444444444444444444444444444444444444444".into()); + let array0 = Token::FixedArray { + arr: NonEmptyArray::new(vec![address1, address2]).unwrap(), + kind: ParamType::Address, + }; + let array1 = Token::FixedArray { + arr: NonEmptyArray::new(vec![address3, address4]).unwrap(), + kind: ParamType::Address, + }; + let fixed = Token::FixedArray { + arr: NonEmptyArray::new(vec![array0, array1]).unwrap(), + kind: ParamType::fixed_array(2, ParamType::fixed_array(2, ParamType::Address).unwrap()) + .unwrap(), + }; + let encoded = encode_tokens(&[fixed]); + let expected = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_fixed_array_of_static_tuples_followed_by_dynamic_type() { + let tuple1 = Token::Tuple { + params: vec![ + NamedToken::with_token(Token::u256(93523141_u64.into())), + NamedToken::with_token(Token::u256(352332135_u64.into())), + NamedToken::with_token(Token::Address( + "0x4444444444444444444444444444444444444444".into(), + )), + ], + }; + let tuple2 = Token::Tuple { + params: vec![ + NamedToken::with_token(Token::u256(12411_u64.into())), + NamedToken::with_token(Token::u256(451_u64.into())), + NamedToken::with_token(Token::Address( + "0x2222222222222222222222222222222222222222".into(), + )), + ], + }; + let fixed = Token::FixedArray { + arr: NonEmptyArray::new(vec![tuple1, tuple2]).unwrap(), + kind: ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::u256()), + Param::with_type(ParamType::u256()), + Param::with_type(ParamType::Address), + ], + }, + }; + let s = Token::String("gavofyork".to_owned()); + let encoded = encode_tokens(&[fixed, s]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000005930cc5", + "0000000000000000000000000000000000000000000000000000000015002967", + "0000000000000000000000004444444444444444444444444444444444444444", + "000000000000000000000000000000000000000000000000000000000000307b", + "00000000000000000000000000000000000000000000000000000000000001c3", + "0000000000000000000000002222222222222222222222222222222222222222", + "00000000000000000000000000000000000000000000000000000000000000e0", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_empty_array() { + // Empty arrays + let encoded = encode_tokens(&[ + Token::Array { + arr: vec![], + kind: ParamType::Bool, + }, + Token::Array { + arr: vec![], + kind: ParamType::Address, + }, + ]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000060", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + + // Nested empty arrays + let encoded = encode_tokens(&[ + Token::Array { + arr: vec![Token::Array { + arr: vec![], + kind: ParamType::i256(), + }], + kind: ParamType::array(ParamType::i256()), + }, + Token::Array { + arr: vec![Token::Array { + arr: vec![], + kind: ParamType::String, + }], + kind: ParamType::array(ParamType::String), + }, + ]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000040", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_bytes() { + let bytes = Token::Bytes(vec![0x12, 0x34]); + let encoded = encode_tokens(&[bytes]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000002", + "1234000000000000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_fixed_bytes() { + let bytes = Token::FixedBytes(NonEmptyBytes::new(vec![0x12, 0x34]).unwrap()); + let encoded = encode_tokens(&[bytes]); + let expected = "1234000000000000000000000000000000000000000000000000000000000000" + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_string() { + let s = Token::String("gavofyork".to_owned()); + let encoded = encode_tokens(&[s]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_bytes2() { + let bytes = Token::Bytes( + "10000000000000000000000000000000000000000000000000000000000002" + .decode_hex() + .unwrap(), + ); + let encoded = encode_tokens(&[bytes]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "000000000000000000000000000000000000000000000000000000000000001f", + "1000000000000000000000000000000000000000000000000000000000000200", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_bytes3() { + let bytes = Token::Bytes( + "10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap() + ); + let encoded = encode_tokens(&[bytes]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000040", + "1000000000000000000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_two_bytes() { + let bytes1 = Token::Bytes( + "10000000000000000000000000000000000000000000000000000000000002" + .decode_hex() + .unwrap(), + ); + let bytes2 = Token::Bytes( + "0010000000000000000000000000000000000000000000000000000000000002" + .decode_hex() + .unwrap(), + ); + let encoded = encode_tokens(&[bytes1, bytes2]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "000000000000000000000000000000000000000000000000000000000000001f", + "1000000000000000000000000000000000000000000000000000000000000200", + "0000000000000000000000000000000000000000000000000000000000000020", + "0010000000000000000000000000000000000000000000000000000000000002", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_uint() { + let mut uint = H256::default(); + uint[31] = 4; + let encoded = encode_tokens(&[Token::u256(U256::from_big_endian(uint))]); + let expected = "0000000000000000000000000000000000000000000000000000000000000004" + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_int() { + let mut int = H256::default(); + int[31] = 4; + let encoded = encode_tokens(&[Token::i256(I256::from_big_endian(int))]); + let expected = "0000000000000000000000000000000000000000000000000000000000000004" + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_bool() { + let encoded = encode_tokens(&[Token::Bool(true)]); + let expected = "0000000000000000000000000000000000000000000000000000000000000001" + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_bool2() { + let encoded = encode_tokens(&[Token::Bool(false)]); + let expected = "0000000000000000000000000000000000000000000000000000000000000000" + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn comprehensive_test() { + let bytes = concat!( + "131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b", + "131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b", + ) + .decode_hex() + .unwrap(); + let encoded = encode_tokens(&[ + Token::i256(5_u64.into()), + Token::Bytes(bytes.clone()), + Token::i256(3_u64.into()), + Token::Bytes(bytes), + ]); + + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000005", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000003", + "00000000000000000000000000000000000000000000000000000000000000e0", + "0000000000000000000000000000000000000000000000000000000000000040", + "131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b", + "131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b", + "0000000000000000000000000000000000000000000000000000000000000040", + "131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b", + "131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn test_pad_u32() { + // this will fail if endianess is not supported + assert_eq!(pad_u32(0x1)[31], 1); + assert_eq!(pad_u32(0x100)[30], 1); + } + + #[test] + fn comprehensive_test2() { + let encoded = encode_tokens(&vec![ + Token::i256(1_u64.into()), + Token::String("gavofyork".to_owned()), + Token::i256(2_u64.into()), + Token::i256(3_u64.into()), + Token::i256(4_u64.into()), + Token::Array { + arr: vec![ + Token::i256(5_u64.into()), + Token::i256(6_u64.into()), + Token::i256(7_u64.into()), + ], + kind: ParamType::i256(), + }, + ]); + + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000001", + "00000000000000000000000000000000000000000000000000000000000000c0", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000003", + "0000000000000000000000000000000000000000000000000000000000000004", + "0000000000000000000000000000000000000000000000000000000000000100", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003", + "0000000000000000000000000000000000000000000000000000000000000005", + "0000000000000000000000000000000000000000000000000000000000000006", + "0000000000000000000000000000000000000000000000000000000000000007", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_array_of_bytes() { + let bytes = "019c80031b20d5e69c8093a571162299032018d913930d93ab320ae5ea44a4218a274f00d607" + .decode_hex() + .unwrap(); + let encoded = encode_tokens(&[Token::Array { + arr: vec![Token::Bytes(bytes.to_vec())], + kind: ParamType::Bytes, + }]); + + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000026", + "019c80031b20d5e69c8093a571162299032018d913930d93ab320ae5ea44a421", + "8a274f00d6070000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_array_of_bytes2() { + let bytes = "4444444444444444444444444444444444444444444444444444444444444444444444444444" + .decode_hex() + .unwrap(); + let bytes2 = "6666666666666666666666666666666666666666666666666666666666666666666666666666" + .decode_hex() + .unwrap(); + let encoded = encode_tokens(&[Token::Array { + arr: vec![Token::Bytes(bytes.to_vec()), Token::Bytes(bytes2.to_vec())], + kind: ParamType::Bytes, + }]); + + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000040", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000026", + "4444444444444444444444444444444444444444444444444444444444444444", + "4444444444440000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000026", + "6666666666666666666666666666666666666666666666666666666666666666", + "6666666666660000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_static_tuple_of_addresses() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let encoded = encode_tokens(&[Token::Tuple { + params: vec![ + NamedToken::with_token(address1), + NamedToken::with_token(address2), + ], + }]); + + let expected = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_tuple() { + let string1 = Token::String("gavofyork".to_owned()); + let string2 = Token::String("gavofyork".to_owned()); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string1), + NamedToken::with_token(string2), + ], + }; + let encoded = encode_tokens(&[tuple]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_tuple_of_bytes2() { + let bytes = "4444444444444444444444444444444444444444444444444444444444444444444444444444" + .decode_hex() + .unwrap(); + let bytes2 = "6666666666666666666666666666666666666666666666666666666666666666666666666666" + .decode_hex() + .unwrap(); + let encoded = encode_tokens(&[Token::Tuple { + params: vec![ + NamedToken::with_token(Token::Bytes(bytes.to_vec())), + NamedToken::with_token(Token::Bytes(bytes2.to_vec())), + ], + }]); + + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000040", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000026", + "4444444444444444444444444444444444444444444444444444444444444444", + "4444444444440000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000026", + "6666666666666666666666666666666666666666666666666666666666666666", + "6666666666660000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_complex_tuple() { + let uint_data = + H256::from("1111111111111111111111111111111111111111111111111111111111111111"); + let uint = Token::u256(U256::from_big_endian(uint_data)); + let string = Token::String("gavofyork".to_owned()); + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(uint), + NamedToken::with_token(string), + NamedToken::with_token(address1), + NamedToken::with_token(address2), + ], + }; + let encoded = encode_tokens(&[tuple]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "1111111111111111111111111111111111111111111111111111111111111111", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000000000000000000000000000000000000000000009", + "6761766f66796f726b0000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_nested_tuple() { + let string1 = Token::String("test".to_owned()); + let string2 = Token::String("cyborg".to_owned()); + let string3 = Token::String("night".to_owned()); + let string4 = Token::String("day".to_owned()); + let string5 = Token::String("weee".to_owned()); + let string6 = Token::String("funtests".to_owned()); + let bool = Token::Bool(true); + let deep_tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string5), + NamedToken::with_token(string6), + ], + }; + let inner_tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string3), + NamedToken::with_token(string4), + NamedToken::with_token(deep_tuple), + ], + }; + let outer_tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(string1), + NamedToken::with_token(bool), + NamedToken::with_token(string2), + NamedToken::with_token(inner_tuple), + ], + }; + let encoded = encode_tokens(&[outer_tuple]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000001", + "00000000000000000000000000000000000000000000000000000000000000c0", + "0000000000000000000000000000000000000000000000000000000000000100", + "0000000000000000000000000000000000000000000000000000000000000004", + "7465737400000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000006", + "6379626f72670000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000060", + "00000000000000000000000000000000000000000000000000000000000000a0", + "00000000000000000000000000000000000000000000000000000000000000e0", + "0000000000000000000000000000000000000000000000000000000000000005", + "6e69676874000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003", + "6461790000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000004", + "7765656500000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000008", + "66756e7465737473000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_params_containing_dynamic_tuple() { + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let address3 = Token::Address("0x3333333333333333333333333333333333333333".into()); + let address4 = Token::Address("0x4444444444444444444444444444444444444444".into()); + let bool1 = Token::Bool(true); + let string1 = Token::String("spaceship".to_owned()); + let string2 = Token::String("cyborg".to_owned()); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(bool1), + NamedToken::with_token(string1), + NamedToken::with_token(string2), + ], + }; + let bool2 = Token::Bool(false); + let encoded = encode_tokens(&[address2, tuple, address3, address4, bool2]); + let expected = concat!( + "0000000000000000000000002222222222222222222222222222222222222222", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000060", + "00000000000000000000000000000000000000000000000000000000000000a0", + "0000000000000000000000000000000000000000000000000000000000000009", + "7370616365736869700000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000006", + "6379626f72670000000000000000000000000000000000000000000000000000", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_params_containing_static_tuple() { + let address1 = Token::Address("0x1111111111111111111111111111111111111111".into()); + let address2 = Token::Address("0x2222222222222222222222222222222222222222".into()); + let address3 = Token::Address("0x3333333333333333333333333333333333333333".into()); + let address4 = Token::Address("0x4444444444444444444444444444444444444444".into()); + let bool1 = Token::Bool(true); + let bool2 = Token::Bool(false); + let tuple = Token::Tuple { + params: vec![ + NamedToken::with_token(address2), + NamedToken::with_token(bool1), + NamedToken::with_token(bool2), + ], + }; + let encoded = encode_tokens(&[address1, tuple, address3, address4]); + let expected = concat!( + "0000000000000000000000001111111111111111111111111111111111111111", + "0000000000000000000000002222222222222222222222222222222222222222", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000003333333333333333333333333333333333333333", + "0000000000000000000000004444444444444444444444444444444444444444", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } + + #[test] + fn encode_dynamic_tuple_with_nested_static_tuples() { + let uint1 = Token::u256(0x777_u64.into()); + let uint2 = Token::u256(0x42_u64.into()); + let uint3 = Token::u256(0x1337_u64.into()); + let token = { + Token::Tuple { + params: vec![ + NamedToken::with_token(Token::Tuple { + params: vec![NamedToken::with_token(Token::Tuple { + params: vec![ + NamedToken::with_token(Token::Bool(false)), + NamedToken::with_token(uint1), + ], + })], + }), + NamedToken::with_token(Token::Array { + arr: vec![uint2, uint3], + kind: ParamType::u256(), + }), + ], + } + }; + let encoded = encode_tokens(&[token]); + let expected = concat!( + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000777", + "0000000000000000000000000000000000000000000000000000000000000060", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000042", + "0000000000000000000000000000000000000000000000000000000000001337", + ) + .decode_hex() + .unwrap(); + assert_eq!(encoded, expected); + } +} diff --git a/rust/tw_evm/src/abi/function.rs b/rust/tw_evm/src/abi/function.rs new file mode 100644 index 00000000000..cae72467a02 --- /dev/null +++ b/rust/tw_evm/src/abi/function.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::decode::decode_params; +use crate::abi::encode::encode_tokens; +use crate::abi::param::Param; +use crate::abi::param_token::NamedToken; +use crate::abi::signature::short_signature; +use crate::abi::token::Token; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use itertools::Itertools; +use serde::Deserialize; +use tw_memory::Data; + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct Function { + /// Function name. + pub name: String, + /// Function input. + pub inputs: Vec, + /// Function output. + pub outputs: Vec, +} + +impl Function { + /// Returns a signature that uniquely identifies this function. + /// + /// Examples: + /// - `functionName()` + /// - `functionName():(uint256)` + /// - `functionName(bool):(uint256,string)` + /// - `functionName(uint256,bytes32):(string,uint256)` + pub fn signature(&self) -> String { + let inputs = self.inputs.iter().map(|p| p.kind.to_type_long()).join(","); + + let outputs = self.outputs.iter().map(|p| p.kind.to_type_long()).join(","); + + match (inputs.len(), outputs.len()) { + (_, 0) => format!("{}({inputs})", self.name), + (_, _) => format!("{}({inputs}):({outputs})", self.name), + } + } + + /// Parses the ABI function input to a list of tokens. + pub fn decode_input(&self, data: &[u8]) -> AbiResult> { + decode_params(&self.inputs, data) + } + + /// Encodes function input to Eth ABI binary. + pub fn encode_input(&self, tokens: &[Token]) -> AbiResult { + // Check if the given tokens match `Self::inputs` ABI. + let input_param_types: Vec<_> = + self.inputs.iter().map(|param| param.kind.clone()).collect(); + for (token, kind) in tokens.iter().zip(input_param_types.iter()) { + if token.to_param_type() != *kind { + return Err(AbiError(AbiErrorKind::Error_abi_mismatch)); + } + } + + let signed = short_signature(&self.name, &input_param_types); + let encoded = encode_tokens(tokens); + Ok(signed.into_iter().chain(encoded.into_iter()).collect()) + } +} diff --git a/rust/tw_evm/src/abi/mod.rs b/rust/tw_evm/src/abi/mod.rs new file mode 100644 index 00000000000..25b36b9f936 --- /dev/null +++ b/rust/tw_evm/src/abi/mod.rs @@ -0,0 +1,49 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::{SigningError, SigningErrorType}; + +pub mod contract; +pub mod decode; +pub mod encode; +pub mod function; +pub mod non_empty_array; +pub mod param; +pub mod param_token; +pub mod param_type; +pub mod prebuild; +pub mod signature; +pub mod token; +pub mod uint; + +#[macro_export] +macro_rules! abi_output_error { + ($output:ty, $error:expr) => {{ + let err = $error; + + let mut output = <$output>::default(); + output.error = err.0; + output.error_message = std::borrow::Cow::from(format!("{err:?}")); + + output + }}; +} + +pub type AbiResult = Result; +pub type AbiErrorKind = tw_proto::EthereumAbi::Proto::AbiError; + +#[derive(Debug)] +pub struct AbiError(pub AbiErrorKind); + +impl From for SigningError { + fn from(err: AbiError) -> Self { + match err.0 { + AbiErrorKind::OK => SigningError(SigningErrorType::OK), + AbiErrorKind::Error_internal => SigningError(SigningErrorType::Error_internal), + _ => SigningError(SigningErrorType::Error_invalid_params), + } + } +} diff --git a/rust/tw_evm/src/abi/non_empty_array.rs b/rust/tw_evm/src/abi/non_empty_array.rs new file mode 100644 index 00000000000..b6a7a535c28 --- /dev/null +++ b/rust/tw_evm/src/abi/non_empty_array.rs @@ -0,0 +1,83 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use core::fmt; +use std::num::NonZeroUsize; +use std::ops::Deref; + +pub type NonEmptyBytes = NonEmptyArray; + +/// A convenient wrapper over `NonZeroUsize`. +#[derive(Copy, Clone, PartialEq)] +pub struct NonZeroLen(NonZeroUsize); + +impl NonZeroLen { + pub fn new(len: usize) -> AbiResult { + NonZeroUsize::new(len) + .ok_or(AbiError(AbiErrorKind::Error_invalid_param_type)) + .map(NonZeroLen) + } + + pub fn get(&self) -> usize { + self.0.get() + } +} + +impl fmt::Debug for NonZeroLen { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl fmt::Display for NonZeroLen { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.get()) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct NonEmptyArray(Vec); + +impl NonEmptyArray { + pub fn new(elements: Vec) -> AbiResult> { + if elements.is_empty() { + return Err(AbiError(AbiErrorKind::Error_empty_type)); + } + Ok(NonEmptyArray(elements)) + } + + pub fn len(&self) -> NonZeroLen { + NonZeroLen::new(self.0.len()).expect("`NonEmptyArray` must have at least one element") + } + + pub fn into_vec(self) -> Vec { + self.0 + } +} + +impl IntoIterator for NonEmptyArray { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Deref for NonEmptyArray { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for NonEmptyArray { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} diff --git a/rust/tw_evm/src/abi/param.rs b/rust/tw_evm/src/abi/param.rs new file mode 100644 index 00000000000..93ca5dcb3eb --- /dev/null +++ b/rust/tw_evm/src/abi/param.rs @@ -0,0 +1,124 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::param_type::ParamType; +use serde::de::{MapAccess, Visitor}; +use serde::{de::Error as DeError, Deserialize, Deserializer}; +use std::fmt::Formatter; + +#[derive(Clone, Debug, PartialEq)] +pub struct Param { + /// Param name. + pub name: Option, + /// Param type. + pub kind: ParamType, + /// Additional Internal type. + pub internal_type: Option, +} + +impl Param { + /// Should be used in tests only. + #[cfg(test)] + pub(crate) fn with_type(kind: ParamType) -> Param { + Param { + name: None, + kind, + internal_type: None, + } + } +} + +impl<'de> Deserialize<'de> for Param { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(ParamVisitor) + } +} + +struct ParamVisitor; + +impl<'a> Visitor<'a> for ParamVisitor { + type Value = Param; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "a valid event parameter spec") + } + + // The method implementation is inspired by + // https://github.com/rust-ethereum/ethabi/blob/b1710adc18f5b771d2d2519c87248b1ba9430778/ethabi/src/param.rs#L59-L103 + // + // The only difference is that tuple parameters have names. + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'a>, + { + macro_rules! handle_map_key { + ($key_name:literal, $value:expr) => {{ + if $value.is_some() { + return Err(DeError::duplicate_field($key_name)); + } + $value = Some(map.next_value()?); + }}; + } + + let mut name = None; + let mut kind = None; + let mut internal_type = None; + let mut components = None; + + while let Some(ref key) = map.next_key::()? { + match key.as_ref() { + "name" => handle_map_key!("name", name), + "type" => handle_map_key!("kind", kind), + "internalType" => handle_map_key!("internalType", internal_type), + "components" => handle_map_key!("components", components), + // Skip unknown field. + _ => {}, + } + } + + let mut kind = kind.ok_or_else(|| DeError::missing_field("kind"))?; + set_tuple_components::(&mut kind, components)?; + + Ok(Param { + name, + kind, + internal_type, + }) + } +} + +/// Set tuple components if: +/// 1) `kind` is `tuple` or `tuple[]` or `tuple[][]`, etc. +/// 2) `components` is `Some`. +fn set_tuple_components( + kind: &mut ParamType, + components: Option>, +) -> Result<(), E> { + if let Some(tuple_components) = inner_tuple_mut(kind) { + *tuple_components = components.ok_or_else(|| E::missing_field("components"))?; + if tuple_components.is_empty() { + return Err(DeError::custom("'components' cannot be empty")); + } + } + Ok(()) +} + +/// Returns a mutable reference to the tuple components if `param` is `tuple` or `tuple[]` or `tuple[][]`, etc. +fn inner_tuple_mut(mut param: &mut ParamType) -> Option<&mut Vec> { + loop { + match param { + ParamType::Array { kind: elem_kind } => param = elem_kind.as_mut(), + ParamType::FixedArray { + kind: elem_kind, .. + } => param = elem_kind.as_mut(), + ParamType::Tuple { params } => return Some(params), + _ => return None, + } + } +} diff --git a/rust/tw_evm/src/abi/param_token.rs b/rust/tw_evm/src/abi/param_token.rs new file mode 100644 index 00000000000..420b8e6f217 --- /dev/null +++ b/rust/tw_evm/src/abi/param_token.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::param::Param; +use crate::abi::token::Token; +use serde::ser::SerializeMap; +use serde::{Serialize, Serializer}; + +#[derive(Clone, Debug, PartialEq)] +pub struct NamedToken { + /// Optional param name. + pub name: Option, + /// Parameter value. + pub value: Token, + /// Additional Internal type. + pub internal_type: Option, +} + +impl NamedToken { + pub fn with_param_and_token(param: &Param, value: Token) -> NamedToken { + NamedToken { + name: param.name.clone(), + value, + internal_type: param.internal_type.clone(), + } + } + + pub fn to_param(&self) -> Param { + Param { + name: self.name.clone(), + kind: self.value.to_param_type(), + internal_type: self.internal_type.clone(), + } + } + + #[cfg(test)] + pub fn with_token(value: Token) -> NamedToken { + NamedToken { + name: None, + value, + internal_type: None, + } + } +} + +impl Serialize for NamedToken { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + const NUMBER_OF_ENTRIES: usize = 3; + + let mut serde_struct = serializer.serialize_map(Some(NUMBER_OF_ENTRIES))?; + serde_struct.serialize_entry("name", &self.name)?; + serde_struct.serialize_entry("type", &self.value.type_short())?; + + if self.value.to_param_type().has_tuple_components() { + serde_struct.serialize_entry("components", &self.value)?; + } else { + serde_struct.serialize_entry("value", &self.value)?; + } + serde_struct.end() + } +} diff --git a/rust/tw_evm/src/abi/param_type/constructor.rs b/rust/tw_evm/src/abi/param_type/constructor.rs new file mode 100644 index 00000000000..37eb5d2d39a --- /dev/null +++ b/rust/tw_evm/src/abi/param_type/constructor.rs @@ -0,0 +1,107 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::non_empty_array::NonZeroLen; +use crate::abi::param_type::ParamType; +use crate::abi::uint::UintBits; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; + +pub trait TypeConstructor: Sized { + fn address() -> Self; + + fn bytes() -> Self; + + fn fixed_bytes(len: usize) -> AbiResult { + let checked_len = NonZeroLen::new(len)?; + Ok(Self::fixed_bytes_checked(checked_len)) + } + + fn fixed_bytes_checked(len: NonZeroLen) -> Self; + + fn int(bits: usize) -> AbiResult { + let checked_bits = UintBits::new(bits)?; + Ok(Self::int_checked(checked_bits)) + } + + fn int_checked(bits: UintBits) -> Self; + + fn uint(bits: usize) -> AbiResult { + let checked_bits = UintBits::new(bits)?; + Ok(Self::uint_checked(checked_bits)) + } + + fn uint_checked(bits: UintBits) -> Self; + + fn bool() -> Self; + + fn string() -> Self; + + fn array(element_type: Self) -> Self; + + fn fixed_array(len: usize, element_type: Self) -> AbiResult { + let checked_len = NonZeroLen::new(len)?; + Ok(Self::fixed_array_checked(checked_len, element_type)) + } + + fn fixed_array_checked(len: NonZeroLen, element_type: Self) -> Self; + + fn empty_tuple() -> AbiResult; + + fn custom(s: &str) -> AbiResult; +} + +impl TypeConstructor for ParamType { + fn address() -> Self { + ParamType::Address + } + + fn bytes() -> Self { + ParamType::Bytes + } + + fn fixed_bytes_checked(len: NonZeroLen) -> Self { + ParamType::FixedBytes { len } + } + + fn int_checked(bits: UintBits) -> Self { + ParamType::Int { bits } + } + + fn uint_checked(bits: UintBits) -> Self { + ParamType::Uint { bits } + } + + fn bool() -> Self { + ParamType::Bool + } + + fn string() -> Self { + ParamType::String + } + + fn array(element_type: Self) -> Self { + ParamType::Array { + kind: Box::new(element_type), + } + } + + fn fixed_array_checked(len: NonZeroLen, element_type: Self) -> Self { + ParamType::FixedArray { + kind: Box::new(element_type), + len, + } + } + + fn empty_tuple() -> AbiResult { + Ok(ParamType::Tuple { + params: Vec::default(), + }) + } + + fn custom(_s: &str) -> AbiResult { + Err(AbiError(AbiErrorKind::Error_invalid_param_type)) + } +} diff --git a/rust/tw_evm/src/abi/param_type/mod.rs b/rust/tw_evm/src/abi/param_type/mod.rs new file mode 100644 index 00000000000..1ca70655574 --- /dev/null +++ b/rust/tw_evm/src/abi/param_type/mod.rs @@ -0,0 +1,137 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::non_empty_array::NonZeroLen; +use crate::abi::param::Param; +use crate::abi::uint::UintBits; +use crate::abi::AbiResult; +use serde::{de::Error as DeError, Deserialize, Deserializer}; + +pub mod constructor; +pub mod reader; +pub mod writer; + +use reader::Reader; +use writer::Writer; + +#[derive(Clone, Debug, PartialEq)] +pub enum ParamType { + /// Address. + /// + /// solidity name: address + /// Encoded to left padded [0u8; 32]. + Address, + /// Vector of bytes with known size. + /// + /// solidity name eg.: bytes8, bytes32, bytes64, bytes1024 + /// Encoded to right padded [0u8; ((N + 31) / 32) * 32]. + FixedBytes { len: NonZeroLen }, + /// Vector of bytes of unknown size. + /// + /// solidity name: bytes + /// Encoded in two parts. + /// Init part: offset of 'closing part`. + /// Closing part: encoded length followed by encoded right padded bytes. + Bytes, + /// Signed integer. + /// + /// solidity name: int + Int { bits: UintBits }, + /// Unsigned integer. + /// + /// solidity name: uint + Uint { bits: UintBits }, + /// Boolean value. + /// + /// solidity name: bool + /// Encoded as left padded [0u8; 32], where last bit represents boolean value. + Bool, + /// String. + /// + /// solidity name: string + /// Encoded in the same way as bytes. Must be utf8 compliant. + String, + /// Array with known size. + /// + /// solidity name eg.: int[3], bool[3], address[][8] + /// Encoding of array is equal to encoding of consecutive elements of array. + FixedArray { + kind: Box, + len: NonZeroLen, + }, + /// Array of params with unknown size. + /// + /// solidity name eg. int[], bool[], address[5][] + Array { kind: Box }, + /// Tuple of params of variable types. + /// + /// solidity name: tuple + Tuple { params: Vec }, +} + +impl ParamType { + pub fn i256() -> ParamType { + ParamType::Int { + bits: UintBits::default(), + } + } + + pub fn u256() -> ParamType { + ParamType::Uint { + bits: UintBits::default(), + } + } + + /// Tuples will be represented as list of inner types in parens, for example `(int256,bool)`. + pub fn to_type_long(&self) -> String { + let serialize_tuple_contents = true; + Writer::write_for_abi(self, serialize_tuple_contents) + } + + /// Tuples will be represented as keyword `tuple`. + pub fn to_type_short(&self) -> String { + let serialize_tuple_contents = false; + Writer::write_for_abi(self, serialize_tuple_contents) + } + + /// returns whether a ParamType is dynamic + /// used to decide how the ParamType should be encoded + pub fn is_dynamic(&self) -> bool { + match self { + ParamType::Bytes | ParamType::String | ParamType::Array { .. } => true, + ParamType::FixedArray { kind, .. } => kind.is_dynamic(), + ParamType::Tuple { params } => params.iter().any(|param| param.kind.is_dynamic()), + _ => false, + } + } +} + +impl ParamType { + pub fn try_from_type_short(type_short: &str) -> AbiResult { + Reader::parse_type(type_short) + } + + pub(crate) fn has_tuple_components(&self) -> bool { + let mut inner_type = self; + loop { + match inner_type { + ParamType::Array { kind } | ParamType::FixedArray { kind, .. } => inner_type = kind, + ParamType::Tuple { .. } => return true, + _ => return false, + } + } + } +} + +impl<'de> Deserialize<'de> for ParamType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let identifier = String::deserialize(deserializer)?; + ParamType::try_from_type_short(&identifier).map_err(|e| DeError::custom(format!("{e:?}"))) + } +} diff --git a/rust/tw_evm/src/abi/param_type/reader.rs b/rust/tw_evm/src/abi/param_type/reader.rs new file mode 100644 index 00000000000..1e084fbec6c --- /dev/null +++ b/rust/tw_evm/src/abi/param_type/reader.rs @@ -0,0 +1,103 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::non_empty_array::NonZeroLen; +use crate::abi::param_type::constructor::TypeConstructor; +use crate::abi::uint::UintBits; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use std::str::FromStr; + +pub struct Reader; + +impl Reader { + /// Doesn't accept tuple types with specified parameters, e.g `(uint32, address)`. + pub fn parse_type(s: &str) -> AbiResult { + // Array + if let Some(remaining) = s.strip_suffix(']') { + let Some((element_type_str, len_str)) = remaining.rsplit_once('[') else { + return Err(AbiError(AbiErrorKind::Error_invalid_param_type)); + }; + + let element_type = Reader::parse_type::(element_type_str)?; + if let Some(len) = parse_len(len_str)? { + return Ok(T::fixed_array_checked(len, element_type)); + } + return Ok(T::array(element_type)); + } + + let all_alphanumeric = s.chars().all(|ch| ch.is_ascii_alphanumeric()); + if s.is_empty() || !all_alphanumeric { + return Err(AbiError(AbiErrorKind::Error_invalid_param_type)); + } + + if s.contains(['[', ']']) { + return Err(AbiError(AbiErrorKind::Error_invalid_param_type)); + } + + // uint, uint32, ... + if let Some(len_str) = s.strip_prefix("uint") { + let bits = parse_uint_bits(len_str)?.unwrap_or_default(); + return Ok(T::uint_checked(bits)); + } + + // int, int32, ... + if let Some(len_str) = s.strip_prefix("int") { + let bits = parse_uint_bits(len_str)?.unwrap_or_default(); + return Ok(T::int_checked(bits)); + } + + // bytes, bytes32, ... + if let Some(len_str) = s.strip_prefix("bytes") { + if let Some(len) = parse_len(len_str)? { + // Fixed-len bytes. + return Ok(T::fixed_bytes_checked(len)); + } + // Otherwise, dynamic-len bytes. + return Ok(T::bytes()); + } + + // Handle other types. + match s { + "address" => Ok(T::address()), + "bool" => Ok(T::bool()), + "string" => Ok(T::string()), + "tuple" => T::empty_tuple(), + custom => T::custom(custom), + } + } +} + +fn parse_len(len_str: &str) -> AbiResult> { + match parse_usize(len_str)? { + Some(u) => { + let len = NonZeroLen::new(u)?; + Ok(Some(len)) + }, + None => Ok(None), + } +} + +fn parse_uint_bits(bits_str: &str) -> AbiResult> { + match parse_usize(bits_str)? { + Some(u) => { + let bits = UintBits::new(u)?; + Ok(Some(bits)) + }, + None => Ok(None), + } +} + +fn parse_usize(usize_str: &str) -> AbiResult> { + if usize_str.is_empty() { + return Ok(None); + } + if usize_str.starts_with('0') { + return Err(AbiError(AbiErrorKind::Error_invalid_param_type)); + } + usize::from_str(usize_str) + .map(Some) + .map_err(|_| AbiError(AbiErrorKind::Error_invalid_param_type)) +} diff --git a/rust/tw_evm/src/abi/param_type/writer.rs b/rust/tw_evm/src/abi/param_type/writer.rs new file mode 100644 index 00000000000..724a5ed9abe --- /dev/null +++ b/rust/tw_evm/src/abi/param_type/writer.rs @@ -0,0 +1,116 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::param_type::ParamType; + +/// Output formatter for param type. +pub struct Writer; + +impl Writer { + /// Returns string which is a formatted represenation of param. + pub fn write(param: &ParamType) -> String { + Writer::write_for_abi(param, true) + } + + /// If `serialize_tuple_contents` is `true`, tuples will be represented + /// as list of inner types in parens, for example `(int256,bool)`. + /// If it is `false`, tuples will be represented as keyword `tuple`. + pub fn write_for_abi(param: &ParamType, serialize_tuple_contents: bool) -> String { + match param { + ParamType::Address => "address".to_owned(), + ParamType::Bytes => "bytes".to_owned(), + ParamType::FixedBytes { len } => format!("bytes{len}"), + ParamType::Int { bits } => format!("int{bits}"), + ParamType::Uint { bits } => format!("uint{bits}"), + ParamType::Bool => "bool".to_owned(), + ParamType::String => "string".to_owned(), + ParamType::FixedArray { kind, len } => { + format!( + "{}[{len}]", + Writer::write_for_abi(kind, serialize_tuple_contents) + ) + }, + ParamType::Array { ref kind } => { + format!( + "{}[]", + Writer::write_for_abi(kind, serialize_tuple_contents) + ) + }, + ParamType::Tuple { ref params } => { + if serialize_tuple_contents { + let formatted = params + .iter() + .map(|t| Writer::write_for_abi(&t.kind, serialize_tuple_contents)) + .collect::>() + .join(","); + format!("({formatted})") + } else { + "tuple".to_owned() + } + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::abi::non_empty_array::NonZeroLen; + use crate::abi::param::Param; + use crate::abi::param_type::constructor::TypeConstructor; + + #[test] + fn test_write_param() { + assert_eq!(Writer::write(&ParamType::Address), "address"); + assert_eq!(Writer::write(&ParamType::Bytes), "bytes"); + assert_eq!( + Writer::write(&ParamType::FixedBytes { + len: NonZeroLen::new(32).unwrap() + }), + "bytes32" + ); + assert_eq!(Writer::write(&ParamType::u256()), "uint256"); + assert_eq!(Writer::write(&ParamType::int(64).unwrap()), "int64"); + assert_eq!(Writer::write(&ParamType::Bool), "bool"); + assert_eq!(Writer::write(&ParamType::String), "string"); + assert_eq!(Writer::write(&ParamType::array(ParamType::Bool)), "bool[]"); + assert_eq!( + Writer::write(&ParamType::fixed_array(2, ParamType::String).unwrap()), + "string[2]" + ); + assert_eq!( + Writer::write(&ParamType::fixed_array(2, ParamType::array(ParamType::Bool)).unwrap()), + "bool[][2]" + ); + assert_eq!( + Writer::write(&ParamType::array(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::array(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::int(256).unwrap()), + Param::with_type(ParamType::uint(256).unwrap()) + ] + })), + Param::with_type(ParamType::fixed_bytes(32).unwrap()), + ] + })), + "((int256,uint256)[],bytes32)[]" + ); + + assert_eq!( + Writer::write_for_abi( + &ParamType::array(ParamType::Tuple { + params: vec![ + Param::with_type(ParamType::array(ParamType::i256())), + Param::with_type(ParamType::fixed_bytes(32).unwrap()), + ] + }), + false + ), + "tuple[]" + ); + } +} diff --git a/rust/tw_evm/src/abi/prebuild/erc1155.rs b/rust/tw_evm/src/abi/prebuild/erc1155.rs new file mode 100644 index 00000000000..1a9bf391f98 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/erc1155.rs @@ -0,0 +1,42 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::contract::Contract; +use crate::abi::token::Token; +use crate::abi::AbiResult; +use crate::address::Address; +use lazy_static::lazy_static; +use tw_memory::Data; +use tw_number::U256; + +/// Generated via https://remix.ethereum.org +/// Solidity: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.2/contracts/token/ERC1155/IERC1155.sol +const ERC1155_ABI: &str = include_str!("resource/erc1155.abi.json"); + +lazy_static! { + static ref ERC1155: Contract = serde_json::from_str(ERC1155_ABI).unwrap(); +} + +pub struct Erc1155; + +impl Erc1155 { + pub fn encode_safe_transfer_from( + from: Address, + to: Address, + token_id: U256, + value: U256, + data: Data, + ) -> AbiResult { + let func = ERC1155.function("safeTransferFrom")?; + func.encode_input(&[ + Token::Address(from), + Token::Address(to), + Token::u256(token_id), + Token::u256(value), + Token::Bytes(data), + ]) + } +} diff --git a/rust/tw_evm/src/abi/prebuild/erc20.rs b/rust/tw_evm/src/abi/prebuild/erc20.rs new file mode 100644 index 00000000000..4d3d042d663 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/erc20.rs @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::contract::Contract; +use crate::abi::token::Token; +use crate::abi::AbiResult; +use crate::address::Address; +use lazy_static::lazy_static; +use tw_memory::Data; +use tw_number::U256; + +/// Generated via https://remix.ethereum.org +/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.2/contracts/token/ERC20/IERC20.sol +const ERC20_ABI: &str = include_str!("resource/erc20.abi.json"); + +lazy_static! { + static ref ERC20: Contract = serde_json::from_str(ERC20_ABI).unwrap(); +} + +pub struct Erc20; + +impl Erc20 { + pub fn transfer(recipient: Address, amount: U256) -> AbiResult { + let func = ERC20.function("transfer")?; + func.encode_input(&[Token::Address(recipient), Token::u256(amount)]) + } + + pub fn approve(spender: Address, amount: U256) -> AbiResult { + let func = ERC20.function("approve")?; + func.encode_input(&[Token::Address(spender), Token::u256(amount)]) + } +} diff --git a/rust/tw_evm/src/abi/prebuild/erc4337.rs b/rust/tw_evm/src/abi/prebuild/erc4337.rs new file mode 100644 index 00000000000..bd3a3977204 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/erc4337.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::contract::Contract; +use crate::abi::param_type::ParamType; +use crate::abi::token::Token; +use crate::abi::AbiResult; +use crate::address::Address; +use lazy_static::lazy_static; +use tw_memory::Data; +use tw_number::U256; + +/// Generated via https://remix.ethereum.org +/// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol +const ERC4337_SIMPLE_ACCOUNT_ABI: &str = include_str!("resource/erc4337.simple_account.abi.json"); + +lazy_static! { + static ref ERC4337_SIMPLE_ACCOUNT: Contract = + serde_json::from_str(ERC4337_SIMPLE_ACCOUNT_ABI).unwrap(); +} + +pub struct ExecuteArgs { + pub to: Address, + pub value: U256, + pub data: Data, +} + +pub struct Erc4337SimpleAccount; + +impl Erc4337SimpleAccount { + pub fn encode_execute(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_SIMPLE_ACCOUNT.function("execute")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + + pub fn encode_execute_batch(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_SIMPLE_ACCOUNT.function("executeBatch")?; + + let args = args.into_iter(); + let capacity = { + let (lower, upper) = args.size_hint(); + upper.unwrap_or(lower) + }; + + let mut addresses = Vec::with_capacity(capacity); + let mut values = Vec::with_capacity(capacity); + let mut datas = Vec::with_capacity(capacity); + + for arg in args { + addresses.push(Token::Address(arg.to)); + values.push(Token::u256(arg.value)); + datas.push(Token::Bytes(arg.data)); + } + + func.encode_input(&[ + Token::array(ParamType::Address, addresses), + Token::array(ParamType::u256(), values), + Token::array(ParamType::Bytes, datas), + ]) + } +} diff --git a/rust/tw_evm/src/abi/prebuild/erc721.rs b/rust/tw_evm/src/abi/prebuild/erc721.rs new file mode 100644 index 00000000000..e58ac7d0c1b --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/erc721.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::contract::Contract; +use crate::abi::token::Token; +use crate::abi::AbiResult; +use crate::address::Address; +use lazy_static::lazy_static; +use tw_memory::Data; +use tw_number::U256; + +/// Generated via https://remix.ethereum.org +/// Solidity: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.2/contracts/token/ERC721/IERC721.sol +const ERC721_ABI: &str = include_str!("resource/erc721.abi.json"); + +lazy_static! { + static ref ERC721: Contract = serde_json::from_str(ERC721_ABI).unwrap(); +} + +pub struct Erc721; + +impl Erc721 { + pub fn encode_transfer_from(from: Address, to: Address, token_id: U256) -> AbiResult { + let func = ERC721.function("transferFrom")?; + func.encode_input(&[ + Token::Address(from), + Token::Address(to), + Token::u256(token_id), + ]) + } +} diff --git a/rust/tw_evm/src/abi/prebuild/mod.rs b/rust/tw_evm/src/abi/prebuild/mod.rs new file mode 100644 index 00000000000..4bbcf59535b --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod erc1155; +pub mod erc20; +pub mod erc4337; +pub mod erc721; diff --git a/rust/tw_evm/src/abi/prebuild/resource/erc1155.abi.json b/rust/tw_evm/src/abi/prebuild/resource/erc1155.abi.json new file mode 100644 index 00000000000..db16ea84248 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/erc1155.abi.json @@ -0,0 +1,295 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/abi/prebuild/resource/erc20.abi.json b/rust/tw_evm/src/abi/prebuild/resource/erc20.abi.json new file mode 100644 index 00000000000..06e0d938333 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/erc20.abi.json @@ -0,0 +1,185 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/abi/prebuild/resource/erc4337.simple_account.abi.json b/rust/tw_evm/src/abi/prebuild/resource/erc4337.simple_account.abi.json new file mode 100644 index 00000000000..c965a66a789 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/erc4337.simple_account.abi.json @@ -0,0 +1,529 @@ +[ + { + "inputs": [ + { + "internalType": "contract IEntryPoint", + "name": "anEntryPoint", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IEntryPoint", + "name": "entryPoint", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "SimpleAccountInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "addDeposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "entryPoint", + "outputs": [ + { + "internalType": "contract IEntryPoint", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dest", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "func", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "dest", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "value", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "func", + "type": "bytes[]" + } + ], + "name": "executeBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "anOwner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "tokensReceived", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "callGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "verificationGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct UserOperation", + "name": "userOp", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "userOpHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "missingAccountFunds", + "type": "uint256" + } + ], + "name": "validateUserOp", + "outputs": [ + { + "internalType": "uint256", + "name": "validationData", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "withdrawAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawDepositTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/abi/prebuild/resource/erc721.abi.json b/rust/tw_evm/src/abi/prebuild/resource/erc721.abi.json new file mode 100644 index 00000000000..d0526f5a9d5 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/erc721.abi.json @@ -0,0 +1,287 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/abi/signature.rs b/rust/tw_evm/src/abi/signature.rs new file mode 100644 index 00000000000..1cc9da4750f --- /dev/null +++ b/rust/tw_evm/src/abi/signature.rs @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::param_type::ParamType; +use tw_hash::sha3::keccak256; +use tw_hash::{H256, H32}; + +/// Returns the first four bytes of the Keccak-256 hash of the signature of the given params. +pub fn short_signature(name: &str, params: &[ParamType]) -> H32 { + let mut result = H32::default(); + fill_signature(name, params, result.as_mut()); + result +} + +/// Returns the full Keccak-256 hash of the signature of the given params. +pub fn long_signature(name: &str, params: &[ParamType]) -> H256 { + let mut result = H256::default(); + fill_signature(name, params, result.as_mut()); + result +} + +fn fill_signature(name: &str, params: &[ParamType], result: &mut [u8]) { + let types = params + .iter() + .map(ParamType::to_type_long) + .collect::>() + .join(","); + + let data: Vec = From::from(format!("{name}({types})").as_str()); + + result.copy_from_slice(&keccak256(&data)[..result.len()]) +} diff --git a/rust/tw_evm/src/abi/token.rs b/rust/tw_evm/src/abi/token.rs new file mode 100644 index 00000000000..3d52d080d98 --- /dev/null +++ b/rust/tw_evm/src/abi/token.rs @@ -0,0 +1,221 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::non_empty_array::{NonEmptyArray, NonEmptyBytes}; +use crate::abi::param_token::NamedToken; +use crate::abi::param_type::ParamType; +use crate::abi::uint::UintBits; +use crate::abi::AbiResult; +use crate::address::Address; +use serde::{Serialize, Serializer}; +use std::fmt; +use tw_encoding::hex::ToHex; +use tw_memory::Data; +use tw_number::{I256, U256}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + /// Address. + /// + /// solidity name: address + /// Encoded to left padded [0u8; 32]. + Address(Address), + /// Vector of bytes with known size. + /// + /// solidity name eg.: bytes8, bytes32, bytes64, bytes1024 + /// Encoded to right padded [0u8; ((N + 31) / 32) * 32]. + FixedBytes(NonEmptyBytes), + /// Vector of bytes of unknown size. + /// + /// solidity name: bytes + /// Encoded in two parts. + /// Init part: offset of 'closing part`. + /// Closing part: encoded length followed by encoded right padded bytes. + Bytes(Data), + /// Signed integer. + /// + /// solidity name: int + Int { int: I256, bits: UintBits }, + /// Unsigned integer. + /// + /// solidity name: uint + Uint { uint: U256, bits: UintBits }, + /// Boolean value. + /// + /// solidity name: bool + /// Encoded as left padded [0u8; 32], where last bit represents boolean value. + Bool(bool), + /// String. + /// + /// solidity name: string + /// Encoded in the same way as bytes. Must be utf8 compliant. + String(String), + /// Array with known size. + /// + /// solidity name eg.: int[3], bool[3], address[][8] + /// Encoding of array is equal to encoding of consecutive elements of array. + FixedArray { + arr: NonEmptyArray, + kind: ParamType, + }, + /// Array of params with unknown size. + /// + /// solidity name eg. int[], bool[], address[5][] + Array { arr: Vec, kind: ParamType }, + /// Tuple of params of variable types. + /// + /// solidity name: tuple + Tuple { params: Vec }, +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// Formats the given element of a tuple or array. + fn format_sequence_token(token: &Token) -> String { + // Check if the parameter value should be quoted. + match token { + Token::Address(_) | Token::FixedBytes(_) | Token::Bytes(_) | Token::String(_) => { + format!(r#""{token}""#) + }, + _ => format!("{token}"), + } + } + + match self { + Token::Address(addr) => write!(f, "{addr}"), + Token::Bytes(bytes) => { + write!(f, "{}", bytes.to_hex_prefixed()) + }, + Token::FixedBytes(bytes) => { + write!(f, "{}", bytes.to_hex_prefixed()) + }, + Token::Int { int, .. } => write!(f, "{int}"), + Token::Uint { uint, .. } => write!(f, "{uint}"), + Token::Bool(bool) => write!(f, "{bool}"), + Token::String(str) => write!(f, "{str}"), + Token::Array { arr, .. } => { + let s = arr + .iter() + .map(format_sequence_token) + .collect::>() + .join(","); + write!(f, "[{s}]") + }, + Token::FixedArray { arr, .. } => { + let s = arr + .iter() + .map(format_sequence_token) + .collect::>() + .join(","); + write!(f, "[{s}]") + }, + Token::Tuple { params } => { + let s = params + .iter() + .map(|param| format_sequence_token(¶m.value)) + .collect::>() + .join(","); + write!(f, "({s})") + }, + } + } +} + +impl Serialize for Token { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Token::Address(addr) => addr.serialize(serializer), + Token::FixedBytes(bytes) => bytes.to_hex_prefixed().serialize(serializer), + Token::Bytes(bytes) => bytes.to_hex_prefixed().serialize(serializer), + Token::Int { int, .. } => int.as_decimal_str(serializer), + Token::Uint { uint, .. } => uint.as_decimal_str(serializer), + Token::Bool(bool) => bool.serialize(serializer), + Token::String(str) => str.serialize(serializer), + Token::FixedArray { arr, .. } => arr.serialize(serializer), + Token::Array { arr, .. } => arr.serialize(serializer), + Token::Tuple { params } => params.serialize(serializer), + } + } +} + +impl Token { + pub fn u256(uint: U256) -> Token { + Token::Uint { + bits: UintBits::default(), + uint, + } + } + + pub fn i256(int: I256) -> Token { + Token::Int { + bits: UintBits::default(), + int, + } + } + + pub fn uint>(bits: usize, uint: UInt) -> AbiResult { + let bits = UintBits::new(bits)?; + Ok(Token::Uint { + uint: uint.into(), + bits, + }) + } + + pub fn int>(bits: usize, int: Int) -> AbiResult { + let bits = UintBits::new(bits)?; + Ok(Token::Int { + int: int.into(), + bits, + }) + } + + pub fn array(element_kind: ParamType, elements: Vec) -> Token { + Token::Array { + kind: element_kind, + arr: elements, + } + } + + pub fn type_short(&self) -> String { + self.to_param_type().to_type_short() + } + + /// Check if the token is a dynamic type resulting in prefixed encoding. + pub fn is_dynamic(&self) -> bool { + match self { + Token::Bytes(_) | Token::String(_) | Token::Array { .. } => true, + Token::FixedArray { arr, .. } => arr.iter().any(|token| token.is_dynamic()), + Token::Tuple { params } => params.iter().any(|token| token.value.is_dynamic()), + _ => false, + } + } + + pub(crate) fn to_param_type(&self) -> ParamType { + match self { + Token::Address(_) => ParamType::Address, + Token::Bytes(_) => ParamType::Bytes, + Token::Int { bits, .. } => ParamType::Int { bits: *bits }, + Token::Uint { bits, .. } => ParamType::Uint { bits: *bits }, + Token::Bool(_) => ParamType::Bool, + Token::String(_) => ParamType::String, + Token::Array { kind, .. } => ParamType::Array { + kind: Box::new(kind.clone()), + }, + Token::FixedBytes(bytes) => ParamType::FixedBytes { len: bytes.len() }, + Token::FixedArray { arr, kind } => ParamType::FixedArray { + kind: Box::new(kind.clone()), + len: arr.len(), + }, + Token::Tuple { params } => { + let params = params.iter().map(|param| param.to_param()).collect(); + ParamType::Tuple { params } + }, + } + } +} diff --git a/rust/tw_evm/src/abi/uint.rs b/rust/tw_evm/src/abi/uint.rs new file mode 100644 index 00000000000..3b5f6021432 --- /dev/null +++ b/rust/tw_evm/src/abi/uint.rs @@ -0,0 +1,55 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use std::fmt; +use tw_number::U256; + +#[derive(Clone, Copy, PartialEq)] +pub struct UintBits(usize); + +impl Default for UintBits { + fn default() -> Self { + UintBits::new(U256::BITS).expect("U256::BITS must be a valid number of bits") + } +} + +impl UintBits { + pub fn new(bits: usize) -> AbiResult { + check_uint_bits(bits)?; + Ok(UintBits(bits)) + } + + pub fn get(&self) -> usize { + self.0 + } +} + +impl fmt::Display for UintBits { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Debug for UintBits { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for usize { + fn from(value: UintBits) -> Self { + value.0 + } +} + +// https://docs.soliditylang.org/en/latest/abi-spec.html#types +pub fn check_uint_bits(bits: usize) -> AbiResult<()> { + if bits % 8 != 0 || bits == 0 || bits > 256 { + return Err(AbiError(AbiErrorKind::Error_invalid_uint_value)); + } + Ok(()) +} diff --git a/rust/tw_evm/src/address.rs b/rust/tw_evm/src/address.rs new file mode 100644 index 00000000000..c53887b5662 --- /dev/null +++ b/rust/tw_evm/src/address.rs @@ -0,0 +1,223 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::{Display, Formatter}; +use std::ops::{RangeFrom, RangeInclusive}; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_encoding::hex; +use tw_hash::{sha3::keccak256, H160, H256}; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; + +pub trait EvmAddress: FromStr + Into
{ + /// Tries to parse an address from the string representation. + /// Returns `Ok(None)` if the given `s` string is empty. + #[inline] + fn from_str_optional(s: &str) -> AddressResult> { + if s.is_empty() { + return Ok(None); + } + + Self::from_str(s).map(Some) + } +} + +/// Represents an Ethereum address. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Address { + bytes: H160, +} + +/// cbindgen:ignore +impl Address { + pub const LEN: usize = 20; + + /// Initializes an address with a `secp256k1` public key. + pub fn with_secp256k1_pubkey(pubkey: &secp256k1::PublicKey) -> Address { + /// `keccak256` returns 32 bytes, but Ethereum address is the last 20 bytes of the hash. + const ADDRESS_HASH_STARTS_AT: usize = H256::len() - H160::len(); + const ADDRESS_HASH_RANGE: RangeFrom = ADDRESS_HASH_STARTS_AT..; + + let pubkey_bytes = pubkey.uncompressed_without_prefix(); + let hash = keccak256(pubkey_bytes.as_slice()); + assert_eq!(hash.len(), H256::len()); + + let bytes = H160::try_from(&hash[ADDRESS_HASH_RANGE]).expect("Expected 20 byte array"); + + Address { bytes } + } + + /// Constructs an address from the 20-length byte array. + pub fn from_bytes(bytes: H160) -> Address { + Address { bytes } + } + + /// Displays the address in mixed-case checksum form + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md + fn into_checksum_address(self) -> String { + const UPPER_RANGE_1: RangeInclusive = '8'..='9'; + const UPPER_RANGE_2: RangeInclusive = 'a'..='f'; + + let prefixed = false; + let addr_hex = hex::encode(self.bytes, prefixed); + let addr_hash = hex::encode(keccak256(addr_hex.as_bytes()), prefixed); + + let payload_chars = addr_hex.chars().zip(addr_hash.chars()).map(|(a, h)| { + if a.is_ascii_digit() { + a + } else if UPPER_RANGE_1.contains(&h) || UPPER_RANGE_2.contains(&h) { + a.to_ascii_uppercase() + } else { + a.to_ascii_lowercase() + } + }); + + "0x".chars().chain(payload_chars).collect() + } + + /// Returns bytes of the address. + #[inline] + pub fn bytes(&self) -> H160 { + self.bytes + } + + /// Returns bytes as a slice of the address. + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +impl CoinAddress for Address { + #[inline] + fn data(&self) -> Data { + self.bytes.to_vec() + } +} + +impl FromStr for Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let addr_hex = s.strip_prefix("0x").ok_or(AddressError::MissingPrefix)?; + let addr_hash = H160::from_str(addr_hex).map_err(|_| AddressError::FromHexError)?; + Ok(Address { bytes: addr_hash }) + } +} + +impl EvmAddress for Address {} + +/// Implement `str` -> `PrivateKey` conversion for test purposes. +impl From<&'static str> for Address { + #[inline] + fn from(addr: &'static str) -> Self { + Address::from_str(addr).expect("Expected a valid Ethereum address") + } +} + +impl Display for Address { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.into_checksum_address()) + } +} + +impl<'de> Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: &str = Deserialize::deserialize(deserializer)?; + Address::from_str(s).map_err(|e| SerdeError::custom(format!("{e:?}"))) + } +} + +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_keypair::ecdsa::secp256k1::PrivateKey; + + struct Eip55Test { + test: &'static str, + mixedcase: &'static str, + } + + #[test] + fn test_to_from_string() { + let tests = [ + Eip55Test { + test: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + mixedcase: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + }, + Eip55Test { + test: "0x5AAEB6053F3E94C9b9A09f33669435E7Ef1BEAED", + mixedcase: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + }, + Eip55Test { + test: "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + mixedcase: "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + }, + Eip55Test { + test: "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + mixedcase: "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + }, + Eip55Test { + test: "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + mixedcase: "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + }, + ]; + + for test in tests { + let addr: Address = test.test.parse().unwrap(); + + let expected_payload = H160::from(test.test); + assert_eq!(addr.bytes, expected_payload); + + let mixed = addr.to_string(); + assert_eq!(mixed, test.mixedcase); + } + } + + #[test] + fn test_from_string_invalid() { + let tests = [ + "abc", + "0xabc", + "0xaaeb60f3e94c9b9a09f33669435e7ef1beaed", + "fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + ]; + for test in tests { + Address::from_str(test).unwrap_err(); + } + } + + #[test] + fn test_from_public_key() { + let private = PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + let addr = Address::with_secp256k1_pubkey(&public); + assert_eq!( + addr.to_string(), + "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309" + ); + } +} diff --git a/rust/tw_evm/src/evm_context.rs b/rust/tw_evm/src/evm_context.rs new file mode 100644 index 00000000000..83113770bb6 --- /dev/null +++ b/rust/tw_evm/src/evm_context.rs @@ -0,0 +1,19 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::{Address, EvmAddress}; + +/// EVM compatible chain specific. +pub trait EvmContext { + type Address: EvmAddress; +} + +#[derive(Default)] +pub struct StandardEvmContext; + +impl EvmContext for StandardEvmContext { + type Address = Address; +} diff --git a/rust/tw_evm/src/evm_entry.rs b/rust/tw_evm/src/evm_entry.rs new file mode 100644 index 00000000000..7ecbba2a885 --- /dev/null +++ b/rust/tw_evm/src/evm_entry.rs @@ -0,0 +1,123 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::evm_context::EvmContext; +use crate::modules::abi_encoder::AbiEncoder; +use crate::modules::rlp_encoder::RlpEncoder; +use tw_memory::Data; +use tw_proto::EthereumAbi::Proto as AbiProto; +use tw_proto::EthereumRlp::Proto as RlpProto; +use tw_proto::{deserialize, serialize, ProtoResult}; + +/// An EVM-compatible chain entry. +pub trait EvmEntry { + type Context: EvmContext; + + /// Encodes an item or a list of items as Eth RLP binary format. + #[inline] + fn encode_rlp(input: RlpProto::EncodingInput<'_>) -> RlpProto::EncodingOutput<'static> { + RlpEncoder::::encode_with_proto(input) + } + + /// Decodes function call data to human readable json format, according to input abi json. + #[inline] + fn decode_abi_contract_call( + input: AbiProto::ContractCallDecodingInput<'_>, + ) -> AbiProto::ContractCallDecodingOutput<'static> { + AbiEncoder::::decode_contract_call(input) + } + + /// Decodes a function input or output data according to a given ABI. + #[inline] + fn decode_abi_params( + input: AbiProto::ParamsDecodingInput<'_>, + ) -> AbiProto::ParamsDecodingOutput { + AbiEncoder::::decode_params(input) + } + + /// Decodes an Eth ABI value according to a given type. + #[inline] + fn decode_abi_value( + input: AbiProto::ValueDecodingInput<'_>, + ) -> AbiProto::ValueDecodingOutput<'static> { + AbiEncoder::::decode_value(input) + } + + /// Returns the function type signature, of the form "baz(int32,uint256)". + #[inline] + fn get_abi_function_signature(input: AbiProto::FunctionGetTypeInput<'_>) -> String { + AbiEncoder::::get_function_signature(input) + } + + // Encodes function inputs to Eth ABI binary. + #[inline] + fn encode_abi_function( + input: AbiProto::FunctionEncodingInput<'_>, + ) -> AbiProto::FunctionEncodingOutput<'static> { + AbiEncoder::::encode_contract_call(input) + } +} + +/// The [`EvmEntry`] trait extension. +pub trait EvmEntryExt { + /// Encodes an item or a list of items as Eth RLP binary format. + fn encode_rlp(&self, input: &[u8]) -> ProtoResult; + + /// Decodes function call data to human readable json format, according to input abi json. + fn decode_abi_contract_call(&self, input: &[u8]) -> ProtoResult; + + /// Decodes a function input or output data according to a given ABI. + fn decode_abi_params(&self, input: &[u8]) -> ProtoResult; + + /// Returns the function type signature, of the form "baz(int32,uint256)". + fn get_abi_function_signature(&self, input: &[u8]) -> ProtoResult; + + /// Encodes function inputs to Eth ABI binary. + fn encode_abi_function(&self, input: &[u8]) -> ProtoResult; + + /// Decodes an Eth ABI value according to a given type. + fn decode_abi_value(&self, input: &[u8]) -> ProtoResult; +} + +impl EvmEntryExt for T +where + T: EvmEntry, +{ + fn encode_rlp(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + let output = ::encode_rlp(input); + serialize(&output) + } + + fn decode_abi_contract_call(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + let output = ::decode_abi_contract_call(input); + serialize(&output) + } + + fn decode_abi_params(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + let output = ::decode_abi_params(input); + serialize(&output) + } + + fn get_abi_function_signature(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + Ok(::get_abi_function_signature(input)) + } + + fn encode_abi_function(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + let output = ::encode_abi_function(input); + serialize(&output) + } + + fn decode_abi_value(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + let output = ::decode_abi_value(input); + serialize(&output) + } +} diff --git a/rust/tw_evm/src/lib.rs b/rust/tw_evm/src/lib.rs new file mode 100644 index 00000000000..cacd3c67e82 --- /dev/null +++ b/rust/tw_evm/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod abi; +pub mod address; +pub mod evm_context; +pub mod evm_entry; +pub mod message; +pub mod modules; +pub mod rlp; +pub mod signature; +pub mod transaction; diff --git a/rust/tw_evm/src/message/eip191.rs b/rust/tw_evm/src/message/eip191.rs new file mode 100644 index 00000000000..4ff12895222 --- /dev/null +++ b/rust/tw_evm/src/message/eip191.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::message::{EthMessage, MessageSigningResult}; +use tw_hash::sha3::keccak256; +use tw_hash::H256; + +/// cbindgen:ignore +pub const ETHEREUM_PREFIX: u8 = 0x19; +/// cbindgen:ignore +pub const ETHEREUM_MESSAGE_PREFIX: &str = "Ethereum Signed Message:\n"; + +pub struct Eip191Message { + user_message: String, +} + +impl Eip191Message { + pub fn new>(user_message: S) -> Eip191Message { + Eip191Message { + user_message: user_message.into(), + } + } + + fn data_to_sign(&self) -> Vec { + let mut data = Vec::with_capacity(self.user_message.len() * 2); + + data.push(ETHEREUM_PREFIX); + data.extend_from_slice(ETHEREUM_MESSAGE_PREFIX.as_bytes()); + data.extend_from_slice(self.user_message.len().to_string().as_bytes()); + data.extend_from_slice(self.user_message.as_bytes()); + + data + } +} + +impl EthMessage for Eip191Message { + fn hash(&self) -> MessageSigningResult { + let hash = keccak256(&self.data_to_sign()); + Ok(H256::try_from(hash.as_slice()).expect("Expected 32 byte hash")) + } +} diff --git a/rust/tw_evm/src/message/eip712/eip712_message.rs b/rust/tw_evm/src/message/eip712/eip712_message.rs new file mode 100644 index 00000000000..9f21b15efaa --- /dev/null +++ b/rust/tw_evm/src/message/eip712/eip712_message.rs @@ -0,0 +1,404 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::encode::encode_tokens; +use crate::abi::non_empty_array::NonEmptyBytes; +use crate::abi::token::Token; +use crate::address::Address; +use crate::message::eip712::property::{Property, PropertyType}; +use crate::message::{EthMessage, MessageSigningError, MessageSigningResult}; +use itertools::Itertools; +use serde::Deserialize; +use serde_json::Value as Json; +use std::collections::HashMap; +use std::str::FromStr; +use tw_encoding::hex::{self, DecodeHex}; +use tw_hash::sha3::keccak256; +use tw_hash::{H160, H256}; +use tw_memory::Data; +use tw_number::{I256, U256}; + +/// EIP-191 compliant. +/// cbindgen:ignore +const PREFIX: &[u8; 2] = b"\x19\x01"; +/// cbindgen:ignore +const EIP712_DOMAIN: &str = "EIP712Domain"; + +type CustomTypes = HashMap>; + +#[derive(Debug, Deserialize)] +pub struct Eip712Message { + types: CustomTypes, + domain: Json, + #[serde(rename = "primaryType")] + primary_type: String, + message: Json, +} + +impl Eip712Message { + /// Tries to construct an EIP712 message from the given string. + pub fn new>(message_to_sign: S) -> MessageSigningResult { + let eip712_msg: Eip712Message = serde_json::from_str(message_to_sign.as_ref()) + .map_err(|_| MessageSigningError::TypeValueMismatch)?; + + // Check if the given message is actually EIP712. + if !eip712_msg.types.contains_key(EIP712_DOMAIN) { + return Err(MessageSigningError::TypeValueMismatch); + } + Ok(eip712_msg) + } + + pub fn new_checked>( + message_to_sign: S, + expected_chain_id: U256, + ) -> MessageSigningResult { + let msg = Eip712Message::new(message_to_sign)?; + + // Check if `domain.chainId` is expected. + let chain_id_value = msg.domain["chainId"].clone(); + let chain_id = U256::from_u64_or_decimal_str(chain_id_value) + .map_err(|_| MessageSigningError::TypeValueMismatch)?; + if chain_id != expected_chain_id { + return Err(MessageSigningError::InvalidChainId); + } + + Ok(msg) + } +} + +impl EthMessage for Eip712Message { + fn hash(&self) -> MessageSigningResult { + let domain_hash = encode_data( + &self.types, + PropertyType::Custom(EIP712_DOMAIN.to_string()), + &self.domain, + )?; + let primary_data_hash = encode_data( + &self.types, + PropertyType::Custom(self.primary_type.clone()), + &self.message, + )?; + + let concat = [ + PREFIX.as_slice(), + domain_hash.as_slice(), + primary_data_hash.as_slice(), + ] + .concat(); + + let hash_data = keccak256(&concat); + Ok(H256::try_from(hash_data.as_slice()).expect("Expected 32-byte hash")) + } +} + +fn encode_data( + custom_types: &CustomTypes, + data_type: PropertyType, + data: &Json, +) -> MessageSigningResult> { + match data_type { + PropertyType::Bool => encode_bool(data), + PropertyType::String => encode_string(data), + PropertyType::Int => encode_i256(data), + PropertyType::Uint => encode_u256(data), + PropertyType::Address => encode_address(data), + PropertyType::FixBytes { len } => encode_fix_bytes(data, len.get()), + PropertyType::Bytes => encode_bytes(data), + PropertyType::Custom(custom) => encode_custom(custom_types, &custom, data), + PropertyType::Array(element_type) => encode_array(custom_types, *element_type, data, None), + PropertyType::FixArray { len, element_type } => { + encode_array(custom_types, *element_type, data, Some(len.get())) + }, + } +} + +fn encode_bool(value: &Json) -> MessageSigningResult { + let bin = value + .as_bool() + .ok_or(MessageSigningError::InvalidParameterValue)?; + Ok(encode_tokens(&[Token::Bool(bin)])) +} + +fn encode_string(value: &Json) -> MessageSigningResult { + let string = value + .as_str() + .ok_or(MessageSigningError::InvalidParameterValue)?; + let hash = keccak256(string.as_bytes()); + let checked_bytes = NonEmptyBytes::new(hash).expect("`hash` must not be empty"); + Ok(encode_tokens(&[Token::FixedBytes(checked_bytes)])) +} + +fn encode_u256(value: &Json) -> MessageSigningResult { + let uint = U256::from_u64_or_decimal_str(value.clone()) + .map_err(|_| MessageSigningError::InvalidParameterValue)?; + Ok(encode_tokens(&[Token::u256(uint)])) +} + +fn encode_i256(value: &Json) -> MessageSigningResult { + let int = I256::from_i64_or_decimal_str(value.clone()) + .map_err(|_| MessageSigningError::InvalidParameterValue)?; + Ok(encode_tokens(&[Token::i256(int)])) +} + +fn encode_address(value: &Json) -> MessageSigningResult { + let addr_str = value + .as_str() + .ok_or(MessageSigningError::InvalidParameterValue)?; + // H160 doesn't require the string to be `0x` prefixed. + let addr_data = + H160::from_str(addr_str).map_err(|_| MessageSigningError::InvalidParameterValue)?; + let addr = Address::from_bytes(addr_data); + Ok(encode_tokens(&[Token::Address(addr)])) +} + +fn encode_fix_bytes(value: &Json, expected_len: usize) -> MessageSigningResult { + let str = value + .as_str() + .ok_or(MessageSigningError::InvalidParameterValue)?; + let fix_bytes = + hex::decode_lenient(str).map_err(|_| MessageSigningError::InvalidParameterValue)?; + if fix_bytes.len() > expected_len { + return Err(MessageSigningError::TypeValueMismatch); + } + let checked_bytes = + NonEmptyBytes::new(fix_bytes).map_err(|_| MessageSigningError::InvalidParameterValue)?; + Ok(encode_tokens(&[Token::FixedBytes(checked_bytes)])) +} + +fn encode_bytes(value: &Json) -> MessageSigningResult { + let str = value + .as_str() + .ok_or(MessageSigningError::InvalidParameterValue)?; + let bytes = str + .decode_hex() + .map_err(|_| MessageSigningError::InvalidParameterValue)?; + let hash = keccak256(&bytes); + Ok(encode_tokens(&[Token::Bytes(hash)])) +} + +fn encode_array( + custom_types: &CustomTypes, + element_type: PropertyType, + data: &Json, + expected_len: Option, +) -> MessageSigningResult { + let elements = data + .as_array() + .ok_or(MessageSigningError::InvalidParameterValue)?; + + // Check if the type definition actually matches the length of items to be encoded. + if expected_len.is_some() && Some(elements.len()) != expected_len { + return Err(MessageSigningError::TypeValueMismatch)?; + } + + let mut encoded_items = vec![]; + for item in elements { + let mut encoded = encode_data(custom_types, element_type.clone(), item)?; + encoded_items.append(&mut encoded); + } + + Ok(keccak256(&encoded_items)) +} + +fn encode_custom( + custom_types: &CustomTypes, + data_ident: &str, + data: &Json, +) -> MessageSigningResult { + let data_properties = custom_types + .get(data_ident) + .ok_or(MessageSigningError::TypeValueMismatch)?; + + let type_hash = encode_custom_type::type_hash(data_ident, custom_types)?; + let checked_bytes = + NonEmptyBytes::new(type_hash).map_err(|_| MessageSigningError::InvalidParameterValue)?; + let mut encoded_tokens = encode_tokens(&[Token::FixedBytes(checked_bytes)]); + + for field in data_properties.iter() { + let field_value = &data[&field.name]; + let field_property = PropertyType::from_str(&field.property_type)?; + let mut encoded = encode_data(custom_types, field_property, field_value)?; + encoded_tokens.append(&mut encoded); + } + + Ok(keccak256(&encoded_tokens)) +} + +mod encode_custom_type { + use super::*; + use std::collections::HashSet; + + pub(super) fn type_hash( + data_type: &str, + custom_types: &CustomTypes, + ) -> MessageSigningResult { + let encoded_type = encode_type(custom_types, data_type)?; + Ok(keccak256(encoded_type.as_bytes())) + } + + pub(super) fn encode_type( + custom_types: &CustomTypes, + data_type: &str, + ) -> MessageSigningResult { + let deps = { + let mut temp = build_dependencies(data_type, custom_types) + .ok_or(MessageSigningError::TypeValueMismatch)?; + temp.remove(data_type); + let mut temp = temp.into_iter().collect::>(); + temp.sort_unstable(); + temp.insert(0, data_type); + temp + }; + + let encoded = deps + .into_iter() + .filter_map(|dep| { + custom_types.get(dep).map(|field_types| { + let types = field_types + .iter() + .map(|value| format!("{} {}", value.property_type, value.name)) + .join(","); + format!("{}({})", dep, types) + }) + }) + .collect::>() + .concat(); + Ok(encoded) + } + + /// Given a type and the set of custom types. + /// Returns a `HashSet` of dependent types of the given type. + pub(super) fn build_dependencies<'a>( + data_type: &'a str, + custom_types: &'a CustomTypes, + ) -> Option> { + custom_types.get(data_type)?; + + let mut types_stack = Vec::new(); + types_stack.push(data_type); + let mut deps = HashSet::new(); + + while let Some(item) = types_stack.pop() { + if let Some(fields) = custom_types.get(item) { + deps.insert(item); + + for field in fields.iter() { + // check if this field is an array type + let field_type = if let Some(index) = field.property_type.find('[') { + &field.property_type[..index] + } else { + &field.property_type + }; + // Seen this type before? or not a custom type - skip + if !deps.contains(field_type) || custom_types.contains_key(field_type) { + types_stack.push(field_type); + } + } + } + } + + Some(deps) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use tw_encoding::hex::ToHex; + + #[test] + fn test_build_dependencies() { + let custom_types = r#"{ + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" } + ] + }"#; + + let custom_types: CustomTypes = serde_json::from_str(custom_types).unwrap(); + + let mail = "Mail"; + let person = "Person"; + + let expected = { + let mut temp = HashSet::new(); + temp.insert(mail); + temp.insert(person); + temp + }; + assert_eq!( + encode_custom_type::build_dependencies(mail, &custom_types), + Some(expected) + ); + } + + #[test] + fn test_encode_type() { + let custom_types = r#"{ + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" } + ] + }"#; + + let custom_types: CustomTypes = serde_json::from_str(custom_types).expect("alas error!"); + assert_eq!( + "Mail(Person from,Person to,string contents)Person(string name,address wallet)", + encode_custom_type::encode_type(&custom_types, "Mail").expect("alas error!") + ) + } + + #[test] + fn test_encode_type_hash() { + let custom_types = r#"{ + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" } + ] + }"#; + + let custom_types = serde_json::from_str::(custom_types).expect("alas error!"); + let hash = encode_custom_type::type_hash("Mail", &custom_types).expect("alas error!"); + assert_eq!( + hash.to_hex(), + "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" + ); + } +} diff --git a/rust/tw_evm/src/message/eip712/mod.rs b/rust/tw_evm/src/message/eip712/mod.rs new file mode 100644 index 00000000000..78ee85335ad --- /dev/null +++ b/rust/tw_evm/src/message/eip712/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod eip712_message; +pub mod property; diff --git a/rust/tw_evm/src/message/eip712/property.rs b/rust/tw_evm/src/message/eip712/property.rs new file mode 100644 index 00000000000..d61340873ad --- /dev/null +++ b/rust/tw_evm/src/message/eip712/property.rs @@ -0,0 +1,192 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::non_empty_array::NonZeroLen; +use crate::abi::param_type::constructor::TypeConstructor; +use crate::abi::param_type::reader::Reader; +use crate::abi::uint::UintBits; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use crate::message::MessageSigningError; +use serde::Deserialize; +use std::str::FromStr; + +#[derive(Clone, Debug, Deserialize)] +pub struct Property { + pub name: String, + #[serde(rename = "type")] + pub property_type: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum PropertyType { + Bool, + String, + Int, + Uint, + Address, + FixBytes { + len: NonZeroLen, + }, + Bytes, + Custom(String), + Array(Box), + FixArray { + len: NonZeroLen, + element_type: Box, + }, +} + +impl TypeConstructor for PropertyType { + fn address() -> Self { + PropertyType::Address + } + + fn bytes() -> Self { + PropertyType::Bytes + } + + fn fixed_bytes_checked(len: NonZeroLen) -> Self { + PropertyType::FixBytes { len } + } + + fn int_checked(_bits: UintBits) -> Self { + PropertyType::Int + } + + fn uint_checked(_bits: UintBits) -> Self { + PropertyType::Uint + } + + fn bool() -> Self { + PropertyType::Bool + } + + fn string() -> Self { + PropertyType::String + } + + fn array(element_type: Self) -> Self { + PropertyType::Array(Box::new(element_type)) + } + + fn fixed_array_checked(len: NonZeroLen, element_type: Self) -> Self { + PropertyType::FixArray { + len, + element_type: Box::new(element_type), + } + } + + fn empty_tuple() -> AbiResult { + Err(AbiError(AbiErrorKind::Error_invalid_param_type)) + } + + fn custom(s: &str) -> AbiResult { + Ok(PropertyType::Custom(s.to_string())) + } +} + +impl FromStr for PropertyType { + type Err = MessageSigningError; + + fn from_str(s: &str) -> Result { + Reader::parse_type(s).map_err(|_| MessageSigningError::InvalidParameterType) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_int() { + let ints = ["int", "int8", "int256"]; + for int in ints { + assert_eq!(PropertyType::from_str(int).unwrap(), PropertyType::Int); + } + } + + #[test] + fn test_parse_uint() { + let ints = ["uint", "uint8", "uint256"]; + for int in ints { + assert_eq!(PropertyType::from_str(int).unwrap(), PropertyType::Uint); + } + } + + #[test] + fn test_parse_bytes() { + assert_eq!( + PropertyType::from_str("bytes").unwrap(), + PropertyType::Bytes + ); + assert_eq!( + PropertyType::from_str("bytes8").unwrap(), + PropertyType::FixBytes { + len: NonZeroLen::new(8).unwrap() + } + ); + assert_eq!( + PropertyType::from_str("bytes31").unwrap(), + PropertyType::FixBytes { + len: NonZeroLen::new(31).unwrap() + } + ); + } + + #[test] + fn test_parse_types() { + assert_eq!(PropertyType::from_str("bool").unwrap(), PropertyType::Bool); + assert_eq!( + PropertyType::from_str("string").unwrap(), + PropertyType::String + ); + assert_eq!( + PropertyType::from_str("address").unwrap(), + PropertyType::Address + ); + assert_eq!( + PropertyType::from_str("Unknown").unwrap(), + PropertyType::Custom("Unknown".to_string()) + ); + } + + #[test] + fn test_parse_nested_arrays() { + fn array(kind: PropertyType) -> PropertyType { + PropertyType::Array(Box::new(kind)) + } + + fn fix_array(kind: PropertyType) -> PropertyType { + PropertyType::FixArray { + len: NonZeroLen::new(N).unwrap(), + element_type: Box::new(kind), + } + } + + let source = "bytes[][][7][]"; + let actual = PropertyType::from_str(source).unwrap(); + let expected = array(fix_array::<7>(array(array(PropertyType::Bytes)))); + assert_eq!(actual, expected); + } + + #[test] + fn test_malformed_array_type() { + let inputs = [ + "string[[]][]", + "string[7[]][]", + "string[7[]uint][]", + "string][", + "[]string", + "[string]", + "[]", + "string[]uint", + ]; + + for input in inputs { + assert_eq!(PropertyType::from_str(input).is_err(), true, "{}", input); + } + } +} diff --git a/rust/tw_evm/src/message/mod.rs b/rust/tw_evm/src/message/mod.rs new file mode 100644 index 00000000000..a962df3519b --- /dev/null +++ b/rust/tw_evm/src/message/mod.rs @@ -0,0 +1,50 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::{SigningError, SigningErrorType}; +use tw_hash::H256; + +pub mod eip191; +pub mod eip712; +pub mod signature; + +pub type EthMessageBoxed = Box; +pub type MessageSigningResult = Result; + +#[derive(Debug)] +pub enum MessageSigningError { + InvalidParameterType, + InvalidParameterValue, + TypeValueMismatch, + InvalidChainId, + Internal, +} + +impl From for SigningError { + fn from(err: MessageSigningError) -> Self { + match err { + MessageSigningError::InvalidParameterType + | MessageSigningError::InvalidParameterValue + | MessageSigningError::TypeValueMismatch + | MessageSigningError::InvalidChainId => { + SigningError(SigningErrorType::Error_invalid_params) + }, + MessageSigningError::Internal => SigningError(SigningErrorType::Error_internal), + } + } +} + +pub trait EthMessage { + fn into_boxed(self) -> EthMessageBoxed + where + Self: 'static + Sized, + { + Box::new(self) + } + + /// Returns hash of the message. + fn hash(&self) -> MessageSigningResult; +} diff --git a/rust/tw_evm/src/message/signature.rs b/rust/tw_evm/src/message/signature.rs new file mode 100644 index 00000000000..847df7f4b4a --- /dev/null +++ b/rust/tw_evm/src/message/signature.rs @@ -0,0 +1,91 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signature::{ + eip155_replay_protection, legacy_replay_protection, remove_replay_protection, +}; +use std::str::FromStr; +use tw_encoding::hex::DecodeHex; +use tw_hash::{H256, H520}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_number::U256; + +pub enum SignatureType { + Standard, + Legacy, + Eip155 { chain_id: u64 }, +} + +pub struct MessageSignature { + r: H256, + s: H256, + v: u8, +} + +impl MessageSignature { + /// The length of the binary representation. + /// cbindgen:ignore + pub const LEN: usize = secp256k1::Signature::LEN; + + pub fn prepared(sign: secp256k1::Signature, sign_type: SignatureType) -> KeyPairResult { + let v = match sign_type { + SignatureType::Standard => U256::from(sign.v()), + SignatureType::Legacy => { + legacy_replay_protection(sign.v()).map_err(|_| KeyPairError::InvalidSignature)? + }, + SignatureType::Eip155 { chain_id } => { + eip155_replay_protection(U256::from(chain_id), sign.v()) + .map_err(|_| KeyPairError::InvalidSignature)? + }, + }; + Ok(MessageSignature { + r: sign.r(), + s: sign.s(), + v: v.low_u8(), + }) + } + + pub fn to_bytes(&self) -> H520 { + let mut bytes = Vec::with_capacity(H520::LEN); + bytes.extend_from_slice(self.r.as_slice()); + bytes.extend_from_slice(self.s.as_slice()); + bytes.push(self.v); + + H520::try_from(bytes.as_slice()).expect("'bytes' is 130 byte array") + } + + pub fn to_secp256k1_signature(&self) -> KeyPairResult { + let v = remove_replay_protection(self.v); + secp256k1::Signature::try_from_parts(self.r, self.s, v) + } +} + +impl FromStr for MessageSignature { + type Err = KeyPairError; + + fn from_str(s: &str) -> Result { + let bytes = s.decode_hex().map_err(|_| KeyPairError::InvalidSignature)?; + Self::try_from(bytes.as_slice()) + } +} + +impl<'a> TryFrom<&'a [u8]> for MessageSignature { + type Error = KeyPairError; + + fn try_from(data: &'a [u8]) -> Result { + if data.len() != Self::LEN { + return Err(KeyPairError::InvalidSignature); + } + + let r = H256::try_from(&data[secp256k1::Signature::R_RANGE]) + .expect("Expected 'r' 32 byte length array"); + let s = H256::try_from(&data[secp256k1::Signature::S_RANGE]) + .expect("Expected 's' 32 byte length array"); + let v = data[secp256k1::Signature::RECOVERY_LAST]; + Ok(MessageSignature { r, s, v }) + } +} diff --git a/rust/tw_evm/src/modules/abi_encoder.rs b/rust/tw_evm/src/modules/abi_encoder.rs new file mode 100644 index 00000000000..6879a1ab2e4 --- /dev/null +++ b/rust/tw_evm/src/modules/abi_encoder.rs @@ -0,0 +1,479 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::decode::{decode_params, decode_value}; +use crate::abi::function::Function; +use crate::abi::param::Param; +use crate::abi::param_token::NamedToken; +use crate::abi::param_type::ParamType; +use crate::abi::token::Token; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use crate::abi_output_error; +use crate::address::Address; +use crate::evm_context::EvmContext; +use serde::Deserialize; +use serde::Serialize; +use std::borrow::Cow; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::str::FromStr; +use tw_hash::H32; +use tw_misc::traits::ToBytesVec; +use tw_number::{I256, U256}; +use tw_proto::EthereumAbi::Proto; + +use crate::abi::non_empty_array::{NonEmptyArray, NonEmptyBytes, NonZeroLen}; +use crate::abi::uint::UintBits; +use Proto::mod_ParamType::OneOfparam as ProtoParamType; +use Proto::mod_ParamsDecodingInput::OneOfabi as AbiEnum; +use Proto::mod_Token::OneOftoken as TokenEnum; + +pub struct AbiEncoder { + _phantom: PhantomData, +} + +impl AbiEncoder { + #[inline] + pub fn decode_contract_call( + input: Proto::ContractCallDecodingInput, + ) -> Proto::ContractCallDecodingOutput<'static> { + Self::decode_contract_call_impl(input) + .unwrap_or_else(|err| abi_output_error!(Proto::ContractCallDecodingOutput, err)) + } + + #[inline] + pub fn decode_params( + input: Proto::ParamsDecodingInput<'_>, + ) -> Proto::ParamsDecodingOutput<'static> { + Self::decode_params_impl(input) + .unwrap_or_else(|err| abi_output_error!(Proto::ParamsDecodingOutput, err)) + } + + #[inline] + pub fn decode_value( + input: Proto::ValueDecodingInput<'_>, + ) -> Proto::ValueDecodingOutput<'static> { + Self::decode_value_impl(input) + .unwrap_or_else(|err| abi_output_error!(Proto::ValueDecodingOutput, err)) + } + + #[inline] + pub fn get_function_signature(input: Proto::FunctionGetTypeInput<'_>) -> String { + Self::get_function_signature_impl(input) + } + + #[inline] + pub fn encode_contract_call( + input: Proto::FunctionEncodingInput<'_>, + ) -> Proto::FunctionEncodingOutput<'static> { + Self::encode_contract_call_impl(input) + .unwrap_or_else(|err| abi_output_error!(Proto::FunctionEncodingOutput, err)) + } + + fn decode_contract_call_impl( + input: Proto::ContractCallDecodingInput, + ) -> AbiResult> { + if input.encoded.len() < H32::len() { + return Err(AbiError(AbiErrorKind::Error_decoding_data)); + } + let short_signature = &input.encoded[0..H32::len()]; + let short_signature = + H32::try_from(short_signature).expect("The length expected to be checked above"); + let encoded_data = &input.encoded[H32::len()..]; + + let mut abi_json: SmartContractCallAbiJson = + serde_json::from_str(&input.smart_contract_abi_json) + .map_err(|_| AbiError(AbiErrorKind::Error_invalid_abi))?; + + let function = abi_json + .map + .get_mut(&short_signature) + .ok_or(AbiError(AbiErrorKind::Error_abi_mismatch))?; + + let decoded_tokens = function.decode_input(encoded_data)?; + + // Clear the `outputs` to avoid adding them to the signature. + // This is a requirement that comes from legacy ABI implementation. + function.outputs.clear(); + let function_signature = function.signature(); + + // Serialize the `decoded_json` result. + let decoded_res = SmartContractCallDecodedInputJson { + function: function_signature, + inputs: &decoded_tokens, + }; + let decoded_json = serde_json::to_string(&decoded_res) + .map_err(|_| AbiError(AbiErrorKind::Error_internal))?; + + // Serialize the Proto parameters. + let decoded_protos = decoded_tokens + .into_iter() + .map(Self::named_token_to_proto) + .collect(); + + Ok(Proto::ContractCallDecodingOutput { + decoded_json: Cow::Owned(decoded_json), + tokens: decoded_protos, + ..Proto::ContractCallDecodingOutput::default() + }) + } + + fn decode_params_impl( + input: Proto::ParamsDecodingInput<'_>, + ) -> AbiResult> { + let abi = match input.abi { + AbiEnum::abi_json(abi_json) => serde_json::from_str(&abi_json) + .map_err(|_| AbiError(AbiErrorKind::Error_invalid_abi))?, + AbiEnum::abi_params(abi_params) => abi_params + .params + .into_iter() + .map(Self::param_from_proto) + .collect::>>()?, + AbiEnum::None => return Err(AbiError(AbiErrorKind::Error_invalid_abi)), + }; + + let decoded_tokens = decode_params(&abi, &input.encoded)?; + // Serialize the Proto parameters. + let decoded_protos = decoded_tokens + .into_iter() + .map(Self::named_token_to_proto) + .collect(); + + Ok(Proto::ParamsDecodingOutput { + tokens: decoded_protos, + ..Proto::ParamsDecodingOutput::default() + }) + } + + fn decode_value_impl( + input: Proto::ValueDecodingInput<'_>, + ) -> AbiResult> { + let param_type = DecodingValueType::from_str(&input.param_type)?.0; + let token = decode_value(¶m_type, &input.encoded)?; + let token_str = token.to_string(); + Ok(Proto::ValueDecodingOutput { + token: Some(Self::token_to_proto(token)), + param_str: token_str.into(), + ..Proto::ValueDecodingOutput::default() + }) + } + + fn get_function_signature_impl(input: Proto::FunctionGetTypeInput<'_>) -> String { + let function_inputs = input + .inputs + .into_iter() + .map(Self::param_from_proto) + .collect::>>() + .unwrap_or_default(); + + let fun = Function { + name: input.function_name.to_string(), + inputs: function_inputs, + ..Function::default() + }; + fun.signature() + } + + fn encode_contract_call_impl( + input: Proto::FunctionEncodingInput<'_>, + ) -> AbiResult> { + let mut tokens = Vec::with_capacity(input.tokens.len()); + let mut input_types = Vec::with_capacity(input.tokens.len()); + + for token in input.tokens { + let named_token = Self::named_token_from_proto(token)?; + input_types.push(named_token.to_param()); + tokens.push(named_token.value); + } + + let fun = Function { + name: input.function_name.to_string(), + inputs: input_types, + ..Function::default() + }; + + let encoded = fun.encode_input(&tokens)?; + Ok(Proto::FunctionEncodingOutput { + function_type: fun.signature().into(), + encoded: encoded.into(), + ..Proto::FunctionEncodingOutput::default() + }) + } + + pub fn param_to_proto(param: Param) -> Proto::Param<'static> { + Proto::Param { + name: Cow::Owned(param.name.unwrap_or_default()), + param: Some(Self::param_type_to_proto(param.kind)), + } + } + + fn param_from_proto(param: Proto::Param<'_>) -> AbiResult { + let name = if param.name.is_empty() { + None + } else { + Some(param.name.to_string()) + }; + + let proto_param_type = param + .param + .ok_or(AbiError(AbiErrorKind::Error_missing_param_type))?; + let kind = Self::param_type_from_proto(proto_param_type)?; + + Ok(Param { + name, + kind, + internal_type: None, + }) + } + + fn named_token_from_proto(named_token: Proto::Token<'_>) -> AbiResult { + Ok(NamedToken { + name: Some(named_token.name.clone().into()), + value: Self::token_from_proto(named_token)?, + internal_type: None, + }) + } + + fn token_from_proto(token: Proto::Token<'_>) -> AbiResult { + match token.token { + TokenEnum::boolean(bool) => Ok(Token::Bool(bool)), + TokenEnum::number_uint(u) => { + let uint = Self::u_number_n_from_proto(&u.value)?; + Token::uint(u.bits as usize, uint) + }, + TokenEnum::number_int(i) => { + let int = Self::s_number_n_from_proto(&i.value)?; + Token::int(i.bits as usize, int) + }, + TokenEnum::string_value(str) => Ok(Token::String(str.to_string())), + TokenEnum::address(addr) => { + let addr = Address::from_str(&addr) + .map_err(|_| AbiError(AbiErrorKind::Error_invalid_address_value))?; + Ok(Token::Address(addr)) + }, + TokenEnum::byte_array(bytes) => Ok(Token::Bytes(bytes.to_vec())), + TokenEnum::byte_array_fix(bytes) => { + let checked_bytes = NonEmptyBytes::new(bytes.to_vec())?; + Ok(Token::FixedBytes(checked_bytes)) + }, + TokenEnum::array(arr) => { + let (arr, kind) = Self::array_from_proto(arr)?; + Ok(Token::Array { arr, kind }) + }, + TokenEnum::fixed_array(arr) => { + let (arr, kind) = Self::array_from_proto(arr)?; + let arr = NonEmptyArray::new(arr)?; + Ok(Token::FixedArray { arr, kind }) + }, + TokenEnum::tuple(Proto::TupleParam { params }) => { + let params = params + .into_iter() + .map(Self::named_token_from_proto) + .collect::>>()?; + Ok(Token::Tuple { params }) + }, + TokenEnum::None => Err(AbiError(AbiErrorKind::Error_missing_param_value)), + } + } + + fn array_from_proto(array: Proto::ArrayParam<'_>) -> AbiResult<(Vec, ParamType)> { + let element_type = array + .element_type + .ok_or(AbiError(AbiErrorKind::Error_missing_param_type))?; + let element_type = Self::param_type_from_proto(element_type)?; + + let mut array_tokens = Vec::with_capacity(array.elements.len()); + for proto_token in array.elements { + let token = Self::token_from_proto(proto_token)?; + let token_type = token.to_param_type(); + + // Check if all tokens are the same as declared in `ArrayParam::element_type`. + if token_type != element_type { + return Err(AbiError(AbiErrorKind::Error_invalid_param_type)); + } + array_tokens.push(token); + } + + Ok((array_tokens, element_type)) + } + + fn param_type_to_proto(param_type: ParamType) -> Proto::ParamType<'static> { + let param = match param_type { + ParamType::Address => ProtoParamType::address(Proto::AddressType {}), + ParamType::FixedBytes { len } => { + let size = len.get() as u64; + ProtoParamType::byte_array_fix(Proto::ByteArrayFixType { size }) + }, + ParamType::Bytes => ProtoParamType::byte_array(Proto::ByteArrayType {}), + ParamType::Int { bits } => ProtoParamType::number_int(Proto::NumberNType { + bits: bits.get() as u32, + }), + ParamType::Uint { bits } => ProtoParamType::number_uint(Proto::NumberNType { + bits: bits.get() as u32, + }), + ParamType::Bool => ProtoParamType::boolean(Proto::BoolType {}), + ParamType::String => ProtoParamType::string_param(Proto::StringType {}), + ParamType::FixedArray { kind, len } => { + let size = len.get() as u64; + let element_type = Some(Box::new(Self::param_type_to_proto(*kind))); + ProtoParamType::fixed_array(Box::new(Proto::FixedArrayType { size, element_type })) + }, + ParamType::Array { kind } => { + let element_type = Some(Box::new(Self::param_type_to_proto(*kind))); + ProtoParamType::array(Box::new(Proto::ArrayType { element_type })) + }, + ParamType::Tuple { params } => { + let params: Vec<_> = params.into_iter().map(Self::param_to_proto).collect(); + ProtoParamType::tuple(Proto::TupleType { params }) + }, + }; + Proto::ParamType { param } + } + + fn param_type_from_proto(param_type: Proto::ParamType<'_>) -> AbiResult { + match param_type.param { + ProtoParamType::boolean(_) => Ok(ParamType::Bool), + ProtoParamType::number_int(i) => { + let bits = UintBits::new(i.bits as usize)?; + Ok(ParamType::Int { bits }) + }, + ProtoParamType::number_uint(u) => { + let bits = UintBits::new(u.bits as usize)?; + Ok(ParamType::Uint { bits }) + }, + ProtoParamType::string_param(_) => Ok(ParamType::String), + ProtoParamType::address(_) => Ok(ParamType::Address), + ProtoParamType::byte_array(_) => Ok(ParamType::Bytes), + ProtoParamType::byte_array_fix(bytes) => { + let len = NonZeroLen::new(bytes.size as usize)?; + Ok(ParamType::FixedBytes { len }) + }, + ProtoParamType::array(arr) => { + let element_type = arr + .element_type + .ok_or(AbiError(AbiErrorKind::Error_missing_param_type))?; + let kind = Self::param_type_from_proto(*element_type)?; + Ok(ParamType::Array { + kind: Box::new(kind), + }) + }, + ProtoParamType::fixed_array(arr) => { + let element_type = arr + .element_type + .ok_or(AbiError(AbiErrorKind::Error_missing_param_type))?; + let kind = Box::new(Self::param_type_from_proto(*element_type)?); + let len = NonZeroLen::new(arr.size as usize)?; + Ok(ParamType::FixedArray { kind, len }) + }, + ProtoParamType::tuple(tuple) => { + let params = tuple + .params + .into_iter() + .map(Self::param_from_proto) + .collect::>>()?; + if params.is_empty() { + return Err(AbiError(AbiErrorKind::Error_invalid_abi)); + } + Ok(ParamType::Tuple { params }) + }, + ProtoParamType::None => Err(AbiError(AbiErrorKind::Error_missing_param_type)), + } + } + + fn named_token_to_proto(token: NamedToken) -> Proto::Token<'static> { + Proto::Token { + name: Cow::Owned(token.name.unwrap_or_default()), + ..Self::token_to_proto(token.value) + } + } + + pub fn token_to_proto(token: Token) -> Proto::Token<'static> { + let value = match token { + Token::Address(addr) => TokenEnum::address(Cow::Owned(addr.to_string())), + Token::FixedBytes(bytes) => TokenEnum::byte_array_fix(Cow::Owned(bytes.into_vec())), + Token::Bytes(bytes) => TokenEnum::byte_array(Cow::Owned(bytes)), + Token::Int { int, bits } => TokenEnum::number_int(Self::s_number_n_proto(int, bits)), + Token::Uint { uint, bits } => { + TokenEnum::number_uint(Self::u_number_n_proto(uint, bits)) + }, + Token::Bool(bool) => TokenEnum::boolean(bool), + Token::String(str) => TokenEnum::string_value(Cow::Owned(str)), + Token::FixedArray { kind, arr } => { + TokenEnum::fixed_array(Self::array_to_proto(kind, arr.into_vec())) + }, + Token::Array { kind, arr, .. } => TokenEnum::array(Self::array_to_proto(kind, arr)), + Token::Tuple { params } => TokenEnum::tuple(Self::tuple_to_proto(params)), + }; + Proto::Token { + name: "".into(), + token: value, + } + } + + fn s_number_n_from_proto(encoded: &[u8]) -> AbiResult { + I256::from_big_endian_slice(encoded) + .map_err(|_| AbiError(AbiErrorKind::Error_invalid_uint_value)) + } + + fn u_number_n_from_proto(encoded: &[u8]) -> AbiResult { + U256::from_big_endian_slice(encoded) + .map_err(|_| AbiError(AbiErrorKind::Error_invalid_uint_value)) + } + + fn s_number_n_proto(i: I256, bits: UintBits) -> Proto::NumberNParam<'static> { + Proto::NumberNParam { + bits: bits.get() as u32, + value: Cow::Owned(i.to_big_endian_compact()), + } + } + + fn u_number_n_proto(u: U256, bits: UintBits) -> Proto::NumberNParam<'static> { + Proto::NumberNParam { + bits: bits.get() as u32, + value: Cow::Owned(u.to_big_endian_compact()), + } + } + + fn array_to_proto(kind: ParamType, arr: Vec) -> Proto::ArrayParam<'static> { + Proto::ArrayParam { + elements: arr.into_iter().map(Self::token_to_proto).collect(), + element_type: Some(Self::param_type_to_proto(kind)), + } + } + + fn tuple_to_proto(params: Vec) -> Proto::TupleParam<'static> { + let params = params.into_iter().map(Self::named_token_to_proto).collect(); + Proto::TupleParam { params } + } +} + +#[derive(Deserialize)] +struct SmartContractCallAbiJson { + #[serde(flatten)] + map: HashMap, +} + +#[derive(Serialize)] +struct SmartContractCallDecodedInputJson<'a> { + function: String, + inputs: &'a [NamedToken], +} + +/// A value type used on [`AbiEncoder::decode_value`]. +/// Please note [`AbiEncoder::decode_value`] doesn't support `ParamType::Tuple` for decoding. +struct DecodingValueType(ParamType); + +impl FromStr for DecodingValueType { + type Err = AbiError; + + fn from_str(s: &str) -> Result { + let param_type = ParamType::try_from_type_short(s)?; + if param_type.has_tuple_components() { + return Err(AbiError(AbiErrorKind::Error_invalid_param_type)); + } + Ok(DecodingValueType(param_type)) + } +} diff --git a/rust/tw_evm/src/modules/compiler.rs b/rust/tw_evm/src/modules/compiler.rs new file mode 100644 index 00000000000..23598eca7aa --- /dev/null +++ b/rust/tw_evm/src/modules/compiler.rs @@ -0,0 +1,90 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::evm_context::EvmContext; +use crate::modules::tx_builder::TxBuilder; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_keypair::ecdsa::secp256k1; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct Compiler { + _phantom: PhantomData, +} + +impl Compiler { + #[inline] + pub fn preimage_hashes( + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + #[inline] + pub fn compile( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn preimage_hashes_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let chain_id = U256::from_big_endian_slice(&input.chain_id)?; + + let unsigned = TxBuilder::::tx_from_proto(&input)?; + let prehash = unsigned.pre_hash(chain_id); + let preimage_data = unsigned.encode(chain_id); + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage_data), + data_hash: Cow::from(prehash.to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) + } + + fn compile_impl( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key: _, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = secp256k1::Signature::from_bytes(&signature)?; + + let chain_id = U256::from_big_endian_slice(&input.chain_id)?; + + let unsigned = TxBuilder::::tx_from_proto(&input)?; + + let pre_hash = unsigned.pre_hash(chain_id); + + let signed = unsigned.try_into_signed(signature, chain_id)?; + + let eth_signature = signed.signature(); + + Ok(Proto::SigningOutput { + encoded: signed.encode().into(), + v: eth_signature.v().to_big_endian_compact().into(), + r: eth_signature.r().to_big_endian_compact().into(), + s: eth_signature.s().to_big_endian_compact().into(), + data: signed.payload().into(), + pre_hash: pre_hash.to_vec().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_evm/src/modules/json_signer.rs b/rust/tw_evm/src/modules/json_signer.rs new file mode 100644 index 00000000000..9bacea446c0 --- /dev/null +++ b/rust/tw_evm/src/modules/json_signer.rs @@ -0,0 +1,30 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::evm_context::EvmContext; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::modules::json_signer::JsonSigner; +use tw_keypair::tw::PrivateKey; + +#[derive(Default)] +pub struct EthJsonSigner { + _phantom: PhantomData, +} + +impl JsonSigner for EthJsonSigner { + #[inline] + fn sign_json( + &self, + _coin: &dyn CoinContext, + _input_json: &str, + _key: &PrivateKey, + ) -> SigningResult { + // TODO implement when `quick_protobuf` is replaced with `rust-protobuf`. + Err(SigningError(SigningErrorType::Error_internal)) + } +} diff --git a/rust/tw_evm/src/modules/message_signer.rs b/rust/tw_evm/src/modules/message_signer.rs new file mode 100644 index 00000000000..e0f76f9e116 --- /dev/null +++ b/rust/tw_evm/src/modules/message_signer.rs @@ -0,0 +1,148 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::message::eip191::Eip191Message; +use crate::message::eip712::eip712_message::Eip712Message; +use crate::message::signature::{MessageSignature, SignatureType}; +use crate::message::{EthMessage, EthMessageBoxed}; +use std::borrow::Cow; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::modules::message_signer::MessageSigner; +use tw_coin_entry::signing_output_error; +use tw_encoding::hex::ToHex; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::ecdsa::signature::VerifySignature; +use tw_keypair::traits::{SigningKeyTrait, VerifyingKeyTrait}; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +#[derive(Default)] +pub struct EthMessageSigner; + +impl MessageSigner for EthMessageSigner { + type MessageSigningInput<'a> = Proto::MessageSigningInput<'a>; + type MessagePreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type MessageSigningOutput = Proto::MessageSigningOutput<'static>; + type MessageVerifyingInput<'a> = Proto::MessageVerifyingInput<'a>; + + fn message_preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::MessageSigningInput<'_>, + ) -> Self::MessagePreSigningOutput { + Self::message_preimage_hashes_impl(input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn sign_message( + &self, + _coin: &dyn CoinContext, + input: Self::MessageSigningInput<'_>, + ) -> Self::MessageSigningOutput { + Self::sign_message_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::MessageSigningOutput, e)) + } + + fn verify_message( + &self, + _coin: &dyn CoinContext, + input: Self::MessageVerifyingInput<'_>, + ) -> bool { + Self::verify_message_impl(input).unwrap_or_default() + } +} + +impl EthMessageSigner { + fn message_preimage_hashes_impl( + input: Proto::MessageSigningInput<'_>, + ) -> SigningResult> { + let msg = Self::message_from_proto(input)?; + let hash = msg.hash()?.to_vec(); + Ok(CompilerProto::PreSigningOutput { + data: Cow::Owned(hash.clone()), + data_hash: Cow::Owned(hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + fn sign_message_impl( + input: Proto::MessageSigningInput<'_>, + ) -> SigningResult> { + let private_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref())?; + let signature_type = + Self::signature_type_from_proto(input.message_type, input.chain_id.clone()); + + let msg = Self::message_from_proto(input)?; + + let hash_to_sign = msg.hash()?; + let secp_sign = private_key.sign(hash_to_sign)?; + let prepared_sign = MessageSignature::prepared(secp_sign, signature_type)?; + + Ok(Proto::MessageSigningOutput { + signature: Cow::Owned(prepared_sign.to_bytes().to_hex()), + ..Proto::MessageSigningOutput::default() + }) + } + + fn verify_message_impl(input: Proto::MessageVerifyingInput<'_>) -> SigningResult { + let public_key = secp256k1::PublicKey::try_from(input.public_key.as_ref())?; + + let msg_hash = Self::message_from_str(&input.message)?.hash()?; + let secp_signature = + MessageSignature::from_str(&input.signature)?.to_secp256k1_signature()?; + + let actual_public_key = secp256k1::PublicKey::recover(secp_signature.clone(), msg_hash)?; + let valid = actual_public_key == public_key + && public_key.verify(VerifySignature::from(secp_signature), msg_hash); + + Ok(valid) + } + + fn message_from_proto(input: Proto::MessageSigningInput<'_>) -> SigningResult { + match input.message_type { + Proto::MessageType::MessageType_legacy + | Proto::MessageType::MessageType_eip155 + | Proto::MessageType::MessageType_immutable_x => { + Ok(Eip191Message::new(input.message).into_boxed()) + }, + Proto::MessageType::MessageType_typed + | Proto::MessageType::MessageType_typed_eip155 => match input.chain_id { + Some(expected_chain_id) => { + let expected_chain_id = U256::from(expected_chain_id.chain_id); + Ok(Eip712Message::new_checked(input.message, expected_chain_id)?.into_boxed()) + }, + None => Ok(Eip712Message::new(input.message)?.into_boxed()), + }, + } + } + + fn message_from_str(user_message: &str) -> SigningResult { + match Eip712Message::new(user_message) { + Ok(typed_data) => Ok(typed_data.into_boxed()), + Err(_) => Ok(Eip191Message::new(user_message).into_boxed()), + } + } + + fn signature_type_from_proto( + msg_type: Proto::MessageType, + maybe_chain_id: Option, + ) -> SignatureType { + match msg_type { + Proto::MessageType::MessageType_immutable_x => SignatureType::Standard, + Proto::MessageType::MessageType_legacy | Proto::MessageType::MessageType_typed => { + SignatureType::Legacy + }, + Proto::MessageType::MessageType_eip155 + | Proto::MessageType::MessageType_typed_eip155 => { + let chain_id = maybe_chain_id.unwrap_or_default().chain_id; + SignatureType::Eip155 { chain_id } + }, + } + } +} diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs new file mode 100644 index 00000000000..ba8e26862b4 --- /dev/null +++ b/rust/tw_evm/src/modules/mod.rs @@ -0,0 +1,13 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod abi_encoder; +pub mod compiler; +pub mod json_signer; +pub mod message_signer; +pub mod rlp_encoder; +pub mod signer; +pub mod tx_builder; diff --git a/rust/tw_evm/src/modules/rlp_encoder.rs b/rust/tw_evm/src/modules/rlp_encoder.rs new file mode 100644 index 00000000000..e45e2ef0d2d --- /dev/null +++ b/rust/tw_evm/src/modules/rlp_encoder.rs @@ -0,0 +1,92 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::evm_context::EvmContext; +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::list::RlpList; +use crate::rlp::RlpEncode; +use std::borrow::Cow; +use std::marker::PhantomData; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::EthereumRlp::Proto; + +/// cbindgen:ignore +pub const RECURSION_LIMIT: usize = 10; + +pub struct RlpEncoder { + _phantom: PhantomData, +} + +impl RlpEncoder { + pub fn encode(val: T) -> Data + where + T: RlpEncode, + { + let mut buf = RlpBuffer::new(); + val.rlp_append(&mut buf); + buf.finish() + } + + pub fn encode_with_proto(input: Proto::EncodingInput<'_>) -> Proto::EncodingOutput<'static> { + Self::encode_with_proto_impl(input) + .unwrap_or_else(|err| signing_output_error!(Proto::EncodingOutput, err)) + } + + fn encode_with_proto_impl( + input: Proto::EncodingInput<'_>, + ) -> SigningResult> { + let Some(rlp_item) = input.item else { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + }; + + let initial_depth = 0; + let encoded = Self::encode_proto_item(initial_depth, rlp_item)?; + Ok(Proto::EncodingOutput { + encoded: Cow::from(encoded.as_slice().to_vec()), + ..Proto::EncodingOutput::default() + }) + } + + fn encode_proto_item(depth: usize, rlp_item: Proto::RlpItem) -> SigningResult { + use Proto::mod_RlpItem::OneOfitem as Item; + + if depth >= RECURSION_LIMIT { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + } + + let encoded_item = match rlp_item.item { + Item::string_item(str) => RlpEncoder::::encode(str.as_ref()), + Item::number_u64(num) => RlpEncoder::::encode(U256::from(num)), + Item::number_u256(num_be) => { + let num = U256::from_big_endian_slice(num_be.as_ref())?; + RlpEncoder::::encode(num) + }, + Item::address(addr_s) => { + let addr = Context::Address::from_str(addr_s.as_ref())?; + RlpEncoder::::encode(addr.into()) + }, + Item::data(data) => RlpEncoder::::encode(data.as_ref()), + Item::list(proto_nested_list) => { + let mut rlp_nested_list = RlpList::new(); + let new_depth = depth + 1; + + for proto_nested_list in proto_nested_list.items { + let encoded_item = Self::encode_proto_item(new_depth, proto_nested_list)?; + rlp_nested_list.append_raw_encoded(encoded_item.as_slice()); + } + rlp_nested_list.finish() + }, + // Pass the `raw_encoded` item as it is. + Item::raw_encoded(encoded) => encoded.to_vec(), + Item::None => return Err(SigningError(SigningErrorType::Error_invalid_params)), + }; + Ok(encoded_item) + } +} diff --git a/rust/tw_evm/src/modules/signer.rs b/rust/tw_evm/src/modules/signer.rs new file mode 100644 index 00000000000..18eb01b3374 --- /dev/null +++ b/rust/tw_evm/src/modules/signer.rs @@ -0,0 +1,60 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::evm_context::EvmContext; +use crate::modules::tx_builder::TxBuilder; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::traits::SigningKeyTrait; +use tw_number::U256; +use tw_proto::Ethereum::Proto; + +const SIGNATURE_V_MIN_LEN: usize = 1; + +pub struct Signer { + _phantom: PhantomData, +} + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let chain_id = U256::from_big_endian_slice(&input.chain_id)?; + let private_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref())?; + + let unsigned = TxBuilder::::tx_from_proto(&input)?; + + let pre_hash = unsigned.pre_hash(chain_id); + let signature = private_key.sign(pre_hash)?; + + let signed = unsigned.try_into_signed(signature, chain_id)?; + + let eth_signature = signed.signature(); + + let v = eth_signature + .v() + .to_big_endian_compact_min_len(SIGNATURE_V_MIN_LEN); + + Ok(Proto::SigningOutput { + encoded: Cow::from(signed.encode()), + v: Cow::from(v), + r: Cow::from(eth_signature.r().to_big_endian().to_vec()), + s: Cow::from(eth_signature.s().to_big_endian().to_vec()), + data: Cow::from(signed.payload()), + pre_hash: Cow::from(pre_hash.to_vec()), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs new file mode 100644 index 00000000000..395fd81489a --- /dev/null +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -0,0 +1,237 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::prebuild::erc1155::Erc1155; +use crate::abi::prebuild::erc20::Erc20; +use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs}; +use crate::abi::prebuild::erc721::Erc721; +use crate::address::{Address, EvmAddress}; +use crate::evm_context::EvmContext; +use crate::transaction::transaction_eip1559::TransactionEip1559; +use crate::transaction::transaction_non_typed::TransactionNonTyped; +use crate::transaction::user_operation::UserOperation; +use crate::transaction::UnsignedTransactionBox; +use std::marker::PhantomData; +use std::str::FromStr; +use tw_coin_entry::error::{AddressResult, SigningError, SigningErrorType, SigningResult}; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::Common::Proto::SigningError as CommonError; +use tw_proto::Ethereum::Proto; + +pub struct TxBuilder { + _phantom: PhantomData, +} + +impl TxBuilder { + pub fn tx_from_proto( + input: &Proto::SigningInput<'_>, + ) -> SigningResult> { + use Proto::mod_Transaction::OneOftransaction_oneof as Tx; + use Proto::TransactionMode as TxMode; + + let Some(ref transaction) = input.transaction else { + return Err(SigningError(CommonError::Error_invalid_params)); + }; + + let (eth_amount, payload, to) = match transaction.transaction_oneof { + Tx::transfer(ref transfer) => { + let amount = U256::from_big_endian_slice(&transfer.amount)?; + let to_address = Self::parse_address(&input.to_address)?; + (amount, transfer.data.to_vec(), Some(to_address)) + }, + Tx::erc20_transfer(ref erc20_transfer) => { + let token_to_address = Self::parse_address(&erc20_transfer.to)?; + let token_amount = U256::from_big_endian_slice(&erc20_transfer.amount)?; + let contract_address = Self::parse_address(&input.to_address)?; + + let payload = Erc20::transfer(token_to_address, token_amount)?; + (U256::zero(), payload, Some(contract_address)) + }, + Tx::erc20_approve(ref erc20_approve) => { + let spender = Self::parse_address(&erc20_approve.spender)?; + let token_amount = U256::from_big_endian_slice(&erc20_approve.amount)?; + let contract_address = Self::parse_address(&input.to_address)?; + + let payload = Erc20::approve(spender, token_amount)?; + (U256::zero(), payload, Some(contract_address)) + }, + Tx::erc721_transfer(ref erc721_transfer) => { + let from = Self::parse_address(&erc721_transfer.from)?; + let token_to_address = Self::parse_address(&erc721_transfer.to)?; + let token_id = U256::from_big_endian_slice(&erc721_transfer.token_id)?; + let contract_address = Self::parse_address(&input.to_address)?; + + let payload = Erc721::encode_transfer_from(from, token_to_address, token_id)?; + (U256::zero(), payload, Some(contract_address)) + }, + Tx::erc1155_transfer(ref erc1155_transfer) => { + let from = Self::parse_address(&erc1155_transfer.from)?; + let to = Self::parse_address(&erc1155_transfer.to)?; + let token_id = U256::from_big_endian_slice(&erc1155_transfer.token_id)?; + let value = U256::from_big_endian_slice(&erc1155_transfer.value)?; + let data = erc1155_transfer.data.to_vec(); + let contract_address = Self::parse_address(&input.to_address)?; + + let payload = Erc1155::encode_safe_transfer_from(from, to, token_id, value, data)?; + (U256::zero(), payload, Some(contract_address)) + }, + Tx::contract_generic(ref contract_generic) => { + let amount = U256::from_big_endian_slice(&contract_generic.amount)?; + let payload = contract_generic.data.to_vec(); + // `to_address` can be omitted for the generic contract call. + // For example, on creating a new smart contract. + let to_address = Self::parse_address_optional(&input.to_address)?; + + (amount, payload, to_address) + }, + Tx::batch(ref batch) => { + if input.tx_mode != TxMode::UserOp { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + } + + // Payload should match ERC4337 standard. + let calls: Vec<_> = batch + .calls + .iter() + .map(Self::erc4337_execute_call_from_proto) + .collect::, _>>()?; + let payload = Erc4337SimpleAccount::encode_execute_batch(calls)?; + + return Self::user_operation_from_proto(input, payload) + .map(UserOperation::into_boxed); + }, + Tx::None => return Err(SigningError(SigningErrorType::Error_invalid_params)), + }; + + let tx = match input.tx_mode { + TxMode::Legacy => { + Self::transaction_non_typed_from_proto(input, eth_amount, payload, to)?.into_boxed() + }, + TxMode::Enveloped => { + Self::transaction_eip1559_from_proto(input, eth_amount, payload, to)?.into_boxed() + }, + TxMode::UserOp => { + let to = to.ok_or(SigningError(SigningErrorType::Error_invalid_address))?; + // Payload should match the ERC4337 standard. + let payload = Erc4337SimpleAccount::encode_execute(ExecuteArgs { + to, + value: eth_amount, + data: payload, + })?; + + Self::user_operation_from_proto(input, payload)?.into_boxed() + }, + }; + Ok(tx) + } + + #[inline] + fn erc4337_execute_call_from_proto( + call: &Proto::mod_Transaction::mod_Batch::BatchedCall, + ) -> SigningResult { + let to = Self::parse_address(&call.address)?; + let value = U256::from_big_endian_slice(&call.amount)?; + Ok(ExecuteArgs { + to, + value, + data: call.payload.to_vec(), + }) + } + + #[inline] + fn transaction_non_typed_from_proto( + input: &Proto::SigningInput, + eth_amount: U256, + payload: Data, + to_address: Option
, + ) -> SigningResult { + let nonce = U256::from_big_endian_slice(&input.nonce)?; + let gas_price = U256::from_big_endian_slice(&input.gas_price)?; + let gas_limit = U256::from_big_endian_slice(&input.gas_limit)?; + + Ok(TransactionNonTyped { + nonce, + gas_price, + gas_limit, + to: to_address, + amount: eth_amount, + payload, + }) + } + + #[inline] + fn transaction_eip1559_from_proto( + input: &Proto::SigningInput, + eth_amount: U256, + payload: Data, + to_address: Option
, + ) -> SigningResult { + let nonce = U256::from_big_endian_slice(&input.nonce)?; + let gas_limit = U256::from_big_endian_slice(&input.gas_limit)?; + let max_inclusion_fee_per_gas = + U256::from_big_endian_slice(&input.max_inclusion_fee_per_gas)?; + let max_fee_per_gas = U256::from_big_endian_slice(&input.max_fee_per_gas)?; + + Ok(TransactionEip1559 { + nonce, + max_inclusion_fee_per_gas, + max_fee_per_gas, + gas_limit, + to: to_address, + amount: eth_amount, + payload, + }) + } + + fn user_operation_from_proto( + input: &Proto::SigningInput, + erc4337_payload: Data, + ) -> SigningResult { + let Some(ref user_op) = input.user_operation else { + return Err(SigningError(CommonError::Error_invalid_params)) + }; + + let nonce = U256::from_big_endian_slice(&input.nonce)?; + let gas_limit = U256::from_big_endian_slice(&input.gas_limit)?; + let max_inclusion_fee_per_gas = + U256::from_big_endian_slice(&input.max_inclusion_fee_per_gas)?; + let max_fee_per_gas = U256::from_big_endian_slice(&input.max_fee_per_gas)?; + + let entry_point = Self::parse_address(user_op.entry_point.as_ref())?; + let sender = Self::parse_address(user_op.sender.as_ref())?; + let verification_gas_limit = U256::from_big_endian_slice(&user_op.verification_gas_limit)?; + let pre_verification_gas = U256::from_big_endian_slice(&user_op.pre_verification_gas)?; + + Ok(UserOperation { + nonce, + entry_point, + sender, + init_code: user_op.init_code.to_vec(), + gas_limit, + verification_gas_limit, + max_fee_per_gas, + max_inclusion_fee_per_gas, + pre_verification_gas, + paymaster_and_data: user_op.paymaster_and_data.to_vec(), + payload: erc4337_payload, + }) + } + + #[inline] + fn parse_address(addr: &str) -> AddressResult
{ + Context::Address::from_str(addr).map(Context::Address::into) + } + + #[inline] + fn parse_address_optional(addr: &str) -> AddressResult> { + match Context::Address::from_str_optional(addr) { + Ok(Some(addr)) => Ok(Some(addr.into())), + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/rust/tw_evm/src/rlp/buffer.rs b/rust/tw_evm/src/rlp/buffer.rs new file mode 100644 index 00000000000..eea9d9e750e --- /dev/null +++ b/rust/tw_evm/src/rlp/buffer.rs @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_memory::Data; + +#[derive(Default)] +pub struct RlpBuffer { + stream: rlp::RlpStream, +} + +impl RlpBuffer { + /// cbindgen:ignore + const NO_ITEMS: usize = 0; + /// cbindgen:ignore + const ONE_ITEM: usize = 1; + + /// Creates `RlpBuffer` with a default capacity. + pub fn new() -> RlpBuffer { + RlpBuffer::default() + } + + /// Begins an RLP list. + /// Please note that it should be finalized using [`RlpBuffer::finalize_list`]. + pub(crate) fn begin_list(&mut self) { + self.stream.begin_unbounded_list(); + } + + /// Appends an item by its `bytes` representation. + /// This method is usually called from [`RlpEncode::rlp_append`]. + pub fn append_data(&mut self, bytes: &[u8]) { + self.stream.encoder().encode_value(bytes) + } + + /// Appends an empty list. + pub(crate) fn append_empty_list(&mut self) { + self.stream.begin_list(Self::NO_ITEMS); + } + + /// Appends an already encoded with all required headers value. + pub(crate) fn append_raw_encoded(&mut self, bytes: &[u8]) { + self.stream.append_raw(bytes, Self::ONE_ITEM); + } + + /// Finalize the list. + /// + /// # Panic + /// + /// The method may panic if [`RlpBuffer::begin_list`] hasn't been called before. + pub(crate) fn finalize_list(&mut self) { + self.stream.finalize_unbounded_list(); + } + + /// Streams out encoded bytes. + /// + /// # Panic + /// + /// Tte method panic if [`RlpBuffer::begin_list`] has been called without [`RlpBuffer::finalize_list`]. + pub fn finish(self) -> Data { + self.stream.out().into() + } +} diff --git a/rust/tw_evm/src/rlp/impls.rs b/rust/tw_evm/src/rlp/impls.rs new file mode 100644 index 00000000000..1927c01cff8 --- /dev/null +++ b/rust/tw_evm/src/rlp/impls.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::RlpEncode; +use tw_number::U256; + +impl RlpEncode for U256 { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(&self.to_big_endian_compact()) + } +} + +impl RlpEncode for Address { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(self.as_slice()) + } +} + +impl RlpEncode for Option
{ + fn rlp_append(&self, buf: &mut RlpBuffer) { + match self { + Some(ref addr) => addr.rlp_append(buf), + None => buf.append_data(&[]), + } + } +} + +impl<'a> RlpEncode for &'a [u8] { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(self) + } +} + +impl<'a> RlpEncode for &'a str { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(self.as_bytes()) + } +} diff --git a/rust/tw_evm/src/rlp/list.rs b/rust/tw_evm/src/rlp/list.rs new file mode 100644 index 00000000000..4bf58634d1f --- /dev/null +++ b/rust/tw_evm/src/rlp/list.rs @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::RlpEncode; +use tw_memory::Data; + +/// An RLP list of items. +pub struct RlpList { + buf: RlpBuffer, +} + +impl Default for RlpList { + fn default() -> Self { + RlpList::new() + } +} + +impl RlpList { + /// Creates a default `RlpList`. + pub fn new() -> RlpList { + let mut buf = RlpBuffer::new(); + buf.begin_list(); + RlpList { buf } + } + + /// Appends an item. + pub fn append(&mut self, item: T) -> &mut Self + where + T: RlpEncode, + { + item.rlp_append(&mut self.buf); + self + } + + /// Appends a sublist. + pub fn append_list(&mut self, list: RlpList) -> &mut Self { + let encoded_list = list.finish(); + self.buf.append_data(encoded_list.as_ref()); + self + } + + /// Appends an empty sublist. + pub fn append_empty_list(&mut self) -> &mut Self { + self.buf.append_empty_list(); + self + } + + /// Appends an already encoded with all required headers value. + pub fn append_raw_encoded(&mut self, encoded: &[u8]) -> &mut Self { + self.buf.append_raw_encoded(encoded); + self + } + + /// Finalizes the RLP list buffer. Returns encoded RLP list with a header. + #[must_use] + pub fn finish(mut self) -> Data { + self.buf.finalize_list(); + self.buf.finish() + } +} diff --git a/rust/tw_evm/src/rlp/mod.rs b/rust/tw_evm/src/rlp/mod.rs new file mode 100644 index 00000000000..88bbe1ff550 --- /dev/null +++ b/rust/tw_evm/src/rlp/mod.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::rlp::buffer::RlpBuffer; + +pub mod buffer; +pub mod impls; +pub mod list; + +/// The trait should be implemented for all types that need to be encoded in RLP. +pub trait RlpEncode { + fn rlp_append(&self, buf: &mut RlpBuffer); +} diff --git a/rust/tw_evm/src/signature.rs b/rust/tw_evm/src/signature.rs new file mode 100644 index 00000000000..0949cb8027e --- /dev/null +++ b/rust/tw_evm/src/signature.rs @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::ops::BitXor; +use tw_number::{NumberResult, U256}; + +/// EIP155 Eth encoding of V, of the form 27+v, or 35+chainID*2+v. +/// cbindgin:ignore +pub const ETHEREUM_SIGNATURE_V_OFFSET: u8 = 27; + +/// Embeds `chain_id` in `v` param, for replay protection, legacy or EIP155. +#[inline] +pub fn replay_protection(chain_id: U256, v: u8) -> NumberResult { + if chain_id.is_zero() { + legacy_replay_protection(v) + } else { + eip155_replay_protection(chain_id, v) + } +} + +/// Embeds `chain_id` in `v` param, for replay protection, legacy. +#[inline] +pub fn legacy_replay_protection(v: u8) -> NumberResult { + U256::from(v).checked_add(ETHEREUM_SIGNATURE_V_OFFSET) +} + +/// Embeds `chain_id` in `v` param, for replay protection, EIP155. +#[inline] +pub fn eip155_replay_protection(chain_id: U256, v: u8) -> NumberResult { + // chain_id + chain_id + 35u8 + v + chain_id + .checked_add(chain_id)? + .checked_add(35_u64)? + .checked_add(v) +} + +/// Removes EIP155 or legacy replay protection. +#[inline] +pub fn remove_replay_protection(v: u8) -> u8 { + const BIT_MASK: u8 = 0x01; + if v >= ETHEREUM_SIGNATURE_V_OFFSET { + return (v & BIT_MASK).bitxor(BIT_MASK); + } + v +} diff --git a/rust/tw_evm/src/transaction/mod.rs b/rust/tw_evm/src/transaction/mod.rs new file mode 100644 index 00000000000..a80521be11c --- /dev/null +++ b/rust/tw_evm/src/transaction/mod.rs @@ -0,0 +1,113 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +//! Transactions can be: +//! - Non-typed (legacy, pre-EIP2718) transactions: +//! -- simple ETH transfer +//! -- others with payload, function call, e.g. ERC20 transfer +//! - Typed transactions (enveloped, EIP2718), with specific type and transaction payload +//! - User operations (EIP4337) + +use crate::transaction::signature::EthSignature; +use tw_coin_entry::error::SigningResult; +use tw_hash::{sha3::keccak256, H256}; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; +use tw_number::U256; + +pub mod signature; +pub mod transaction_eip1559; +pub mod transaction_non_typed; +pub mod user_operation; + +pub trait TransactionCommon { + fn payload(&self) -> Data; +} + +pub trait UnsignedTransaction: TransactionCommon { + type SignedTransaction: SignedTransaction + 'static; + + fn pre_hash(&self, chain_id: U256) -> H256 { + let hash = keccak256(&self.encode(chain_id)); + H256::try_from(hash.as_slice()).expect("keccak256 returns 32 bytes") + } + + fn encode(&self, chain_id: U256) -> Data; + + fn try_into_signed( + self, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult; +} + +pub trait SignedTransaction: TransactionCommon { + type Signature: EthSignature; + + fn encode(&self) -> Data; + + fn signature(&self) -> &Self::Signature; +} + +pub trait UnsignedTransactionBox: TransactionCommon { + fn into_boxed(self) -> Box + where + Self: Sized + 'static, + { + Box::new(self) + } + + fn pre_hash(&self, chain_id: U256) -> H256; + + fn encode(&self, chain_id: U256) -> Data; + + fn try_into_signed( + self: Box, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult>; +} + +impl UnsignedTransactionBox for T +where + T: UnsignedTransaction, +{ + fn pre_hash(&self, chain_id: U256) -> H256 { + ::pre_hash(self, chain_id) + } + + fn encode(&self, chain_id: U256) -> Data { + ::encode(self, chain_id) + } + + fn try_into_signed( + self: Box, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult> { + let signed = ::try_into_signed(*self, signature, chain_id)?; + Ok(Box::new(signed)) + } +} + +pub trait SignedTransactionBox: TransactionCommon { + fn encode(&self) -> Data; + + fn signature(&self) -> &dyn EthSignature; +} + +impl SignedTransactionBox for T +where + T: SignedTransaction, +{ + fn encode(&self) -> Data { + ::encode(self) + } + + fn signature(&self) -> &dyn EthSignature { + ::signature(self) + } +} diff --git a/rust/tw_evm/src/transaction/signature.rs b/rust/tw_evm/src/transaction/signature.rs new file mode 100644 index 00000000000..198ca2e91bb --- /dev/null +++ b/rust/tw_evm/src/transaction/signature.rs @@ -0,0 +1,184 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signature::replay_protection; +use tw_hash::H520; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_number::U256; + +pub trait EthSignature { + fn v(&self) -> U256; + + fn r(&self) -> U256; + + fn s(&self) -> U256; +} + +/// R-S-V Signature values. +pub struct Signature { + v: U256, + r: U256, + s: U256, + rsv: H520, +} + +impl Signature { + #[inline] + pub fn new(sign: secp256k1::Signature) -> Self { + Signature { + v: U256::from(sign.v()), + r: U256::from_big_endian(sign.r()), + s: U256::from_big_endian(sign.s()), + rsv: sign.to_bytes(), + } + } + + #[inline] + pub fn to_rsv_bytes(&self) -> H520 { + self.rsv + } +} + +impl EthSignature for Signature { + #[inline] + fn v(&self) -> U256 { + self.v + } + + #[inline] + fn r(&self) -> U256 { + self.r + } + + #[inline] + fn s(&self) -> U256 { + self.s + } +} + +/// R-S-V Signature values EIP115. +pub struct SignatureEip155 { + v: U256, + r: U256, + s: U256, +} + +impl SignatureEip155 { + #[inline] + pub fn new(sign: secp256k1::Signature, chain_id: U256) -> KeyPairResult { + let v = + replay_protection(chain_id, sign.v()).map_err(|_| KeyPairError::InvalidSignature)?; + Ok(SignatureEip155 { + v, + r: U256::from_big_endian(sign.r()), + s: U256::from_big_endian(sign.s()), + }) + } +} + +impl EthSignature for SignatureEip155 { + #[inline] + fn v(&self) -> U256 { + self.v + } + + #[inline] + fn r(&self) -> U256 { + self.r + } + + #[inline] + fn s(&self) -> U256 { + self.s + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex; + use tw_hash::H256; + + #[test] + fn test_signature_no_eip155() { + let secp_bytes = hex::decode("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a00").unwrap(); + let secp_sign = secp256k1::Signature::from_bytes(secp_bytes.as_slice()).unwrap(); + + let eth_sign = Signature::new(secp_sign); + + let r = H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); + let s = H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); + let v = hex::decode("00").unwrap(); + + assert_eq!(eth_sign.r(), U256::from_big_endian(r)); + assert_eq!(eth_sign.s(), U256::from_big_endian(s)); + assert_eq!( + eth_sign.v(), + U256::from_big_endian_slice(v.as_slice()).unwrap() + ); + } + + #[test] + fn test_signature_eip155_legacy() { + let secp_bytes = hex::decode("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a00").unwrap(); + let secp_sign = secp256k1::Signature::from_bytes(secp_bytes.as_slice()).unwrap(); + + let chain_id = U256::zero(); + let eth_sign = SignatureEip155::new(secp_sign, chain_id).unwrap(); + + let r = H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); + let s = H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); + let v = hex::decode("1b").unwrap(); + + assert_eq!(eth_sign.r(), U256::from_big_endian(r)); + assert_eq!(eth_sign.s(), U256::from_big_endian(s)); + assert_eq!( + eth_sign.v(), + U256::from_big_endian_slice(v.as_slice()).unwrap() + ); + } + + #[test] + fn test_signature_eip155_v0() { + let secp_bytes = hex::decode("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a00").unwrap(); + let secp_sign = secp256k1::Signature::from_bytes(secp_bytes.as_slice()).unwrap(); + + let chain_id = U256::from(1_u64); + let eth_sign = SignatureEip155::new(secp_sign, chain_id).unwrap(); + + let r = H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); + let s = H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); + let v = hex::decode("25").unwrap(); + + assert_eq!(eth_sign.r(), U256::from_big_endian(r)); + assert_eq!(eth_sign.s(), U256::from_big_endian(s)); + assert_eq!( + eth_sign.v(), + U256::from_big_endian_slice(v.as_slice()).unwrap() + ); + } + + #[test] + fn test_signature_eip155_v1() { + let secp_bytes = hex::decode("42556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eaf172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b301").unwrap(); + let secp_sign = secp256k1::Signature::from_bytes(secp_bytes.as_slice()).unwrap(); + + let chain_id = U256::from(1_u64); + let eth_sign = SignatureEip155::new(secp_sign, chain_id).unwrap(); + + let r = H256::from("42556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eaf"); + let s = H256::from("172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"); + let v = hex::decode("26").unwrap(); + + assert_eq!(eth_sign.r(), U256::from_big_endian(r)); + assert_eq!(eth_sign.s(), U256::from_big_endian(s)); + assert_eq!( + eth_sign.v(), + U256::from_big_endian_slice(v.as_slice()).unwrap() + ); + } +} diff --git a/rust/tw_evm/src/transaction/transaction_eip1559.rs b/rust/tw_evm/src/transaction/transaction_eip1559.rs new file mode 100644 index 00000000000..352a5b53a8e --- /dev/null +++ b/rust/tw_evm/src/transaction/transaction_eip1559.rs @@ -0,0 +1,138 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::rlp::list::RlpList; +use crate::transaction::signature::{EthSignature, Signature}; +use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; +use tw_coin_entry::error::SigningResult; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; +use tw_number::U256; + +const EIP1559_TX_TYPE: u8 = 0x02; + +/// EIP1559 transaction. +pub struct TransactionEip1559 { + pub nonce: U256, + pub max_inclusion_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub to: Option
, + pub amount: U256, + pub payload: Data, +} + +impl TransactionCommon for TransactionEip1559 { + #[inline] + fn payload(&self) -> Data { + self.payload.clone() + } +} + +impl UnsignedTransaction for TransactionEip1559 { + type SignedTransaction = SignedTransactionEip1559; + + #[inline] + fn encode(&self, chain_id: U256) -> Data { + encode_transaction(self, chain_id, None) + } + + #[inline] + fn try_into_signed( + self, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult { + Ok(SignedTransactionEip1559 { + unsigned: self, + signature: Signature::new(signature), + chain_id, + }) + } +} + +pub struct SignedTransactionEip1559 { + unsigned: TransactionEip1559, + signature: Signature, + chain_id: U256, +} + +impl TransactionCommon for SignedTransactionEip1559 { + #[inline] + fn payload(&self) -> Data { + self.unsigned.payload.clone() + } +} + +impl SignedTransaction for SignedTransactionEip1559 { + type Signature = Signature; + + #[inline] + fn encode(&self) -> Data { + encode_transaction(&self.unsigned, self.chain_id, Some(&self.signature)) + } + + #[inline] + fn signature(&self) -> &Self::Signature { + &self.signature + } +} + +fn encode_transaction( + tx: &TransactionEip1559, + chain_id: U256, + signature: Option<&Signature>, +) -> Data { + let mut list = RlpList::new(); + list.append(chain_id) + .append(tx.nonce) + .append(tx.max_inclusion_fee_per_gas) + .append(tx.max_fee_per_gas) + .append(tx.gas_limit) + .append(tx.to) + .append(tx.amount) + .append(tx.payload.as_slice()) + // empty `access_list`. + .append_empty_list(); + + if let Some(signature) = signature { + list.append(signature.v()); + list.append(signature.r()); + list.append(signature.s()); + } + + let tx_encoded = list.finish(); + + let mut envelope = Vec::with_capacity(tx_encoded.len() + 1); + envelope.push(EIP1559_TX_TYPE); + envelope.extend_from_slice(tx_encoded.as_slice()); + envelope +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex; + + #[test] + fn test_encode_transaction_eip1559() { + let tx = TransactionEip1559 { + nonce: U256::from(6u64), + max_inclusion_fee_per_gas: U256::from(2_000_000_000u64), + max_fee_per_gas: U256::from(3_000_000_000u64), + gas_limit: U256::from(21100u32), + to: Some(Address::from("0x6b175474e89094c44da98b954eedeac495271d0f")), + amount: U256::zero(), + payload: hex::decode("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1").unwrap(), + }; + let chain_id = U256::from(10u64); + let actual = tx.encode(chain_id); + + let expected = "02f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0"; + assert_eq!(hex::encode(actual, false), expected); + } +} diff --git a/rust/tw_evm/src/transaction/transaction_non_typed.rs b/rust/tw_evm/src/transaction/transaction_non_typed.rs new file mode 100644 index 00000000000..67d386def60 --- /dev/null +++ b/rust/tw_evm/src/transaction/transaction_non_typed.rs @@ -0,0 +1,158 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::rlp::list::RlpList; +use crate::transaction::signature::{EthSignature, SignatureEip155}; +use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; +use tw_coin_entry::error::SigningResult; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; +use tw_number::U256; + +/// Original transaction format, with no explicit type, legacy as pre-EIP2718. +pub struct TransactionNonTyped { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub to: Option
, + pub amount: U256, + pub payload: Data, +} + +impl TransactionCommon for TransactionNonTyped { + #[inline] + fn payload(&self) -> Data { + self.payload.to_vec() + } +} + +impl UnsignedTransaction for TransactionNonTyped { + type SignedTransaction = SignedTransactionNonTyped; + + #[inline] + fn encode(&self, chain_id: U256) -> Data { + encode_transaction(self, chain_id, None) + } + + #[inline] + fn try_into_signed( + self, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult { + Ok(SignedTransactionNonTyped { + unsigned: self, + signature: SignatureEip155::new(signature, chain_id)?, + chain_id, + }) + } +} + +pub struct SignedTransactionNonTyped { + unsigned: TransactionNonTyped, + signature: SignatureEip155, + chain_id: U256, +} + +impl TransactionCommon for SignedTransactionNonTyped { + #[inline] + fn payload(&self) -> Data { + self.unsigned.payload.clone() + } +} + +impl SignedTransaction for SignedTransactionNonTyped { + type Signature = SignatureEip155; + + #[inline] + fn encode(&self) -> Data { + encode_transaction(&self.unsigned, self.chain_id, Some(&self.signature)) + } + + #[inline] + fn signature(&self) -> &Self::Signature { + &self.signature + } +} + +fn encode_transaction( + tx: &TransactionNonTyped, + chain_id: U256, + signature: Option<&SignatureEip155>, +) -> Data { + let mut list = RlpList::new(); + list.append(tx.nonce) + .append(tx.gas_price) + .append(tx.gas_limit) + .append(tx.to) + .append(tx.amount) + .append(tx.payload.as_slice()); + + let (v, r, s) = match signature { + Some(sign) => (sign.v(), sign.r(), sign.s()), + None => (chain_id, U256::zero(), U256::zero()), + }; + list.append(v).append(r).append(s); + list.finish() +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex; + + #[test] + fn test_encode_unsigned_non_typed_zero() { + let tx = TransactionNonTyped { + nonce: U256::zero(), + gas_price: U256::zero(), + gas_limit: U256::zero(), + to: Some(Address::default()), + amount: U256::zero(), + payload: Vec::new(), + }; + let chain_id = U256::zero(); + let actual = tx.encode(chain_id); + + let expected = "dd8080809400000000000000000000000000000000000000008080808080"; + assert_eq!(hex::encode(actual, false), expected); + } + + #[test] + fn test_encode_unsigned_non_typed() { + let tx = TransactionNonTyped { + nonce: U256::zero(), + gas_price: U256::from(42_000_000_000_u64), + gas_limit: U256::from(78009_u32), + to: Some(Address::from("0x6b175474e89094c44da98b954eedeac495271d0f")), + amount: U256::zero(), + payload: hex::decode("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000").unwrap(), + }; + let chain_id = U256::from(10_u8); + let actual = tx.encode(chain_id); + + let expected = "f86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec800000a8080"; + assert_eq!(hex::encode(actual, false), expected); + } + + #[test] + fn test_encode_unsigned_non_typed_pre_hash() { + let tx = TransactionNonTyped { + nonce: U256::from(9_u64), + gas_price: U256::from(20_000_000_000_u64), + gas_limit: U256::from(21_000_u64), + to: Some(Address::from("0x3535353535353535353535353535353535353535")), + amount: U256::from(1_000_000_000_000_000_000_u64), + payload: Vec::default(), + }; + let chain_id = U256::from(1_u64); + let actual = tx.pre_hash(chain_id); + + let expected = "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"; + assert_eq!(hex::encode(actual, false), expected); + } +} diff --git a/rust/tw_evm/src/transaction/user_operation.rs b/rust/tw_evm/src/transaction/user_operation.rs new file mode 100644 index 00000000000..6abe6bd048b --- /dev/null +++ b/rust/tw_evm/src/transaction/user_operation.rs @@ -0,0 +1,203 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::abi::encode::encode_tokens; +use crate::abi::non_empty_array::NonEmptyBytes; +use crate::abi::token::Token; +use crate::address::Address; +use crate::transaction::signature::Signature; +use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; +use serde::Serialize; +use tw_coin_entry::error::SigningResult; +use tw_encoding::hex; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_memory::Data; +use tw_number::U256; + +/// EIP4337 UserOperation +/// https://github.com/ethereum/EIPs/blob/3fd65b1a782912bfc18cb975c62c55f733c7c96e/EIPS/eip-4337.md#specification +pub struct UserOperation { + pub nonce: U256, + pub entry_point: Address, + pub sender: Address, + pub init_code: Data, + pub gas_limit: U256, + pub verification_gas_limit: U256, + pub max_fee_per_gas: U256, + pub max_inclusion_fee_per_gas: U256, + pub pre_verification_gas: U256, + pub paymaster_and_data: Data, + pub payload: Data, +} + +impl TransactionCommon for UserOperation { + #[inline] + fn payload(&self) -> Data { + self.payload.clone() + } +} + +impl UnsignedTransaction for UserOperation { + type SignedTransaction = SignedUserOperation; + + fn pre_hash(&self, chain_id: U256) -> H256 { + let encode_hash = keccak256(&self.encode(chain_id)); + let encode_hash = + NonEmptyBytes::new(encode_hash).expect("keccak256 must not return an empty hash"); + + let tokens = [ + Token::FixedBytes(encode_hash), + Token::Address(self.entry_point), + Token::u256(chain_id), + ]; + let encoded = encode_tokens(&tokens); + let pre_hash = keccak256(&encoded); + H256::try_from(pre_hash.as_slice()).expect("keccak256 returns 32 bytes") + } + + fn encode(&self, _chain_id: U256) -> Data { + let init_code_hash = keccak256(&self.init_code); + let init_code_hash = + NonEmptyBytes::new(init_code_hash).expect("keccak256 must not return an empty hash"); + + let payload_hash = keccak256(&self.payload); + let payload_hash = + NonEmptyBytes::new(payload_hash).expect("keccak256 must not return an empty hash"); + + let paymaster_and_data_hash = keccak256(&self.paymaster_and_data); + let paymaster_and_data_hash = NonEmptyBytes::new(paymaster_and_data_hash) + .expect("keccak256 must not return an empty hash"); + + let tokens = [ + Token::Address(self.sender), + Token::u256(self.nonce), + Token::FixedBytes(init_code_hash), + Token::FixedBytes(payload_hash), + Token::u256(self.gas_limit), + Token::u256(self.verification_gas_limit), + Token::u256(self.pre_verification_gas), + Token::u256(self.max_fee_per_gas), + Token::u256(self.max_inclusion_fee_per_gas), + Token::FixedBytes(paymaster_and_data_hash), + ]; + + encode_tokens(&tokens) + } + + #[inline] + fn try_into_signed( + self, + signature: tw_keypair::ecdsa::secp256k1::Signature, + _chain_id: U256, + ) -> SigningResult { + Ok(SignedUserOperation { + unsigned: self, + signature: Signature::new(signature), + }) + } +} + +pub struct SignedUserOperation { + unsigned: UserOperation, + signature: Signature, +} + +impl TransactionCommon for SignedUserOperation { + #[inline] + fn payload(&self) -> Data { + self.unsigned.payload.clone() + } +} + +impl SignedTransaction for SignedUserOperation { + type Signature = Signature; + + fn encode(&self) -> Data { + let mut signature = self.signature.to_rsv_bytes(); + signature[64] += 27; + + let prefix = true; + let tx = SignedUserOperationSerde { + call_data: hex::encode(&self.unsigned.payload, prefix), + call_gas_limit: self.unsigned.gas_limit.to_string(), + init_code: hex::encode(&self.unsigned.init_code, prefix), + max_fee_per_gas: self.unsigned.max_fee_per_gas.to_string(), + max_priority_fee_per_gas: self.unsigned.max_inclusion_fee_per_gas.to_string(), + nonce: self.unsigned.nonce.to_string(), + paymaster_and_data: hex::encode(&self.unsigned.paymaster_and_data, prefix), + pre_verification_gas: self.unsigned.pre_verification_gas.to_string(), + sender: self.unsigned.sender.to_string(), + signature: hex::encode(signature.as_slice(), prefix), + verification_gas_limit: self.unsigned.verification_gas_limit.to_string(), + }; + serde_json::to_string(&tx) + .expect("Simple structure should never fail on serialization") + .into_bytes() + } + + #[inline] + fn signature(&self) -> &Self::Signature { + &self.signature + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct SignedUserOperationSerde { + call_data: String, + call_gas_limit: String, + init_code: String, + max_fee_per_gas: String, + max_priority_fee_per_gas: String, + nonce: String, + paymaster_and_data: String, + pre_verification_gas: String, + sender: String, + signature: String, + verification_gas_limit: String, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs}; + + #[test] + fn test_encode_user_operation() { + let chain_id = U256::from(97u64); + + let execute_args = ExecuteArgs { + to: Address::from("0x61061fCAE11fD5461535e134EfF67A98CFFF44E9"), + value: U256::from(0x2_386f_26fc_10000u64), + data: Vec::default(), + }; + let payload = Erc4337SimpleAccount::encode_execute(execute_args).unwrap(); + + let user_op = UserOperation { + nonce: U256::from(2u64), + entry_point: Address::from("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), + sender: Address::from("0xb16Db98B365B1f89191996942612B14F1Da4Bd5f"), + init_code: Vec::default(), + gas_limit: U256::from(0x186a0u64), + verification_gas_limit: U256::from(0x186a0u64), + max_fee_per_gas: U256::from(0x1_a339_c9e9u64), + max_inclusion_fee_per_gas: U256::from(0x1_a339_c9e9u64), + pre_verification_gas: U256::from(0xb708u64), + paymaster_and_data: Vec::default(), + payload, + }; + + let encoded = hex::encode(user_op.encode(chain_id), false); + let expected = "000000000000000000000000b16db98b365b1f89191996942612b14f1da4bd5f0000000000000000000000000000000000000000000000000000000000000002c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470fbec3c1db0378685d954edd265aa6eb11e8474d828e6bda151810263838e457000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000000000000000000000000000000000000000b70800000000000000000000000000000000000000000000000000000001a339c9e900000000000000000000000000000000000000000000000000000001a339c9e9c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; + assert_eq!(encoded, expected); + + let pre_hash = user_op.pre_hash(chain_id); + let expected_pre_hash = + H256::from("2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae"); + assert_eq!(pre_hash, expected_pre_hash); + } +} diff --git a/rust/tw_evm/tests/abi_encoder.rs b/rust/tw_evm/tests/abi_encoder.rs new file mode 100644 index 00000000000..b94533bbb27 --- /dev/null +++ b/rust/tw_evm/tests/abi_encoder.rs @@ -0,0 +1,689 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::{json, Value as Json}; +use std::borrow::Cow; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_evm::abi::AbiErrorKind; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::abi_encoder::AbiEncoder; +use tw_number::{I256, U256}; +use tw_proto::EthereumAbi::Proto; + +use Proto::mod_ParamType::OneOfparam as ParamTypeEnum; +use Proto::mod_ParamsDecodingInput::OneOfabi as AbiEnum; +use Proto::mod_Token::OneOftoken as TokenEnum; + +const SWAP_V2_ABI: &str = include_str!("data/swap_v2.json"); +const SWAP_V2_DECODED: &str = include_str!("data/swap_v2_decoded.json"); + +fn param(name: &str, kind: ParamTypeEnum<'static>) -> Proto::Param<'static> { + Proto::Param { + name: name.to_string().into(), + param: Some(Proto::ParamType { param: kind }), + } +} + +fn named_token(name: &str, token: TokenEnum<'static>) -> Proto::Token<'static> { + Proto::Token { + name: Cow::Owned(name.to_string()), + token, + } +} + +fn u_number_n(value: u64) -> TokenEnum<'static> { + TokenEnum::number_uint(Proto::NumberNParam { + bits: BITS, + value: U256::encode_be_compact(value), + }) +} + +fn s_number_n(value: i64) -> TokenEnum<'static> { + TokenEnum::number_int(Proto::NumberNParam { + bits: BITS, + value: I256::encode_be_compact(value), + }) +} + +fn number_type_n() -> Proto::NumberNType { + Proto::NumberNType { bits: BITS } +} + +fn array( + element_type: ParamTypeEnum<'static>, + values: Vec>, +) -> Proto::ArrayParam<'static> { + let elements = values + .into_iter() + .map(|token| Proto::Token { + name: "".into(), + token, + }) + .collect(); + let element_type = Some(Proto::ParamType { + param: element_type, + }); + Proto::ArrayParam { + element_type, + elements, + } +} + +fn array_type(element_type: ParamTypeEnum<'static>) -> Box> { + Box::new(Proto::ArrayType { + element_type: Some(Box::new(Proto::ParamType { + param: element_type, + })), + }) +} + +fn tuple(params: I) -> TokenEnum<'static> +where + I: IntoIterator>, +{ + TokenEnum::tuple(Proto::TupleParam { + params: params.into_iter().collect(), + }) +} + +fn test_encode_contract_call_impl( + function_name: &str, + tokens: Vec>, + expected_call: &str, +) { + let input = Proto::FunctionEncodingInput { + function_name: function_name.into(), + tokens, + }; + + let output = AbiEncoder::::encode_contract_call(input); + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.encoded.to_hex(), expected_call); +} + +fn test_decode_params_impl( + encoded: &str, + expected_tokens: Vec>, + abi: AbiEnum<'_>, +) { + let input = Proto::ParamsDecodingInput { + encoded: encoded.decode_hex().unwrap().into(), + abi, + }; + + let output = AbiEncoder::::decode_params(input); + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.tokens, expected_tokens); +} + +fn test_decode_contract_call_impl( + encoded: &str, + abi_json: &str, + decoded_json: &str, + expected_tokens: Vec>, +) { + let input = Proto::ContractCallDecodingInput { + encoded: encoded.decode_hex().unwrap().into(), + smart_contract_abi_json: Cow::Borrowed(abi_json), + }; + + let output = AbiEncoder::::decode_contract_call(input); + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + + let actual_json: Json = serde_json::from_str(&output.decoded_json).unwrap(); + let expected_json: Json = serde_json::from_str(decoded_json).unwrap(); + assert_eq!(actual_json, expected_json); + assert_eq!(output.tokens, expected_tokens); +} + +mod swap_v2 { + use super::*; + + const FUNCTION_NAME: &str = "callBridgeCall"; + const ENCODED_CALL: &str = "846a1bc6000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000820000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000003840651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000762696e616e636500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + fn calls_array_type() -> ParamTypeEnum<'static> { + ParamTypeEnum::tuple(Proto::TupleType { + params: vec![ + param("callType", ParamTypeEnum::number_uint(number_type_n::<8>())), + param("target", ParamTypeEnum::address(Proto::AddressType {})), + param("value", ParamTypeEnum::number_uint(number_type_n::<256>())), + param( + "callData", + ParamTypeEnum::byte_array(Proto::ByteArrayType {}), + ), + param( + "payload", + ParamTypeEnum::byte_array(Proto::ByteArrayType {}), + ), + ], + }) + } + + fn token_values() -> Vec> { + let call_data_1 = "0x095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000".decode_hex().unwrap(); + let call_data_2 = "0x0651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666".decode_hex().unwrap(); + + let calls = vec![ + tuple(vec![ + named_token("callType", u_number_n::<8>(0)), + named_token( + "target", + TokenEnum::address("0xdAC17F958D2ee523a2206206994597C13D831ec7".into()), + ), + named_token("value", u_number_n::<256>(0)), + named_token("callData", TokenEnum::byte_array(call_data_1.into())), + named_token("payload", TokenEnum::byte_array(Cow::default())), + ]), + tuple(vec![ + named_token("callType", u_number_n::<8>(0)), + named_token( + "target", + TokenEnum::address("0x99a58482BD75cbab83b27EC03CA68fF489b5788f".into()), + ), + named_token("value", u_number_n::<256>(0)), + named_token("callData", TokenEnum::byte_array(call_data_2.into())), + named_token("payload", TokenEnum::byte_array(Cow::default())), + ]), + ]; + + let payload = "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap(); + vec![ + named_token( + "token", + TokenEnum::address("0xdAC17F958D2ee523a2206206994597C13D831ec7".into()), + ), + named_token("amount", u_number_n::<256>(20000000000000000)), + named_token("calls", TokenEnum::array(array(calls_array_type(), calls))), + named_token("bridgedTokenSymbol", TokenEnum::string_value("USDC".into())), + named_token( + "destinationChain", + TokenEnum::string_value("binance".into()), + ), + named_token( + "destinationAddress", + TokenEnum::string_value("0xce16F69375520ab01377ce7B88f5BA8C48F8D666".into()), + ), + named_token("payload", TokenEnum::byte_array(payload.into())), + named_token( + "gasRefundRecipient", + TokenEnum::address("0xa140F413C63FBDA84E9008607E678258ffFbC76b".into()), + ), + named_token("enableExpress", TokenEnum::boolean(true)), + ] + } + + #[test] + fn test_decode_contract_call_swap_v2() { + test_decode_contract_call_impl(ENCODED_CALL, SWAP_V2_ABI, SWAP_V2_DECODED, token_values()); + } + + #[test] + fn test_encode_contract_call_swap_v2() { + test_encode_contract_call_impl(FUNCTION_NAME, token_values(), ENCODED_CALL); + } +} + +#[test] +fn test_decode_params_with_abi_json() { + let abi_json = json!([ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ]); + let abi_json = serde_json::to_string(&abi_json).unwrap(); + let encoded = "e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"; + + let node_bytes = "0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f" + .decode_hex() + .unwrap(); + let expected_tokens = vec![ + named_token("node", TokenEnum::byte_array_fix(node_bytes.into())), + named_token( + "resolver", + TokenEnum::address("0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41".into()), + ), + ]; + + test_decode_params_impl(encoded, expected_tokens, AbiEnum::abi_json(abi_json.into())); +} + +#[test] +fn test_decode_params_with_abi_params() { + let encoded = "e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"; + + let node_bytes = "0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f" + .decode_hex() + .unwrap(); + let expected_tokens = vec![ + named_token("node", TokenEnum::byte_array_fix(node_bytes.into())), + named_token( + "resolver", + TokenEnum::address("0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41".into()), + ), + ]; + + let abi_params = vec![ + param( + "node", + ParamTypeEnum::byte_array_fix(Proto::ByteArrayFixType { size: 32 }), + ), + param("resolver", ParamTypeEnum::address(Proto::AddressType {})), + ]; + + test_decode_params_impl( + encoded, + expected_tokens, + AbiEnum::abi_params(Proto::AbiParams { params: abi_params }), + ); +} + +mod dynamic_arguments { + use super::*; + + const FUNCTION_NAME: &str = "f"; + const ENCODED_CALL_WITH_SIGNATURE: &str = "47b941bf00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000"; + const ENCODED_CALL: &str = "00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000"; + + fn token_values() -> Vec> { + let dynamic_array = array( + ParamTypeEnum::number_uint(number_type_n::<32>()), + vec![u_number_n::<32>(0x456), u_number_n::<32>(0x789)], + ); + let byte_array = vec![0x31u8, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30]; + vec![ + named_token("", u_number_n::<256>(0x123)), + named_token("", TokenEnum::array(dynamic_array)), + named_token("", TokenEnum::byte_array_fix(byte_array.into())), + named_token("", TokenEnum::string_value("Hello, world!".into())), + ] + } + + fn param_types() -> Vec> { + vec![ + param("", ParamTypeEnum::number_uint(number_type_n::<256>())), + param( + "", + ParamTypeEnum::array(array_type( + ParamTypeEnum::number_uint(number_type_n::<32>()), + )), + ), + param( + "", + ParamTypeEnum::byte_array_fix(Proto::ByteArrayFixType { size: 10 }), + ), + param("", ParamTypeEnum::string_param(Proto::StringType {})), + ] + } + + #[test] + fn test_encode_contract_call_with_dynamic_arguments() { + test_encode_contract_call_impl(FUNCTION_NAME, token_values(), ENCODED_CALL_WITH_SIGNATURE); + } + + #[test] + fn test_decode_params_with_dynamic_arguments_case2() { + test_decode_params_impl( + ENCODED_CALL, + token_values(), + AbiEnum::abi_params(Proto::AbiParams { + params: param_types(), + }), + ); + } +} + +mod negative_integers { + use super::*; + + const FUNCTION_NAME: &str = "approve"; + const ENCODED_CALL_WITH_SIGNATURE: &str = "d4e12f2e0000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaedfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"; + const ENCODED_CALL: &str = "0000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaedfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"; + + const ABI_JSON: &str = include_str!("data/abi_with_negative_int.json"); + const DECODED_JSON: &str = include_str!("data/decoded_abi_with_negative_int.json"); + + fn token_values() -> Vec> { + vec![ + named_token( + "_spender", + TokenEnum::address("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".into()), + ), + named_token("_value", s_number_n::<256>(-3)), + ] + } + + fn param_types() -> Vec> { + vec![ + param("_spender", ParamTypeEnum::address(Proto::AddressType {})), + param("_value", ParamTypeEnum::number_int(number_type_n::<256>())), + ] + } + + #[test] + fn test_encode_contract_call_with_negative_integer() { + test_encode_contract_call_impl(FUNCTION_NAME, token_values(), ENCODED_CALL_WITH_SIGNATURE); + } + + #[test] + fn test_decode_params_with_negative_integer() { + test_decode_params_impl( + ENCODED_CALL, + token_values(), + AbiEnum::abi_params(Proto::AbiParams { + params: param_types(), + }), + ); + } + + #[test] + fn test_decode_contract_call_with_negative_integer() { + test_decode_contract_call_impl( + ENCODED_CALL_WITH_SIGNATURE, + ABI_JSON, + DECODED_JSON, + token_values(), + ); + } +} + +#[test] +fn test_encode_params_monster() { + let byte_array = "3132333435".decode_hex().unwrap(); + + let u1 = u_number_n::<8>(1); + let u2 = u_number_n::<16>(2); + let u3 = u_number_n::<32>(3); + let u4 = u_number_n::<64>(4); + let u5 = u_number_n::<168>(0x123); + let u6 = u_number_n::<256>(0x123); + + let u1t = ParamTypeEnum::number_uint(number_type_n::<8>()); + let u2t = ParamTypeEnum::number_uint(number_type_n::<16>()); + let u3t = ParamTypeEnum::number_uint(number_type_n::<32>()); + let u4t = ParamTypeEnum::number_uint(number_type_n::<64>()); + let u5t = ParamTypeEnum::number_uint(number_type_n::<168>()); + let u6t = ParamTypeEnum::number_uint(number_type_n::<256>()); + + let i1 = s_number_n::<8>(1); + let i2 = s_number_n::<16>(2); + let i3 = s_number_n::<32>(3); + let i4 = s_number_n::<64>(4); + let i5 = s_number_n::<168>(0x123); + let i6 = s_number_n::<256>(0x123); + + let i1t = ParamTypeEnum::number_int(number_type_n::<8>()); + let i2t = ParamTypeEnum::number_int(number_type_n::<16>()); + let i3t = ParamTypeEnum::number_int(number_type_n::<32>()); + let i4t = ParamTypeEnum::number_int(number_type_n::<64>()); + let i5t = ParamTypeEnum::number_int(number_type_n::<168>()); + let i6t = ParamTypeEnum::number_int(number_type_n::<256>()); + + let b = TokenEnum::boolean(true); + let bt = ParamTypeEnum::boolean(Proto::BoolType {}); + let s = TokenEnum::string_value("Hello, world!".into()); + let st = ParamTypeEnum::string_param(Proto::StringType {}); + let a = TokenEnum::address("0xf784682c82526e245f50975190ef0fff4e4fc077".into()); + let at = ParamTypeEnum::address(Proto::AddressType {}); + let bytes = TokenEnum::byte_array(byte_array.clone().into()); + let bytes_t = ParamTypeEnum::byte_array(Proto::ByteArrayType {}); + let fbytes = TokenEnum::byte_array_fix(byte_array.clone().into()); + let fbytes_t = ParamTypeEnum::byte_array_fix(Proto::ByteArrayFixType { + size: byte_array.len() as u64, + }); + + let tokens = vec![ + // Uint + named_token("u1", u1.clone()), + named_token("u2", u2.clone()), + named_token("u3", u3.clone()), + named_token("u4", u4.clone()), + named_token("u5", u5.clone()), + named_token("u6", u6.clone()), + // Int + named_token("i1", i1.clone()), + named_token("i2", i2.clone()), + named_token("i3", i3.clone()), + named_token("i4", i4.clone()), + named_token("i5", i5.clone()), + named_token("i6", i6.clone()), + // Single params + named_token("b", b.clone()), + named_token("s", s.clone()), + named_token("a", a.clone()), + named_token("bytes", bytes.clone()), + named_token("fbytes", fbytes.clone()), + // Array + named_token("a_u1", TokenEnum::array(array(u1t, vec![u1]))), + named_token("a_u2", TokenEnum::array(array(u2t, vec![u2]))), + named_token("a_u3", TokenEnum::array(array(u3t, vec![u3]))), + named_token("a_u4", TokenEnum::array(array(u4t, vec![u4]))), + named_token("a_u5", TokenEnum::array(array(u5t, vec![u5]))), + named_token("a_u6", TokenEnum::array(array(u6t, vec![u6]))), + // Array + named_token("a_i1", TokenEnum::array(array(i1t, vec![i1]))), + named_token("a_i2", TokenEnum::array(array(i2t, vec![i2]))), + named_token("a_i3", TokenEnum::array(array(i3t, vec![i3]))), + named_token("a_i4", TokenEnum::array(array(i4t, vec![i4]))), + named_token("a_i5", TokenEnum::array(array(i5t, vec![i5]))), + named_token("a_i6", TokenEnum::array(array(i6t, vec![i6]))), + // Arrays with single params + named_token("a_b", TokenEnum::array(array(bt, vec![b]))), + named_token("a_s", TokenEnum::array(array(st, vec![s]))), + named_token("a_a", TokenEnum::array(array(at, vec![a]))), + named_token("a_bytes", TokenEnum::array(array(bytes_t, vec![bytes]))), + named_token("a_fbytes", TokenEnum::array(array(fbytes_t, vec![fbytes]))), + ]; + let encoding_input = Proto::FunctionEncodingInput { + function_name: "monster".into(), + tokens, + }; + let output = AbiEncoder::::encode_contract_call(encoding_input); + + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + + // let expected_encoded = "4061f075000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077000000000000000000000000000000000000000000000000000000000000030031323334350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005c00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000531323334350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013132333435000000000000000000000000000000000000000000000000000000"; + let expected_encoded = "70efb5a500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000440000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000000000000000000000000000000000000000000480313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000531323334350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013132333435000000000000000000000000000000000000000000000000000000"; + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_decode_value() { + struct TestInput { + encoded: &'static str, + kind: &'static str, + expected_value_str: &'static str, + expected_value_proto: TokenEnum<'static>, + } + + let num1 = u_number_n::<8>(49); + let num2 = u_number_n::<8>(50); + let num3 = u_number_n::<8>(51); + let address1 = TokenEnum::address("0xF784682C82526e245F50975190EF0fff4E4fC077".into()); + let address2 = TokenEnum::address("0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3".into()); + let bytes1 = TokenEnum::byte_array("0x1011".decode_hex().unwrap().into()); + let bytes2 = TokenEnum::byte_array("0x102222".decode_hex().unwrap().into()); + + let test_values = [ + TestInput { + encoded: "0000000000000000000000000000000000000000000000000000091d0eb3e2af", + kind: "uint256", + expected_value_str: "10020405371567", + expected_value_proto: u_number_n::<256>(10020405371567), + }, + TestInput { + encoded: "0000000000000000000000000000000000000000000000000000091d0eb3e2af0000000000000000000000000000000000000000000000000000000000000000", + kind: "int256", + expected_value_str: "10020405371567", + expected_value_proto: s_number_n::<256>(10020405371567), + }, + TestInput { + encoded: "000000000000000000000000000000000000000000000000000000000000002a", + kind: "uint", + expected_value_str: "42", + expected_value_proto: u_number_n::<256>(42), + }, + TestInput { + encoded: "0000000000000000000000000000000000000000000000000000000000000018", + kind: "uint8", + expected_value_str: "24", + expected_value_proto: u_number_n::<8>(24), + }, + TestInput { + encoded: "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033", + kind: "uint8[]", + expected_value_str: "[49,50,51]", + expected_value_proto: TokenEnum::array(array(ParamTypeEnum::number_uint(number_type_n::<8>()), vec![num1, num2, num3])), + }, + TestInput { + encoded: "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077", + kind: "address", + expected_value_str: "0xF784682C82526e245F50975190EF0fff4E4fC077", + expected_value_proto: TokenEnum::address( + "0xF784682C82526e245F50975190EF0fff4E4fC077".into(), + ), + }, + TestInput { + encoded: "000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000", + kind: "string", + expected_value_str: "Hello World! Hello World! Hello World!", + expected_value_proto: TokenEnum::string_value( + "Hello World! Hello World! Hello World!".into(), + ), + }, + TestInput { + encoded: "0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3", + kind: "address[]", + expected_value_str: r#"["0xF784682C82526e245F50975190EF0fff4E4fC077","0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3"]"#, + expected_value_proto: TokenEnum::array( + array(ParamTypeEnum::address(Proto::AddressType {}), vec![address1, address2])), + }, + TestInput { + encoded: "3132333435363738393000000000000000000000000000000000000000000000", + kind: "bytes10", + expected_value_str: "0x31323334353637383930", + expected_value_proto: TokenEnum::byte_array_fix( + "0x31323334353637383930".decode_hex().unwrap().into(), + ), + }, + TestInput { + encoded: "0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031022220000000000000000000000000000000000000000000000000000000000", + kind: "bytes[]", + expected_value_str: r#"["0x1011","0x102222"]"#, + expected_value_proto: TokenEnum::array( + array(ParamTypeEnum::byte_array(Proto::ByteArrayType {}), vec![bytes1, bytes2]) + ), + }, + ]; + + for test in test_values { + let input = Proto::ValueDecodingInput { + encoded: test.encoded.decode_hex().unwrap().into(), + param_type: test.kind.into(), + }; + let output = AbiEncoder::::decode_value(input); + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + + assert_eq!(output.token.unwrap().token, test.expected_value_proto); + assert_eq!(output.param_str, test.expected_value_str); + } +} + +#[test] +fn test_decode_value_error() { + #[track_caller] + fn test_decode_value_error_impl(kind: &str, encoded: &str) { + let input = Proto::ValueDecodingInput { + encoded: encoded.decode_hex().unwrap().into(), + param_type: kind.into(), + }; + let output = AbiEncoder::::decode_value(input); + assert_ne!(output.error, AbiErrorKind::OK); + assert!(!output.error_message.is_empty()); + } + + test_decode_value_error_impl("tuple[7][][2][2]", "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000375696e742a6c"); + test_decode_value_error_impl("bytes0[0][][2][6]", "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000375696e742a6c"); +} + +#[test] +fn test_encode_params_invalid_address() { + struct TestInput { + token: TokenEnum<'static>, + error: AbiErrorKind, + } + + let test_inputs = [ + // No address '0x' prefix. + TestInput { + token: TokenEnum::address("f784682c82526e245f50975190ef0fff4e4fc077".into()), + error: AbiErrorKind::Error_invalid_address_value, + }, + // There is no uint0. + TestInput { + token: u_number_n::<0>(0), + error: AbiErrorKind::Error_invalid_uint_value, + }, + // There is no int7. + TestInput { + token: s_number_n::<7>(1), + error: AbiErrorKind::Error_invalid_uint_value, + }, + // There is no uint512. + TestInput { + token: u_number_n::<512>(123), + error: AbiErrorKind::Error_invalid_uint_value, + }, + // ArrayType::element_type and token mismatch. + TestInput { + token: TokenEnum::array(array( + ParamTypeEnum::number_uint(number_type_n::<8>()), + vec![u_number_n::<256>(1000)], + )), + error: AbiErrorKind::Error_invalid_param_type, + }, + ]; + + for TestInput { token, error } in test_inputs { + let encoding_input = Proto::FunctionEncodingInput { + function_name: "NonExisting".into(), + tokens: vec![named_token("", token.clone())], + }; + + let output = AbiEncoder::::encode_contract_call(encoding_input); + assert_eq!( + output.error, error, + "Expected error on encoding {:?}", + token + ); + assert!(!output.error_message.is_empty()); + } +} + +#[test] +fn test_decode_contract_call_error() { + let encoded_input = "11223344000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000820000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000003840651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000762696e616e636500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap(); + let input = Proto::ContractCallDecodingInput { + encoded: Cow::Owned(encoded_input), + smart_contract_abi_json: Cow::Borrowed(SWAP_V2_ABI), + }; + + let output = AbiEncoder::::decode_contract_call(input); + assert_eq!(output.error, AbiErrorKind::Error_abi_mismatch); + assert!(!output.error_message.is_empty()); +} diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs new file mode 100644 index 00000000000..270bd958ae2 --- /dev/null +++ b/rust/tw_evm/tests/barz.rs @@ -0,0 +1,194 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex; +use tw_evm::abi::prebuild::erc20::Erc20; +use tw_evm::address::Address; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::signer::Signer; +use tw_number::U256; +use tw_proto::Ethereum::Proto; + +// https://testnet.bscscan.com/tx/0x43fc13dfdf06bbb09da8ce070953753764f1e43782d0c8b621946d8b45749419 +#[test] +fn test_barz_transfer_account_deployed() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(0x23_86f2_6fc1_0000), + data: Cow::default(), + }; + let user_op = Proto::UserOperation { + entry_point: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".into(), + init_code: Cow::default(), + sender: "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f".into(), + pre_verification_gas: U256::encode_be_compact(0xb708), + verification_gas_limit: U256::encode_be_compact(0x186a0), + paymaster_and_data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(97), + nonce: U256::encode_be_compact(2), + tx_mode: Proto::TransactionMode::UserOp, + gas_price: Cow::default(), + gas_limit: U256::encode_be_compact(0x186A0), + max_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), + max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), + to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + user_operation: Some(user_op), + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = r#"{"callData":"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000","callGasLimit":"100000","initCode":"0x","maxFeePerGas":"7033440745","maxPriorityFeePerGas":"7033440745","nonce":"2","paymasterAndData":"0x","preVerificationGas":"46856","sender":"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f","signature":"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c","verificationGasLimit":"100000"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae" + ); +} + +// // https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 +#[test] +fn test_barz_transfer_account_not_deployed() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + // TODO generate `init_code` with Barz when it's moved to Rust. + // See: https://github.com/trustwallet/wallet-core/blob/f266567e913a40bfee80b8ecdd4b74785cea2b32/tests/chains/Ethereum/BarzTests.cpp#L160 + let init_code = hex::decode("3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(0x23_86f2_6fc1_0000), + data: Cow::default(), + }; + let user_op = Proto::UserOperation { + entry_point: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".into(), + init_code: init_code.into(), + sender: "0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218".into(), + pre_verification_gas: U256::encode_be_compact(0xb708), + verification_gas_limit: U256::encode_be_compact(0x2D_C6C0), + paymaster_and_data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(97), + nonce: U256::encode_be_compact(0), + tx_mode: Proto::TransactionMode::UserOp, + gas_price: Cow::default(), + gas_limit: U256::encode_be_compact(0x26_25A0), + max_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), + max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), + to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + user_operation: Some(user_op), + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = r#"{"callData":"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000","callGasLimit":"2500000","initCode":"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000","maxFeePerGas":"7033440745","maxPriorityFeePerGas":"7033440745","nonce":"0","paymasterAndData":"0x","preVerificationGas":"46856","sender":"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218","signature":"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b","verificationGasLimit":"3000000"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937" + ); +} + +// // https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 +#[test] +fn test_barz_batched_account_deployed() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let contract_address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266"; + + let mut calls = Vec::with_capacity(2); + + // ERC20 approve + { + let spender = Address::from("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + let amount = U256::from(0x8AC7_2304_89E8_0000_u64); + let payload = Erc20::approve(spender, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + address: contract_address.into(), + amount: Cow::default(), + payload: payload.into(), + }); + }; + + // ERC20 transfer + { + let recipient = Address::from("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + let amount = U256::from(0x8AC7_2304_89E8_0000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + address: contract_address.into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + let user_op = Proto::UserOperation { + entry_point: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".into(), + init_code: Cow::default(), + sender: "0x1e6c542ebc7c960c6a155a9094db838cef842cf5".into(), + pre_verification_gas: U256::encode_be_compact(0xDAFC), + verification_gas_limit: U256::encode_be_compact(0x07_F7C4), + paymaster_and_data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(97), + nonce: U256::encode_be_compact(3), + tx_mode: Proto::TransactionMode::UserOp, + gas_price: Cow::default(), + gas_limit: U256::encode_be_compact(0x01_5A61), + max_fee_per_gas: U256::encode_be_compact(0x02_540B_E400), + max_inclusion_fee_per_gas: U256::encode_be_compact(0x02_540B_E400), + to_address: contract_address.into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( + Proto::mod_Transaction::Batch { calls }, + ), + }), + user_operation: Some(user_op), + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = r#"{"callData":"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000","callGasLimit":"88673","initCode":"0x","maxFeePerGas":"10000000000","maxPriorityFeePerGas":"10000000000","nonce":"3","paymasterAndData":"0x","preVerificationGas":"56060","sender":"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5","signature":"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c","verificationGasLimit":"522180"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab" + ); +} diff --git a/rust/tw_evm/tests/data/abi_with_negative_int.json b/rust/tw_evm/tests/data/abi_with_negative_int.json new file mode 100644 index 00000000000..c5b59d3e84d --- /dev/null +++ b/rust/tw_evm/tests/data/abi_with_negative_int.json @@ -0,0 +1,20 @@ +{ + "d4e12f2e": { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "int256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/decoded_abi_with_negative_int.json b/rust/tw_evm/tests/data/decoded_abi_with_negative_int.json new file mode 100644 index 00000000000..0165692cb82 --- /dev/null +++ b/rust/tw_evm/tests/data/decoded_abi_with_negative_int.json @@ -0,0 +1,15 @@ +{ + "function": "approve(address,int256)", + "inputs": [ + { + "name": "_spender", + "value": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "type": "address" + }, + { + "name": "_value", + "value": "-3", + "type": "int256" + } + ] +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_case_1.json b/rust/tw_evm/tests/data/eip712_case_1.json new file mode 100644 index 00000000000..22e6b14317f --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_case_1.json @@ -0,0 +1,64 @@ +{ + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + }, + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_case_2.json b/rust/tw_evm/tests/data/eip712_case_2.json new file mode 100644 index 00000000000..3155577ea67 --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_case_2.json @@ -0,0 +1,83 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallets", + "type": "address[]" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[]" + }, + { + "name": "contents", + "type": "string" + } + ], + "Group": [ + { + "name": "name", + "type": "string" + }, + { + "name": "members", + "type": "Person[]" + } + ] + }, + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "primaryType": "Mail", + "message": { + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ], + "contents": "Hello, Bob!" + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_case_3.json b/rust/tw_evm/tests/data/eip712_case_3.json new file mode 100644 index 00000000000..1c1da8c9d85 --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_case_3.json @@ -0,0 +1,43 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_fixed_bytes.json b/rust/tw_evm/tests/data/eip712_fixed_bytes.json new file mode 100644 index 00000000000..563c8eaaa37 --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_fixed_bytes.json @@ -0,0 +1,83 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + }, + { + "name": "salt", + "type": "bytes32" + } + ], + "Trade": [ + { + "name": "nonce", + "type": "bytes32" + }, + { + "name": "firstParty", + "type": "address" + }, + { + "name": "askingId", + "type": "uint256" + }, + { + "name": "askingQty", + "type": "uint256" + }, + { + "name": "offeringId", + "type": "uint256" + }, + { + "name": "offeringQty", + "type": "uint256" + }, + { + "name": "maxFee", + "type": "uint256" + }, + { + "name": "secondParty", + "type": "address" + }, + { + "name": "count", + "type": "uint8" + } + ] + }, + "domain": { + "name": "CryptoFights Trading", + "version": "1", + "chainId": 1, + "verifyingContract": "0xdc45529aC0FA3185f79A005e57deF64F600c4e97", + "salt": "0x0" + }, + "primaryType": "Trade", + "message": { + "count": 1, + "offeringQty": 1, + "askingQty": 2, + "nonce": "0xcfe49aa546453df3f2e768a97204a3268cef7c27df53cc2f2d47385cfeaf", + "firstParty": "0xC36edF48e21cf395B206352A1819DE658fD7f988", + "askingId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "offeringId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "maxFee": "1000000000000000000", + "secondParty": "0x0000000000000000000000000000000000000000" + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_greenfield.json b/rust/tw_evm/tests/data/eip712_greenfield.json new file mode 100644 index 00000000000..31382ae06ab --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_greenfield.json @@ -0,0 +1,149 @@ +{ + "types": { + "Coin": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "denom", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "salt", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ], + "Fee": [ + { + "name": "amount", + "type": "Coin[]" + }, + { + "name": "gas_limit", + "type": "uint256" + }, + { + "name": "granter", + "type": "string" + }, + { + "name": "payer", + "type": "string" + } + ], + "Msg1": [ + { + "name": "amount", + "type": "TypeMsg1Amount[]" + }, + { + "name": "from_address", + "type": "string" + }, + { + "name": "to_address", + "type": "string" + }, + { + "name": "type", + "type": "string" + } + ], + "Tx": [ + { + "name": "account_number", + "type": "uint256" + }, + { + "name": "chain_id", + "type": "uint256" + }, + { + "name": "fee", + "type": "Fee" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "msg1", + "type": "Msg1" + }, + { + "name": "sequence", + "type": "uint256" + }, + { + "name": "timeout_height", + "type": "uint256" + } + ], + "TypeMsg1Amount": [ + { + "name": "amount", + "type": "string" + }, + { + "name": "denom", + "type": "string" + } + ] + }, + "primaryType": "Tx", + "domain": { + "name": "Greenfield Tx", + "version": "1.0.0", + "chainId": "0x15e0", + "verifyingContract": "greenfield", + "salt": "0" + }, + "message": { + "sequence": "2", + "account_number": "15560", + "chain_id": "5600", + "memo": "", + "fee": { + "amount": [ + { + "amount": "2000000000000000", + "denom": "BNB" + } + ], + "gas_limit": "200000", + "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "granter": "" + }, + "timeout_height": "0", + "msg1": { + "from_address": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "to_address": "0x280b27f3676db1C4475EE10F75D510Eb527fd155", + "amount": [ + { + "amount": "1000000000000000", + "denom": "BNB" + } + ], + "type": "/cosmos.bank.v1beta1.MsgSend" + } + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_unequal_array_lengths.json b/rust/tw_evm/tests/data/eip712_unequal_array_lengths.json new file mode 100644 index 00000000000..b1279af8a31 --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_unequal_array_lengths.json @@ -0,0 +1,66 @@ +{ + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": [ + { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + } + ], + "contents": "Hello, Bob!" + }, + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[2]" + }, + { + "name": "contents", + "type": "string" + } + ] + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_with_chain_id_string.json b/rust/tw_evm/tests/data/eip712_with_chain_id_string.json new file mode 100644 index 00000000000..7d078cd3904 --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_with_chain_id_string.json @@ -0,0 +1,83 @@ +{ + "domain": { + "chainId": "5600", + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ] + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Group": [ + { + "name": "name", + "type": "string" + }, + { + "name": "members", + "type": "Person[]" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[]" + }, + { + "name": "contents", + "type": "string" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallets", + "type": "address[]" + } + ] + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/eip712_with_custom_array.json b/rust/tw_evm/tests/data/eip712_with_custom_array.json new file mode 100644 index 00000000000..116612888e1 --- /dev/null +++ b/rust/tw_evm/tests/data/eip712_with_custom_array.json @@ -0,0 +1,86 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallets", + "type": "address[]" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Group" + }, + { + "name": "contents", + "type": "string" + } + ], + "Group": [ + { + "name": "name", + "type": "string" + }, + { + "name": "members", + "type": "Person[]" + } + ] + }, + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "primaryType": "Mail", + "message": { + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": { + "name": "Farmers", + "members": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ] + }, + "contents": "Hello, Bob!" + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/swap_v2.json b/rust/tw_evm/tests/data/swap_v2.json new file mode 100644 index 00000000000..5794290fd00 --- /dev/null +++ b/rust/tw_evm/tests/data/swap_v2.json @@ -0,0 +1,69 @@ +{ + "846a1bc6": { + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + }, + { + "components": [ + { + "name": "callType", + "type": "uint8" + }, + { + "name": "target", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "callData", + "type": "bytes" + }, + { + "name": "payload", + "type": "bytes" + } + ], + "name": "calls", + "type": "tuple[]" + }, + { + "name": "bridgedTokenSymbol", + "type": "string" + }, + { + "name": "destinationChain", + "type": "string" + }, + { + "name": "destinationAddress", + "type": "string" + }, + { + "name": "payload", + "type": "bytes" + }, + { + "name": "gasRefundRecipient", + "type": "address" + }, + { + "name": "enableExpress", + "type": "bool" + } + ], + "name": "callBridgeCall", + "outputs": [ + ], + "stateMutability": "nonpayable", + "type": "function" + } +} \ No newline at end of file diff --git a/rust/tw_evm/tests/data/swap_v2_decoded.json b/rust/tw_evm/tests/data/swap_v2_decoded.json new file mode 100644 index 00000000000..85bb0570a62 --- /dev/null +++ b/rust/tw_evm/tests/data/swap_v2_decoded.json @@ -0,0 +1,105 @@ +{ + "function": "callBridgeCall(address,uint256,(uint8,address,uint256,bytes,bytes)[],string,string,string,bytes,address,bool)", + "inputs": [ + { + "name": "token", + "type": "address", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "name": "amount", + "type": "uint256", + "value": "20000000000000000" + }, + { + "components": [ + [ + { + "name": "callType", + "type": "uint8", + "value": "0" + }, + { + "name": "target", + "type": "address", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "name": "value", + "type": "uint256", + "value": "0" + }, + { + "name": "callData", + "type": "bytes", + "value": "0x095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x" + } + ], + [ + { + "name": "callType", + "type": "uint8", + "value": "0" + }, + { + "name": "target", + "type": "address", + "value": "0x99a58482BD75cbab83b27EC03CA68fF489b5788f" + }, + { + "name": "value", + "type": "uint256", + "value": "0" + }, + { + "name": "callData", + "type": "bytes", + "value": "0x0651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x" + } + ] + ], + "name": "calls", + "type": "tuple[]" + }, + { + "name": "bridgedTokenSymbol", + "type": "string", + "value": "USDC" + }, + { + "name": "destinationChain", + "type": "string", + "value": "binance" + }, + { + "name": "destinationAddress", + "type": "string", + "value": "0xce16F69375520ab01377ce7B88f5BA8C48F8D666" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "gasRefundRecipient", + "type": "address", + "value": "0xa140F413C63FBDA84E9008607E678258ffFbC76b" + }, + { + "name": "enableExpress", + "type": "bool", + "value": true + } + ] +} \ No newline at end of file diff --git a/rust/tw_evm/tests/message_signer.rs b/rust/tw_evm/tests/message_signer.rs new file mode 100644 index 00000000000..2f3b8327d92 --- /dev/null +++ b/rust/tw_evm/tests/message_signer.rs @@ -0,0 +1,279 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::SigningErrorType; +use tw_coin_entry::modules::message_signer::MessageSigner; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_evm::modules::message_signer::EthMessageSigner; +use tw_keypair::ecdsa::secp256k1; +use tw_proto::Ethereum::Proto; + +const EIP712_CASE_1: &str = include_str!("data/eip712_case_1.json"); +const EIP712_CASE_2: &str = include_str!("data/eip712_case_2.json"); +const EIP712_CASE_3: &str = include_str!("data/eip712_case_3.json"); +const EIP712_WITH_CUSTOM_ARRAY: &str = include_str!("data/eip712_with_custom_array.json"); +const EIP712_UNEQUAL_ARRAY_LEN: &str = include_str!("data/eip712_unequal_array_lengths.json"); +const EIP712_WITH_CHAIN_ID_STR: &str = include_str!("data/eip712_with_chain_id_string.json"); +const EIP712_GREENFIELD: &str = include_str!("data/eip712_greenfield.json"); +const EIP712_FIXED_BYTES: &str = include_str!("data/eip712_fixed_bytes.json"); + +struct SignVerifyTestInput { + private_key: &'static str, + msg: &'static str, + msg_type: Proto::MessageType, + chain_id: Option, + signature: &'static str, +} + +fn test_message_signer_sign_verify(test_input: SignVerifyTestInput) { + let coin = TestCoinContext::default(); + + let private_key = test_input.private_key.decode_hex().unwrap(); + let chain_id = test_input + .chain_id + .map(|chain_id| Proto::MaybeChainId { chain_id }); + let signing_input = Proto::MessageSigningInput { + private_key: private_key.into(), + message: test_input.msg.into(), + message_type: test_input.msg_type, + chain_id, + ..Proto::MessageSigningInput::default() + }; + + let output = EthMessageSigner.sign_message(&coin, signing_input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.signature, test_input.signature); + + let public_key = secp256k1::PrivateKey::try_from(test_input.private_key) + .unwrap() + .public(); + + let verifying_input = Proto::MessageVerifyingInput { + message: test_input.msg.into(), + public_key: public_key.compressed().to_vec().into(), + signature: test_input.signature.into(), + }; + assert!( + EthMessageSigner.verify_message(&coin, verifying_input), + "!verify_message: {}", + test_input.signature + ); +} + +struct SignErrorTestInput { + private_key: &'static str, + msg: &'static str, + msg_type: Proto::MessageType, + chain_id: u64, + error: SigningErrorType, +} + +fn test_message_signer_sign_err(test_input: SignErrorTestInput) { + let coin = TestCoinContext::default(); + + let private_key = test_input.private_key.decode_hex().unwrap(); + let signing_input = Proto::MessageSigningInput { + private_key: private_key.into(), + message: test_input.msg.into(), + message_type: test_input.msg_type, + chain_id: Some(Proto::MaybeChainId { + chain_id: test_input.chain_id, + }), + ..Proto::MessageSigningInput::default() + }; + + let output = EthMessageSigner.sign_message(&coin, signing_input); + assert_eq!(output.error, test_input.error); +} + +struct PreimageTestInput { + msg: &'static str, + msg_type: Proto::MessageType, + chain_id: u64, + data_hash: &'static str, +} + +fn test_message_signer_preimage_hashes(test_input: PreimageTestInput) { + let coin = TestCoinContext::default(); + + let signing_input = Proto::MessageSigningInput { + message: test_input.msg.into(), + message_type: test_input.msg_type, + chain_id: Some(Proto::MaybeChainId { + chain_id: test_input.chain_id, + }), + ..Proto::MessageSigningInput::default() + }; + + let output = EthMessageSigner.message_preimage_hashes(&coin, signing_input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.data_hash.to_hex(), test_input.data_hash); +} + +#[test] +fn test_message_signer_sign_verify_legacy() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d", + msg: "Foo", + msg_type: Proto::MessageType::MessageType_legacy, + chain_id: None, + signature: "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b", + }); +} + +#[test] +fn test_message_signer_sign_verify_eip155() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d", + msg: "Foo", + msg_type: Proto::MessageType::MessageType_eip155, + chain_id: Some(0), + signature: "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023", + }); +} + +#[test] +fn test_message_signer_sign_verify_immutable_x() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84", + msg:"Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3", + msg_type: Proto::MessageType::MessageType_immutable_x, + chain_id: None, + signature: "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01", + }); +} + +#[test] +fn test_message_signer_hash_eip712_case_1() { + test_message_signer_preimage_hashes(PreimageTestInput { + msg: EIP712_CASE_1, + msg_type: Proto::MessageType::MessageType_typed, + chain_id: 1, + data_hash: "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2", + }); +} + +#[test] +fn test_message_signer_hash_eip712_case_2() { + test_message_signer_preimage_hashes(PreimageTestInput { + msg: EIP712_CASE_2, + msg_type: Proto::MessageType::MessageType_typed, + chain_id: 1, + data_hash: "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2", + }); +} + +#[test] +fn test_message_signer_hash_with_custom_array() { + test_message_signer_preimage_hashes(PreimageTestInput { + msg: EIP712_WITH_CUSTOM_ARRAY, + msg_type: Proto::MessageType::MessageType_typed_eip155, + chain_id: 1, + data_hash: "cd8b34cd09c541cfc0a2fcd147e47809b98b335649c2aa700db0b0c4501a02a0", + }); +} + +#[test] +fn test_message_signer_hash_unequal_array_len() { + let coin = TestCoinContext::default(); + + let signing_input = Proto::MessageSigningInput { + message: EIP712_UNEQUAL_ARRAY_LEN.into(), + message_type: Proto::MessageType::MessageType_typed_eip155, + ..Proto::MessageSigningInput::default() + }; + + let output = EthMessageSigner.message_preimage_hashes(&coin, signing_input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params); +} + +#[test] +fn test_message_signer_sign_and_verify_eip712_case_3() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d", + msg: EIP712_CASE_3, + msg_type: Proto::MessageType::MessageType_typed, + chain_id: Some(0), + signature: "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf3761c", + }); +} + +#[test] +fn test_message_signer_sign_and_verify_eip712_case_3_eip155() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d", + msg: EIP712_CASE_3, + msg_type: Proto::MessageType::MessageType_typed_eip155, + chain_id: None, + signature: "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf37624", + }); +} + +#[test] +fn test_message_signer_sign_and_verify_eip712_invalid_chain_id() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d", + msg: EIP712_CASE_3, + msg_type: Proto::MessageType::MessageType_typed_eip155, + chain_id: None, + signature: "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf37624", + }); +} + +#[test] +fn test_message_signer_sign_eip712_invalid_chain_id() { + test_message_signer_sign_err(SignErrorTestInput { + private_key: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d", + msg: EIP712_CASE_3, + msg_type: Proto::MessageType::MessageType_typed_eip155, + // Actual value is 0. + chain_id: 1, + error: SigningErrorType::Error_invalid_params, + }); +} + +// Test `TWEthereumMessageSignerSignTypedMessageEip155` where `domain.chainId` is a base10 decimal string. +// Generated by using https://metamask.github.io/test-dapp/ +#[test] +fn test_message_signer_sign_verify_eip712_with_chain_id_string() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0", + msg: EIP712_WITH_CHAIN_ID_STR, + msg_type: Proto::MessageType::MessageType_typed_eip155, + // 5600 + chain_id: Some(0x15e0), + signature: "248b45acf2920a9cef00d3b469a875482b5f0e8ce16f6290212d395aaec7f3be0645d6a5cb6fcdfdca9ecefbadd4e77dae656124094ecc984c5fcb9cb4384b05e3", + }); +} + +// The test checks if extra types are ordered correctly. +// The typed message was used to sign a Greenfield transaction: +// https://greenfieldscan.com/tx/9F895CF2DD64FB1F428CEFCF2A6585A813C3540FC9FE1EF42DB1DA2CB1DF55AB +#[test] +fn test_message_signer_sign_verify_eip712_greenfield() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0", + msg: EIP712_GREENFIELD, + msg_type: Proto::MessageType::MessageType_typed, + chain_id: None, + signature: "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b", + }); +} + +// The test checks if `0x0` is a valid `bytes32` value. +#[test] +fn test_message_signer_sign_verify_eip712_fixed_bytes() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", + msg: EIP712_FIXED_BYTES, + msg_type: Proto::MessageType::MessageType_typed, + chain_id: None, + signature: "7ee9b54fedf355e40fa86bbe23e63b318ef797bd8fdbc5bb714edbace042d4cb60111912218234e856f2cf300b3b47c91383b98e263ecf69c6c10193fef6c9581b", + }); +} diff --git a/rust/tw_evm/tests/rlp.rs b/rust/tw_evm/tests/rlp.rs new file mode 100644 index 00000000000..df7bb0c9915 --- /dev/null +++ b/rust/tw_evm/tests/rlp.rs @@ -0,0 +1,247 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use std::str::FromStr; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::rlp_encoder::{RlpEncoder, RECURSION_LIMIT}; +use tw_number::U256; +use tw_proto::EthereumRlp::Proto as RlpProto; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +fn make_item(item: Item) -> RlpProto::RlpItem { + RlpProto::RlpItem { item } +} + +#[track_caller] +fn test_encode(item: Item, expected: &str) { + let input = RlpProto::EncodingInput { + item: Some(make_item(item)), + }; + let output = RlpEncoder::::encode_with_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.encoded.to_hex(), expected); +} + +#[test] +fn test_encode_string() { + test_encode(Item::string_item(Cow::from("")), "80"); + test_encode(Item::string_item(Cow::from("d")), "64"); + test_encode(Item::string_item(Cow::from("dog")), "83646f67"); +} + +#[test] +fn test_encode_number_u64() { + test_encode(Item::number_u64(0), "80"); + test_encode(Item::number_u64(127), "7f"); + test_encode(Item::number_u64(128), "8180"); + test_encode(Item::number_u64(255), "81ff"); + test_encode(Item::number_u64(256), "820100"); + test_encode(Item::number_u64(1024), "820400"); + test_encode(Item::number_u64(0xffff), "82ffff"); + test_encode(Item::number_u64(0x010000), "83010000"); + test_encode(Item::number_u64(0xffffff), "83ffffff"); + test_encode(Item::number_u64(0xffffffff), "84ffffffff"); + test_encode(Item::number_u64(0xffffffffffffff), "87ffffffffffffff"); +} + +#[test] +fn test_ethereum_rlp_number_u256() { + macro_rules! test_encode_u256 { + ($num_str:literal => $expected:literal) => { + let num = U256::from_str($num_str).unwrap().to_big_endian_compact(); + test_encode(Item::number_u256(Cow::from(num)), $expected); + }; + } + + test_encode_u256!("0" => "80"); + test_encode_u256!("1" => "01"); + test_encode_u256!("127" => "7f"); + test_encode_u256!("128" => "8180"); + test_encode_u256!("256" => "820100"); + test_encode_u256!("1024" => "820400"); + + // 0xffffff + test_encode_u256!("16777215" => "83ffffff"); + // 0xffffffff + test_encode_u256!("4294967295" => "84ffffffff"); + // 0xffffffffffffff + test_encode_u256!("72057594037927935" => "87ffffffffffffff"); + // 0x102030405060708090a0b0c0d0e0f2 + test_encode_u256!("83729609699884896815286331701780722" => "8f102030405060708090a0b0c0d0e0f2"); + // 0x0100020003000400050006000700080009000a000b000c000d000e01 + test_encode_u256!("105315505618206987246253880190783558935785933862974822347068935681" => "9c0100020003000400050006000700080009000a000b000c000d000e01"); + // 0x0100000000000000000000000000000000000000000000000000000000000000 + test_encode_u256!("452312848583266388373324160190187140051835877600158453279131187530910662656" => "a00100000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_ethereum_rlp_raw_encoded() { + let raw_encoded = "946b175474e89094c44da98b954eedeac495271d0f" + .decode_hex() + .unwrap(); + test_encode( + Item::raw_encoded(Cow::from(raw_encoded)), + "946b175474e89094c44da98b954eedeac495271d0f", + ); + + let empty: &[u8] = &[]; + test_encode(Item::raw_encoded(Cow::from(empty)), ""); +} + +#[test] +fn test_ethereum_rlp_list() { + // Empty list. + let list = RlpProto::RlpList { + items: Vec::default(), + }; + test_encode(Item::list(list), "c0"); + + // [1, 2, 3] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::number_u64(1)), + make_item(Item::number_u64(2)), + make_item(Item::number_u64(3)), + ], + }; + test_encode(Item::list(list), "c3010203"); + + // ["a", "b"] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("a"))), + make_item(Item::string_item(Cow::from("b"))), + ], + }; + test_encode(Item::list(list), "c26162"); + + // ["cat", "dog"] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("cat"))), + make_item(Item::string_item(Cow::from("dog"))), + ], + }; + test_encode(Item::list(list), "c88363617483646f67"); + + // ["cat", "dog"] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("cat"))), + make_item(Item::string_item(Cow::from("dog"))), + ], + }; + test_encode(Item::list(list), "c88363617483646f67"); +} + +#[test] +fn test_ethereum_rlp_nested_list() { + let l11 = RlpProto::RlpList { + items: vec![ + make_item(Item::number_u64(1)), + make_item(Item::number_u64(2)), + make_item(Item::number_u64(3)), + ], + }; + let l12 = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("apple"))), + make_item(Item::string_item(Cow::from("banana"))), + make_item(Item::string_item(Cow::from("cherry"))), + ], + }; + let l21 = RlpProto::RlpList { + items: vec![ + make_item(Item::data(Cow::from("abcdef".decode_hex().unwrap()))), + make_item(Item::data(Cow::from( + "00010203040506070809".decode_hex().unwrap(), + ))), + ], + }; + let l22 = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("bitcoin"))), + make_item(Item::string_item(Cow::from("beeenbee"))), + make_item(Item::string_item(Cow::from("eth"))), + ], + }; + let l1 = RlpProto::RlpList { + items: vec![make_item(Item::list(l11)), make_item(Item::list(l12))], + }; + let l2 = RlpProto::RlpList { + items: vec![make_item(Item::list(l21)), make_item(Item::list(l22))], + }; + let list = RlpProto::RlpList { + items: vec![make_item(Item::list(l1)), make_item(Item::list(l2))], + }; + + // The value is checked by using https://codechain-io.github.io/rlp-debugger/ + test_encode( + Item::list(list), + "f841d9c3010203d4856170706c658662616e616e6186636865727279e6cf83abcdef8a00010203040506070809d587626974636f696e88626565656e62656583657468" + ); +} + +#[test] +fn test_ethereum_rlp_nested_list_recursion_limit() { + fn make_nested_list(nested_lists_to_create: usize) -> RlpProto::RlpList<'static> { + if nested_lists_to_create == 1 { + // This is the last call in the recursion. + return RlpProto::RlpList { items: Vec::new() }; + } + + let nested_list = make_nested_list(nested_lists_to_create - 1); + RlpProto::RlpList { + items: vec![make_item(Item::list(nested_list))], + } + } + + let nested_list = make_nested_list(RECURSION_LIMIT + 10); + let input = RlpProto::EncodingInput { + item: Some(make_item(Item::list(nested_list))), + }; + + let output = RlpEncoder::::encode_with_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params); +} + +#[test] +fn test_ethereum_rlp_list_eip1559() { + let chain_id = U256::encode_be_compact(10); + let nonce = U256::encode_be_compact(6); + let max_inclusion_fee_per_gas = 2_000_000_000; + let max_fee_per_gas = U256::encode_be_compact(3_000_000_000); + let gas_limit = U256::encode_be_compact(21_100); + let to = "0x6b175474e89094c44da98b954eedeac495271d0f"; + let amount = 0; + let payload = "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1".decode_hex().unwrap(); + + // Empty nested list. + let access_list = RlpProto::RlpList { + items: Vec::default(), + }; + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::number_u256(chain_id)), + make_item(Item::number_u256(nonce)), + make_item(Item::number_u64(max_inclusion_fee_per_gas)), + make_item(Item::number_u256(max_fee_per_gas)), + make_item(Item::number_u256(gas_limit)), + make_item(Item::address(Cow::from(to))), + make_item(Item::number_u64(amount)), + make_item(Item::data(Cow::from(payload))), + make_item(Item::list(access_list)), + ], + }; + test_encode( + Item::list(list), + "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0" + ); +} diff --git a/rust/tw_evm/tests/signer.rs b/rust/tw_evm/tests/signer.rs new file mode 100644 index 00000000000..9b43ca7cea3 --- /dev/null +++ b/rust/tw_evm/tests/signer.rs @@ -0,0 +1,586 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{self, ToHex}; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::signer::Signer; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::Ethereum::Proto::TransactionMode; + +#[test] +fn test_sign_transaction_non_typed_erc20_transfer() { + let private = + hex::decode("0x4646464646464646464646464646464646464646464646464646464646464646").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + amount: U256::encode_be_compact(2_000_000_000_000_000_000), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(0x34), + tx_mode: TransactionMode::Legacy, + // 42000000000 + gas_price: U256::encode_be_compact(0x09_c765_2400), + // 78009 + gas_limit: U256::encode_be_compact(0x01_30B9), + // DAI + to_address: "0x6b175474e89094c44da98b954eedeac495271d0f".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + erc20_transfer, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f8ab808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000818ba0c34040ff76f6d5e397b54b47f7fa2b3a7213f3c2a39a750260211fa15249ae8aa01ac5061e9bcf05aebef461864662652f25c45ee99240e3bb91b31f456208a6cd"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "b3525019dc367d3ecac48905f9a95ff3550c25a24823db765f92cae2dec7ebfd" + ); +} + +#[test] +fn test_sign_transaction_non_typed_native() { + let private = + hex::decode("0x4646464646464646464646464646464646464646464646464646464646464646").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(9), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.r, false), + "28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276" + ); + assert_eq!( + hex::encode(output.s, false), + "67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83" + ); + assert_eq!(hex::encode(output.v, false), "25"); + + assert_eq!( + hex::encode(output.pre_hash, false), + "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53" + ); +} + +#[test] +fn test_sign_transaction_non_typed_erc20_approve() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let approve = Proto::mod_Transaction::ERC20Approve { + // DAI + spender: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + amount: U256::encode_be_compact(2_000_000_000_000_000_000), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(0), + // 0x09c7652400 + gas_price: U256::encode_be_compact(42_000_000_000), + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + to_address: "0x6b175474e89094c44da98b954eedeac495271d0f".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_approve( + approve, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0d8136d66da1e0ba8c7208d5c4f143167f54b89a0fe2e23440653bcca28b34dc1a049222a79339f1a9e4641cb4ad805c49c225ae704299ffc10627bf41c035c464a"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.r, false), + "d8136d66da1e0ba8c7208d5c4f143167f54b89a0fe2e23440653bcca28b34dc1" + ); + assert_eq!( + hex::encode(output.s, false), + "49222a79339f1a9e4641cb4ad805c49c225ae704299ffc10627bf41c035c464a" + ); + assert_eq!(hex::encode(output.v, false), "25"); + + assert_eq!( + hex::encode(output.pre_hash, false), + "fe34a2b97f583db2d4baca3753f105d70dcf2d9800ddd0247900a026b9de6183" + ); +} + +#[test] +fn test_sign_transaction_non_typed_contract_generic() { + let private = + hex::decode("0x4646464646464646464646464646464646464646464646464646464646464646").unwrap(); + let call_data = "60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f540000000000000000000000000000000000000000000000000000000000"; + + let contract = Proto::mod_Transaction::ContractGeneric { + amount: U256::encode_be_compact(0), + data: hex::decode(call_data).unwrap().into(), + }; + + // `tx_mode` is not set, Legacy is the default. + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(11), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(1_000_000), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::contract_generic( + contract, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.pre_hash, false), + "5d2556f7d0e629dc6ce9dbc8a205853a7b89c136791840a39765e34cb5e3466a" + ); + + let expected = "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!(hex::encode(output.data, false), call_data); +} + +// https://ropsten.etherscan.io/tx/0x14429509307efebfdaa05227d84c147450d168c68539351fbc01ed87c916ab2e +#[test] +fn test_sign_transaction_eip1559_native_transfer() { + let private = + hex::decode("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(543_210_987_654_321), + data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(3), + nonce: U256::encode_be_compact(6), + tx_mode: TransactionMode::Enveloped, + gas_limit: U256::encode_be_compact(21_100), + max_inclusion_fee_per_gas: U256::encode_be_compact(2_000_000_000), + max_fee_per_gas: U256::encode_be_compact(3_000_000_000), + to_address: "0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "02f8710306847735940084b2d05e0082526c94b9f5771c27664bf2282d98e09d7f50cec7cb01a78701ee0c29f50cb180c080a092c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64a06487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + output.r.to_hex(), + "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64" + ); + assert_eq!( + output.s.to_hex(), + "6487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58" + ); + assert_eq!(output.v.to_hex(), "00"); + + assert_eq!( + output.pre_hash.to_hex(), + "6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155" + ); +} + +#[test] +fn test_sign_transaction_eip1559_erc20_transfer() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + amount: U256::encode_be_compact(2_000_000_000_000_000_000), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(0), + tx_mode: TransactionMode::Enveloped, + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + // 0x77359400 + max_inclusion_fee_per_gas: U256::encode_be_compact(2_000_000_000), + // 0xB2D05E00 + max_fee_per_gas: U256::encode_be_compact(3_000_000_000), + // DAI + to_address: "0x6b175474e89094c44da98b954eedeac495271d0f".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + erc20_transfer, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "aa0ec30afa12acb48a080aa7157254193eeb2a4d248538b0747535baab98141f" + ); +} + +#[test] +fn test_sign_transaction_eip1559_erc20_approve() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc20_approve = Proto::mod_Transaction::ERC20Approve { + spender: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + amount: U256::encode_be_compact(2_000_000_000_000_000_000), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(0), + tx_mode: TransactionMode::Enveloped, + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + // 0x77359400 + max_inclusion_fee_per_gas: U256::encode_be_compact(2_000_000_000), + // 0xB2D05E00 + max_fee_per_gas: U256::encode_be_compact(3_000_000_000), + // DAI + to_address: "0x6b175474e89094c44da98b954eedeac495271d0f".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_approve( + erc20_approve, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a05a43dda3dc193480ee532a5ed67ba8fbd2e3afb9eee218f4fb955b415d592925a01300e5b5f51c8cd5bf80f018cea3fb347fae589e65355068ac44ffc996313c60"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "bed87d402cc536fac3dbb346eac221f5ee783e04f0a7e3713817e55a78d08065" + ); +} + +#[test] +fn test_sign_transaction_eip1559_erc721_transfer() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc20_approve = Proto::mod_Transaction::ERC721Transfer { + from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + token_id: hex::decode("23c47ee5").unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(0), + tx_mode: TransactionMode::Enveloped, + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + // 0x77359400 + max_inclusion_fee_per_gas: U256::encode_be_compact(2_000_000_000), + // 0xB2D05E00 + max_fee_per_gas: U256::encode_be_compact(3_000_000_000), + to_address: "0x4e45e92ed38f885d39a733c14f1817217a89d425".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc721_transfer( + erc20_approve, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "02f8d00180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee5c080a0dbd591d1eac39bad62d7c158d5e1d55e7014d2218998f8980462e2f283f42d4aa05acadb904484a0fb5526a4c64b8addb8aac4f6548f90199e40eb787b79faed4a"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "6089b11574c558e717fd25fe84bb7525bc32408f7124c2f199e26dfb0845abdd" + ); +} + +#[test] +fn test_sign_transaction_eip1559_erc1155_transfer() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc1155_approve = Proto::mod_Transaction::ERC1155Transfer { + from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + token_id: hex::decode("23c47ee5").unwrap().into(), + value: U256::encode_be_compact(2_000_000_000_000_000_000), + data: hex::decode("01020304").unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(0), + tx_mode: TransactionMode::Enveloped, + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + // 0x77359400 + max_inclusion_fee_per_gas: U256::encode_be_compact(2_000_000_000), + // 0xB2D05E00 + max_fee_per_gas: U256::encode_be_compact(3_000_000_000), + to_address: "0x4e45e92ed38f885d39a733c14f1817217a89d425".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc1155_transfer( + erc1155_approve, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "02f901500180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000c080a0533df41dda5540c57257b7fe89c29cefff0155c333e063220df2bf9680fcc15aa036a844fd20de5a51de96ceaaf078558e87d86426a4a5d4b215ee1fd0fa397f8a"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "d18f2ac6b3bff71457bfed1fc897b42a5a4220b1589eadbbace8d3def656f914" + ); +} + +#[test] +fn test_sign_transaction_non_typed_erc20_transfer_as_contract_generic() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + // payload: transfer(0x5322b34c88ed0691971bf52a7047448f0f4efc84, 2000000000000000000) + let contract_data = hex::decode("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000").unwrap(); + let contract = Proto::mod_Transaction::ContractGeneric { + amount: U256::encode_be_compact(0), + data: contract_data.into(), + }; + + // `tx_mode` is not set, Legacy is the default. + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(0), + // 0x09c7652400 + gas_price: U256::encode_be_compact(42_000_000_000), + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + to_address: "0x6b175474e89094c44da98b954eedeac495271d0f".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::contract_generic( + contract, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "3a3fc6df8815e15874cc8d6c65f88ea0643b375e5b22726269d187035a5cb486" + ); +} + +#[test] +fn test_sign_transaction_non_typed_erc20_transfer_invalid_address() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + amount: U256::encode_be_compact(2_000_000_000_000_000_000), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + tx_mode: TransactionMode::Legacy, + // 42000000000 + gas_price: U256::encode_be_compact(0x09_c765_2400), + // 78009 + gas_limit: U256::encode_be_compact(0x01_30B9), + // DAI + to_address: "0xdeadbeef".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + erc20_transfer, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_address); + assert!(!output.error_message.is_empty()); +} + +#[test] +fn test_sign_transaction_non_typed_erc721_transfer() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc721_transfer = Proto::mod_Transaction::ERC721Transfer { + from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + token_id: hex::decode("23c47ee5").unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + tx_mode: TransactionMode::Legacy, + // 0x09c7652400 + gas_price: U256::encode_be_compact(42_000_000_000), + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + to_address: "0x4e45e92ed38f885d39a733c14f1817217a89d425".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc721_transfer( + erc721_transfer, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f8ca808509c7652400830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee526a0d38440a4dc140a4100d301eb49fcc35b64439e27d1d8dd9b55823dca04e6e659a03b5f56a57feabc3406f123d6f8198cd7d7e2ced7e2d58d375f076952ecd9ce88"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "5de6e2bc3a0e95a32c2a6075bec622feccb8e8dab8fa564de7468416b44eff32" + ); + + let expected_data = "23b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee5"; + assert_eq!(hex::encode(output.data, false), expected_data); +} + +#[test] +fn test_sign_transaction_non_typed_erc1155_transfer() { + let private = + hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + + let erc1155_transfer = Proto::mod_Transaction::ERC1155Transfer { + from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), + to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), + token_id: hex::decode("23c47ee5").unwrap().into(), + value: U256::encode_be_compact(2_000_000_000_000_000_000), + data: hex::decode("01020304").unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + tx_mode: TransactionMode::Legacy, + // 0x09c7652400 + gas_price: U256::encode_be_compact(42_000_000_000), + // 0x130B9 + gas_limit: U256::encode_be_compact(78_009), + to_address: "0x4e45e92ed38f885d39a733c14f1817217a89d425".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc1155_transfer( + erc1155_transfer, + ), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f9014a808509c7652400830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000026a010315488201ac801ce346bffd1570de147615462d7e7db3cf08cf558465c6b79a06643943b24593bc3904a9fda63bb169881730994c973ab80f07d66a698064573"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "d183270df1081772f867c9e6d16601028dd7faa6103e5d7286f1f5e4bde4f547" + ); + + let expected_data = "f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000"; + assert_eq!(hex::encode(output.data, false), expected_data); +} diff --git a/rust/tw_hash/Cargo.toml b/rust/tw_hash/Cargo.toml index a1f57154a06..9d22a32c414 100644 --- a/rust/tw_hash/Cargo.toml +++ b/rust/tw_hash/Cargo.toml @@ -3,18 +3,24 @@ name = "tw_hash" version = "0.1.0" edition = "2021" +[features] +default = ["serde"] + [dependencies] +arbitrary = { version = "1", features = ["derive"], optional = true } blake-hash = "0.4.1" -blake2 = "0.10.6" blake2b-ref = "0.3.1" digest = "0.10.6" groestl = "0.10.1" hmac = "0.12.1" ripemd = "0.1.3" +serde = { version = "1.0.159", features = ["derive"], optional = true } sha1 = "0.10.5" sha2 = "0.10.6" sha3 = "0.10.6" +tw_encoding = { path = "../tw_encoding" } tw_memory = { path = "../tw_memory" } +zeroize = "1.6.0" [dev-dependencies] -hex = "0.4.3" +serde_json = "1.0.95" diff --git a/rust/tw_hash/fuzz/.gitignore b/rust/tw_hash/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_hash/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_hash/fuzz/Cargo.toml b/rust/tw_hash/fuzz/Cargo.toml new file mode 100644 index 00000000000..af42f8796a8 --- /dev/null +++ b/rust/tw_hash/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tw_hash-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +[dependencies.tw_hash] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "hash_fuzz" +path = "fuzz_targets/hash_fuzz.rs" +test = false +doc = false diff --git a/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs b/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs new file mode 100644 index 00000000000..ddd74bce7df --- /dev/null +++ b/rust/tw_hash/fuzz/fuzz_targets/hash_fuzz.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct HashInput<'a> { + data: &'a [u8], + additional_data: &'a [u8], + hash_size: usize, +} + +fuzz_target!(|input: HashInput<'_>| { + tw_hash::blake::blake_256(input.data); + tw_hash::blake2::blake2_b(input.data, input.hash_size).ok(); + tw_hash::blake2::blake2_b_personal(input.data, input.hash_size, input.additional_data).ok(); + tw_hash::crc32::crc32(input.data); + tw_hash::groestl::groestl_512(input.data); + tw_hash::hmac::hmac_sha256(input.additional_data, input.data); + tw_hash::ripemd::ripemd_160(input.data); + tw_hash::sha1::sha1(input.data); + tw_hash::sha2::sha256(input.data); + tw_hash::sha2::sha512(input.data); + tw_hash::sha2::sha512_256(input.data); + tw_hash::sha3::keccak256(input.data); + tw_hash::sha3::keccak512(input.data); + tw_hash::sha3::sha3_256(input.data); + tw_hash::sha3::sha3_512(input.data); +}); diff --git a/rust/tw_hash/src/blake2.rs b/rust/tw_hash/src/blake2.rs index 1968c4d2fdd..6eaef3a5ad9 100644 --- a/rust/tw_hash/src/blake2.rs +++ b/rust/tw_hash/src/blake2.rs @@ -4,26 +4,67 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use blake2::{ - digest::{Update, VariableOutput}, - Blake2bVar, -}; +use crate::Error; use blake2b_ref::Blake2bBuilder; +use std::ops::RangeInclusive; -pub fn blake2_b(input: &[u8], hash_size: usize) -> Vec { - let mut hasher = Blake2bVar::new(hash_size).unwrap(); +const OUTPUT_HASH_LEN_RANGE: RangeInclusive = 1..=64; +const PERSONAL_INPUT_MAX_LEN: usize = 16; + +pub fn blake2_b(input: &[u8], hash_size: usize) -> Result, Error> { + if !OUTPUT_HASH_LEN_RANGE.contains(&hash_size) { + return Err(Error::InvalidHashLength); + } + + let mut hasher = Blake2bBuilder::new(hash_size).build(); hasher.update(input); let mut buf = vec![0; hash_size]; - hasher.finalize_variable(&mut buf).unwrap(); - buf + hasher.finalize(&mut buf); + Ok(buf) } -pub fn blake2_b_personal(input: &[u8], hash_size: usize, personal_input: &[u8]) -> Vec { +pub fn blake2_b_personal( + input: &[u8], + hash_size: usize, + personal_input: &[u8], +) -> Result, Error> { + if !OUTPUT_HASH_LEN_RANGE.contains(&hash_size) { + return Err(Error::InvalidHashLength); + } + if personal_input.len() > PERSONAL_INPUT_MAX_LEN { + return Err(Error::InvalidArgument); + } + let mut output: Vec = vec![0; hash_size]; let mut blake2b = Blake2bBuilder::new(hash_size) .personal(personal_input) .build(); blake2b.update(input); blake2b.finalize(&mut output); - output + Ok(output) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_blake2_b_invalid_hash_size() { + let input = b"Hello world"; + blake2_b(input, 65).unwrap_err(); + } + + #[test] + fn test_blake2_b_personal_invalid_hash_size() { + let input = b"Hello world"; + let personal_data = b"MyApp Files Hash"; + blake2_b_personal(input, 65, personal_data).unwrap_err(); + } + + #[test] + fn test_blake2_b_personal_invalid_personal_data() { + let input = b"Hello world"; + let personal_data = b"MyApp Files Hash ..."; + blake2_b_personal(input, 64, personal_data).unwrap_err(); + } } diff --git a/rust/tw_hash/src/crc32.rs b/rust/tw_hash/src/crc32.rs new file mode 100644 index 00000000000..f413fd13a19 --- /dev/null +++ b/rust/tw_hash/src/crc32.rs @@ -0,0 +1,52 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +// Table taken from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +// This table is used to speed up the crc calculation. +const CRC32_TABLE: [u32; 256] = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +]; + +// Algorithm inspired by this old-style C implementation: +// https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +pub fn crc32(input: &[u8]) -> u32 { + let mut c = u32::MAX; + for b in input { + c = CRC32_TABLE[((c ^ (*b as u32)) & 0xFF) as usize] ^ (c >> 8); + } + !c +} diff --git a/rust/tw_hash/src/ffi.rs b/rust/tw_hash/src/ffi.rs index 48fd50e627b..e49f96f336b 100644 --- a/rust/tw_hash/src/ffi.rs +++ b/rust/tw_hash/src/ffi.rs @@ -6,8 +6,32 @@ #![allow(clippy::missing_safety_doc)] -use crate::{blake, blake2, groestl, hmac, ripemd, sha1, sha2, sha3}; -use tw_memory::ffi::c_byte_array::CByteArray; +use crate::{blake, blake2, groestl, hmac, ripemd, sha1, sha2, sha3, Error}; +use tw_memory::ffi::c_byte_array::{CByteArray, CByteArrayResult}; +use tw_memory::ffi::c_result::ErrorCode; + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub enum CHashingCode { + Ok = 0, + InvalidHashLength = 1, + InvalidArgument = 2, +} + +impl From for CHashingCode { + fn from(e: Error) -> Self { + match e { + Error::FromHexError(_) | Error::InvalidArgument => CHashingCode::InvalidArgument, + Error::InvalidHashLength => CHashingCode::InvalidHashLength, + } + } +} + +impl From for ErrorCode { + fn from(e: CHashingCode) -> Self { + e as ErrorCode + } +} /// Computes the Blake-256 hash of the `input` byte array. /// \param input *non-null* byte array. @@ -29,9 +53,12 @@ pub unsafe extern "C" fn blake2_b( input: *const u8, input_len: usize, hash_size: usize, -) -> CByteArray { +) -> CByteArrayResult { let input = std::slice::from_raw_parts(input, input_len); - blake2::blake2_b(input, hash_size).into() + blake2::blake2_b(input, hash_size) + .map(CByteArray::from) + .map_err(CHashingCode::from) + .into() } /// Computes the personalized BLAKE2B hash of the `input` byte array. @@ -48,10 +75,13 @@ pub unsafe extern "C" fn blake2_b_personal( hash_size: usize, personal_input: *const u8, personal_len: usize, -) -> CByteArray { +) -> CByteArrayResult { let input = std::slice::from_raw_parts(input, input_len); let personal = std::slice::from_raw_parts(personal_input, personal_len); - blake2::blake2_b_personal(input, hash_size, personal).into() + blake2::blake2_b_personal(input, hash_size, personal) + .map(CByteArray::from) + .map_err(CHashingCode::from) + .into() } /// Computes the Groestl-512 hash of the `input` byte array. diff --git a/rust/tw_hash/src/hash_array.rs b/rust/tw_hash/src/hash_array.rs new file mode 100644 index 00000000000..95ab0388a12 --- /dev/null +++ b/rust/tw_hash/src/hash_array.rs @@ -0,0 +1,312 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::Error; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; +use tw_encoding::hex; +use tw_encoding::hex::ToHex; +use zeroize::DefaultIsZeroes; + +pub type H32 = Hash<4>; +pub type H160 = Hash<20>; +pub type H256 = Hash<32>; +pub type H264 = Hash<33>; +pub type H512 = Hash<64>; +pub type H520 = Hash<65>; + +pub type SplitHash = (Hash, Hash); + +/// Concatenates `left: Hash` and `right: Hash` into `Hash` +/// where `N = L + R` (statically checked). +pub fn concat( + left: Hash, + right: Hash, +) -> Hash { + // Ensure if `L + R == N` at compile time. + let _ = AssertSplit::::VALID; + + let mut res = Hash::new(); + res[0..L].copy_from_slice(left.as_slice()); + res[L..(L + R)].copy_from_slice(right.as_slice()); + + res +} + +/// Represents a fixed-length byte array. +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Hash([u8; N]); + +impl DefaultIsZeroes for Hash {} + +/// cbindgen:ignore +impl Hash { + pub const LEN: usize = N; + + pub const fn new() -> Self { + Hash([0; N]) + } + + pub fn as_slice(&self) -> &[u8] { + &self.0 + } + + pub fn into_vec(self) -> Vec { + self.0.to_vec() + } + + /// Splits the byte array into two pieces with `L` and `R` lengths accordingly, + /// where `N = L + R` (statically checked). + pub fn split(&self) -> SplitHash { + // Ensure if `L + R == N` at compile time. + let _ = AssertSplit::::VALID; + + let mut left: Hash = Hash::default(); + let mut right: Hash = Hash::default(); + + left.copy_from_slice(&self.0[0..L]); + right.copy_from_slice(&self.0[L..]); + + (left, right) + } + + pub const fn take(self) -> [u8; N] { + self.0 + } + + pub const fn len() -> usize { + N + } +} + +/// This is a [`Hash::split`] helper that ensures that `L + R == N` at compile time. +/// Assertion example: +/// ```rust(ignore) +/// let hash = H256::default(); +/// let (left, right): (H128, H160) = hash.split(); +/// +/// // output: +/// // error[E0080]: evaluation of `hash_array::AssertSplit::<16, 20, 32>::EQ` failed +/// // --> tw_hash/src/hash_array.rs:67:41 +/// // | +/// // 67 | pub const EQ: usize = (R + L - N) + (N - (R + L)); +/// // | ^^^^^^^^^^^^^ attempt to compute `32_usize - 36_usize`, which would overflow +/// ``` +/// +/// TODO remove once [feature(generic_const_exprs)](https://github.com/rust-lang/rust/issues/76560) is stable. +struct AssertSplit; + +/// cbindgen:ignore +impl AssertSplit { + pub const VALID: usize = (R + L - N) + (N - (R + L)); +} + +/// Implement `str` -> `Hash` conversion for test purposes. +impl From<&'static str> for Hash { + fn from(hex: &'static str) -> Self { + hex.parse().expect("Expected a valid hex-encoded hash") + } +} + +impl From<[u8; N]> for Hash { + fn from(data: [u8; N]) -> Self { + Hash(data) + } +} + +impl<'a, const N: usize> TryFrom<&'a [u8]> for Hash { + type Error = Error; + + fn try_from(data: &'a [u8]) -> Result { + if data.len() != N { + return Err(Error::InvalidHashLength); + } + + let mut dest = Hash::default(); + dest.0.copy_from_slice(data); + Ok(dest) + } +} + +impl FromStr for Hash { + type Err = Error; + + fn from_str(s: &str) -> Result { + let data = hex::decode(s)?; + Hash::try_from(data.as_slice()) + } +} + +impl Default for Hash { + fn default() -> Self { + Hash::new() + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Deref for Hash { + type Target = [u8; N]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Hash { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +#[cfg(feature = "serde")] +mod impl_serde { + use super::Hash; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + impl<'de, const N: usize> Deserialize<'de> for Hash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + hex.parse().map_err(|e| Error::custom(format!("{e:?}"))) + } + } + + impl Serialize for Hash { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_from_str() { + let actual = H256::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + let expected = [ + 175u8, 238, 252, 167, 77, 154, 50, 92, 241, 214, 182, 145, 29, 97, 166, 92, 50, 175, + 168, 224, 43, 213, 231, 142, 46, 74, 194, 145, 11, 171, 69, 245, + ]; + assert_eq!(actual.0[..], expected[..]); + } + + #[test] + fn test_hash_display() { + let str = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + assert_eq!(H256::from(str).to_string(), str); + } + + #[test] + #[should_panic] + fn test_from_hex_literal_invalid() { + let _ = H256::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45x5"); + } + + #[test] + fn test_from_hex_invalid() { + let err = + H256::from_str("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45x5") + .unwrap_err(); + + match err { + Error::FromHexError(_) => (), + other => panic!("Expected 'FromHexError', found: {other:?}"), + } + } + + #[test] + fn test_from_hex_invalid_len() { + let err = H256::from_str("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45") + .unwrap_err(); + + match err { + Error::InvalidHashLength => (), + other => panic!("Expected 'Error::InvalidHashLength', found: {other:?}"), + } + } + + #[test] + fn test_hash_split_at() { + let hash = + Hash::<32>::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + + let (left, right): (Hash<10>, Hash<22>) = hash.split(); + assert_eq!(left, Hash::from("afeefca74d9a325cf1d6")); + assert_eq!( + right, + Hash::from("b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5") + ); + } + + #[test] + fn test_hash_concat() { + let left: Hash<10> = Hash::from("afeefca74d9a325cf1d6"); + let right: Hash<22> = Hash::from("b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + let res: Hash<32> = concat(left, right); + + let expected: Hash<32> = + Hash::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + assert_eq!(res, expected); + } +} + +/// cbindgen:ignore +#[cfg(all(test, feature = "serde"))] +mod serde_tests { + use super::*; + use serde_json::json; + + const BYTES_32: [u8; 32] = [ + 175u8, 238, 252, 167, 77, 154, 50, 92, 241, 214, 182, 145, 29, 97, 166, 92, 50, 175, 168, + 224, 43, 213, 231, 142, 46, 74, 194, 145, 11, 171, 69, 245, + ]; + const HEX_32: &str = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + + #[test] + fn test_hash_deserialize() { + let unprefixed: Hash<32> = serde_json::from_value(json!(HEX_32)).unwrap(); + assert_eq!(unprefixed.0, BYTES_32); + + let prefixed: Hash<32> = serde_json::from_value(json!(HEX_32)).unwrap(); + assert_eq!(prefixed.0, BYTES_32); + } + + #[test] + fn test_hash_deserialize_error() { + serde_json::from_value::>(json!( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45" + )) + .unwrap_err(); + } + + #[test] + fn test_hash_serialize() { + let hash = Hash::<32>::from(HEX_32); + let actual = serde_json::to_value(&hash).unwrap(); + assert_eq!(actual, json!(HEX_32)); + } +} diff --git a/rust/tw_hash/src/hasher.rs b/rust/tw_hash/src/hasher.rs new file mode 100644 index 00000000000..086c9915f0c --- /dev/null +++ b/rust/tw_hash/src/hasher.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ripemd::ripemd_160; +use crate::sha2::sha256; +use crate::sha3::keccak256; +use serde::Deserialize; +use tw_memory::Data; + +// keccak256 + +/// Enum selector for the supported hash functions. +/// Add hash types if necessary. For example, when add a new hasher to `registry.json`. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +pub enum Hasher { + #[serde(rename = "sha256")] + Sha256, + #[serde(rename = "keccak256")] + Keccak256, + /// SHA256 hash of the SHA256 hash + #[serde(rename = "sha256d")] + Sha256d, + /// ripemd hash of the SHA256 hash + #[serde(rename = "sha256ripemd")] + Sha256ripemd, +} + +impl Hasher { + pub fn hash(&self, data: &[u8]) -> Data { + match self { + Hasher::Sha256 => sha256(data), + Hasher::Keccak256 => keccak256(data), + Hasher::Sha256d => sha256(&sha256(data)), + Hasher::Sha256ripemd => ripemd_160(&sha256(data)), + } + } +} diff --git a/rust/tw_hash/src/lib.rs b/rust/tw_hash/src/lib.rs index 24a6f5d3121..8020e6cde19 100644 --- a/rust/tw_hash/src/lib.rs +++ b/rust/tw_hash/src/lib.rs @@ -6,12 +6,34 @@ pub mod blake; pub mod blake2; +pub mod crc32; pub mod ffi; pub mod groestl; +pub mod hasher; pub mod hmac; pub mod ripemd; pub mod sha1; pub mod sha2; pub mod sha3; +mod hash_array; mod hash_wrapper; + +pub use hash_array::{concat, Hash, H160, H256, H264, H32, H512, H520}; + +use tw_encoding::hex::FromHexError; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + FromHexError(FromHexError), + InvalidHashLength, + InvalidArgument, +} + +impl From for Error { + fn from(e: FromHexError) -> Self { + Error::FromHexError(e) + } +} diff --git a/rust/tw_hash/src/sha2.rs b/rust/tw_hash/src/sha2.rs index ad448769bbf..0ac313162c2 100644 --- a/rust/tw_hash/src/sha2.rs +++ b/rust/tw_hash/src/sha2.rs @@ -5,7 +5,11 @@ // file LICENSE at the root of the source code distribution tree. use crate::hash_wrapper::hasher; -use sha2::{Sha256, Sha512, Sha512_256}; +use sha2::{Sha224, Sha256, Sha512, Sha512_256}; + +pub fn sha224(input: &[u8]) -> Vec { + hasher::(input) +} pub fn sha256(input: &[u8]) -> Vec { hasher::(input) diff --git a/rust/tw_hash/tests/hash_ffi_tests.rs b/rust/tw_hash/tests/hash_ffi_tests.rs index 9fc13561154..a66f6dd13d9 100644 --- a/rust/tw_hash/tests/hash_ffi_tests.rs +++ b/rust/tw_hash/tests/hash_ffi_tests.rs @@ -4,18 +4,22 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use tw_encoding::hex; +use tw_encoding::hex::FromHexError; use tw_hash::ffi::{ blake2_b, blake2_b_personal, blake_256, groestl_512, hmac__sha256, keccak256, keccak512, - ripemd_160, sha1, sha256, sha3__256, sha3__512, sha512, sha512_256, + ripemd_160, sha1, sha256, sha3__256, sha3__512, sha512, sha512_256, CHashingCode, }; +use tw_hash::Error; use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::c_result::ErrorCode; type ExternFn = unsafe extern "C" fn(*const u8, usize) -> CByteArray; #[track_caller] pub fn test_hash_helper(hash: ExternFn, input: &[u8], expected: &str) { let decoded = unsafe { hash(input.as_ptr(), input.len()).into_vec() }; - assert_eq!(hex::encode(decoded), expected); + assert_eq!(hex::encode(decoded, false), expected); } #[test] @@ -24,7 +28,7 @@ fn test_blake2b() { /// Declare a `blake2_b` helper that forwards `HASH_SIZE`. unsafe extern "C" fn blake2_b_hash(input: *const u8, input_len: usize) -> CByteArray { - blake2_b(input, input_len, HASH_SIZE) + blake2_b(input, input_len, HASH_SIZE).unwrap() } test_hash_helper(blake2_b_hash, b"Hello world", "6ff843ba685842aa82031d3f53c48b66326df7639a63d128974c5c14f31a0f33343a8c65551134ed1ae0f2b0dd2bb495dc81039e3eeb0aa1bb0388bbeac29183"); @@ -44,10 +48,11 @@ fn test_blake2b_personal() { personal_data.as_ptr(), personal_data.len(), ) + .unwrap() .into_vec() }; let expected = "20d9cd024d4fb086aae819a1432dd2466de12947831b75c5a30cf2676095d3b4"; - assert_eq!(hex::encode(actual), expected); + assert_eq!(hex::encode(actual, false), expected); } #[test] @@ -81,7 +86,7 @@ fn test_hmac_sha256() { let actual = unsafe { hmac__sha256(key.as_ptr(), key.len(), data.as_ptr(), data.len()).into_vec() }; let expected = "a7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab".to_string(); - assert_eq!(hex::encode(actual), expected); + assert_eq!(hex::encode(actual, false), expected); } #[test] @@ -210,3 +215,20 @@ fn test_sha3_512() { "01dedd5de4ef14642445ba5f5b97c15e47b9ad931326e4b0727cd94cefc44fff23f07bf543139939b49128caf436dc1bdee54fcb24023a08d9403f9b4bf0d450", ); } + +#[test] +fn test_c_hashing_error_convert() { + assert_eq!(ErrorCode::from(CHashingCode::Ok), 0); + assert_eq!(ErrorCode::from(CHashingCode::InvalidHashLength), 1); + assert_eq!(ErrorCode::from(CHashingCode::InvalidArgument), 2); + + assert_eq!( + CHashingCode::InvalidArgument, + Error::FromHexError(FromHexError::OddLength).into(), + ); + assert_eq!(CHashingCode::InvalidArgument, Error::InvalidArgument.into()); + assert_eq!( + CHashingCode::InvalidHashLength, + Error::InvalidHashLength.into(), + ); +} diff --git a/rust/tw_internet_computer/Cargo.toml b/rust/tw_internet_computer/Cargo.toml new file mode 100644 index 00000000000..7fd375e1071 --- /dev/null +++ b/rust/tw_internet_computer/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tw_internet_computer" +version = "0.1.0" +edition = "2021" + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0.136", features = ["derive"] } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_proto = { path = "../tw_proto" } + +[build-dependencies] +pb-rs = "0.10.0" diff --git a/rust/tw_internet_computer/build.rs b/rust/tw_internet_computer/build.rs new file mode 100644 index 00000000000..a163f826026 --- /dev/null +++ b/rust/tw_internet_computer/build.rs @@ -0,0 +1,54 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use pb_rs::types::FileDescriptor; +use pb_rs::ConfigBuilder; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +fn main() { + let proto_ext = Some(Path::new("proto").as_os_str()); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + + let proto_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src") + .join("transactions") + .join("proto"); + let proto_dir_str = proto_dir.to_str().expect("Invalid proto directory path"); + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={}", proto_dir_str); + + let protos: Vec<_> = fs::read_dir(&proto_dir) + .expect("Expected a valid directory with proto files") + .filter_map(|file| { + let file = file.ok()?; + if file.path().extension() != proto_ext { + return None; + } + + let path = file.path(); + let path_str = path.to_str().expect("Invalid Proto file name"); + println!("cargo:rerun-if-changed={}", path_str); + Some(path) + }) + .collect(); + + // Delete all old generated files before re-generating new ones + if out_dir.exists() { + fs::remove_dir_all(&out_dir).expect("Error removing out directory"); + } + fs::DirBuilder::new() + .create(&out_dir) + .expect("Error creating out directory"); + + let out_protos = ConfigBuilder::new(&protos, None, Some(&out_dir), &[proto_dir]) + .expect("Error configuring pb-rs builder") + .dont_use_cow(true) + .owned(true) + .build(); + FileDescriptor::run(&out_protos).expect("Error generating proto files"); +} diff --git a/rust/tw_internet_computer/fuzz/.gitignore b/rust/tw_internet_computer/fuzz/.gitignore new file mode 100644 index 00000000000..1a45eee7760 --- /dev/null +++ b/rust/tw_internet_computer/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/rust/tw_internet_computer/fuzz/Cargo.lock b/rust/tw_internet_computer/fuzz/Cargo.lock new file mode 100644 index 00000000000..887d622e7ea --- /dev/null +++ b/rust/tw_internet_computer/fuzz/Cargo.lock @@ -0,0 +1,1595 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d1988118c887f61418940e322d574e8a2dd67165f1f1556eaae22e4019c6af" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "ppv-lite86", +] + +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "blake2b-ref" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294d17c72e0ba59fad763caa112368d0672083779cdebbb97164f4bb4c1e339a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "groestl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343cfc165f92a988fd60292f7a0bfde4352a5a0beff9fbec29251ca4e9676e4d" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.7", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.7", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d640b40e81625f53b59f9c5812d3fb9e7ce11971d3fb34268eb7a83adf98051" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac464815d51fff2f64d690bf6ea4442d365e53495d50737685cfecfa3dd3f62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pb-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" +dependencies = [ + "clap", + "env_logger", + "log", + "nom", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "starknet-crypto" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693e6362f150f9276e429a910481fb7f3bcb8d6aa643743f587cfece0b374874" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2 0.10.7", + "starknet-crypto-codegen", + "starknet-curve 0.3.0", + "starknet-ff", + "zeroize", +] + +[[package]] +name = "starknet-crypto-codegen" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" +dependencies = [ + "starknet-curve 0.4.0", + "starknet-ff", + "syn 2.0.31", +] + +[[package]] +name = "starknet-curve" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252610baff59e4c4332ce3569f7469c5d3f9b415a2240d698fb238b2b4fc0942" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-curve" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68a0d87ae56572abf83ddbfd44259a7c90dbeeee1629a1ffe223e7f9a8f3052" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-ff" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2cb1d9c0a50380cddab99cb202c6bfb3332728a2769bd0ca2ee80b0b390dd4" +dependencies = [ + "ark-ff", + "bigdecimal", + "crypto-bigint", + "getrandom 0.2.10", + "hex", + "serde", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tw_coin_entry" +version = "0.1.0" +dependencies = [ + "serde_json", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_encoding" +version = "0.1.0" +dependencies = [ + "bs58", + "ciborium", + "data-encoding", + "hex", + "serde", + "tw_memory", +] + +[[package]] +name = "tw_hash" +version = "0.1.0" +dependencies = [ + "blake-hash", + "blake2b-ref", + "digest 0.10.7", + "groestl", + "hmac", + "ripemd", + "serde", + "sha1", + "sha2 0.10.7", + "sha3", + "tw_encoding", + "tw_memory", + "zeroize", +] + +[[package]] +name = "tw_internet_computer" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_internet_computer-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "tw_internet_computer", + "tw_keypair", +] + +[[package]] +name = "tw_keypair" +version = "0.1.0" +dependencies = [ + "blake2", + "curve25519-dalek", + "der", + "digest 0.9.0", + "ecdsa", + "k256", + "lazy_static", + "p256", + "pkcs8", + "rfc6979", + "serde", + "sha2 0.9.9", + "starknet-crypto", + "starknet-ff", + "tw_encoding", + "tw_hash", + "tw_memory", + "tw_misc", + "zeroize", +] + +[[package]] +name = "tw_memory" +version = "0.1.0" + +[[package]] +name = "tw_misc" +version = "0.1.0" +dependencies = [ + "zeroize", +] + +[[package]] +name = "tw_number" +version = "0.1.0" +dependencies = [ + "primitive-types", + "tw_hash", + "tw_memory", +] + +[[package]] +name = "tw_proto" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "tw_encoding", + "tw_memory", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.31", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] diff --git a/rust/tw_internet_computer/fuzz/Cargo.toml b/rust/tw_internet_computer/fuzz/Cargo.toml new file mode 100644 index 00000000000..a31848778a8 --- /dev/null +++ b/rust/tw_internet_computer/fuzz/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tw_internet_computer-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +[dependencies.tw_internet_computer] +path = ".." + + +[dependencies.tw_keypair] +path = "../../tw_keypair" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "tw_internet_computer_transfer" +path = "fuzz_targets/tw_internet_computer_transfer.rs" +test = false +doc = false diff --git a/rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs b/rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs new file mode 100644 index 00000000000..cfd4b494762 --- /dev/null +++ b/rust/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs @@ -0,0 +1,70 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_internet_computer::{ + address::AccountIdentifier, + protocol::principal::Principal, + transactions::transfer::{transfer, TransferArgs}, +}; +use tw_keypair::ecdsa::secp256k1; + +#[derive(Debug, arbitrary::Arbitrary)] +struct ArbitraryTransferArgs { + memo: u64, + amount: u64, + max_fee: Option, + #[arbitrary(with = arbitrary_to_field)] + to: String, + current_timestamp_nanos: u64, +} + +fn arbitrary_to_field(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mut buf = [0; 29]; + u.fill_buffer(&mut buf)?; + let principal = Principal::from_slice(&buf); + let account_identifier = AccountIdentifier::new(&principal); + Ok(account_identifier.to_hex()) +} + +impl From<&ArbitraryTransferArgs> for TransferArgs { + fn from(prev: &ArbitraryTransferArgs) -> TransferArgs { + TransferArgs { + memo: prev.memo, + amount: prev.amount, + max_fee: prev.max_fee, + to: prev.to.clone(), + current_timestamp_nanos: prev.current_timestamp_nanos, + } + } +} + +#[derive(Debug, arbitrary::Arbitrary)] +struct TWInternetComputerTransactionsTransferInput { + #[arbitrary(with = arbitrary_private_key)] + private: Vec, + #[arbitrary(with = arbitrary_canister_id)] + canister_id: Principal, + args: ArbitraryTransferArgs, +} + +fn arbitrary_private_key(u: &mut arbitrary::Unstructured) -> arbitrary::Result> { + let mut buf = [0; 32]; + u.fill_buffer(&mut buf)?; + Ok(Vec::from(buf.as_slice())) +} + +fn arbitrary_canister_id(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mut buf = [0; 29]; + u.fill_buffer(&mut buf)?; + let principal = Principal::from_slice(&buf); + Ok(principal) +} + +fuzz_target!(|input: TWInternetComputerTransactionsTransferInput| { + let Ok(private_key) = secp256k1::PrivateKey::try_from(input.private.as_slice()) else { + return; + }; + + let args = TransferArgs::from(&input.args); + transfer(private_key, input.canister_id, args).ok(); +}); diff --git a/rust/tw_internet_computer/src/address.rs b/rust/tw_internet_computer/src/address.rs new file mode 100644 index 00000000000..17ba0ed3ee4 --- /dev/null +++ b/rust/tw_internet_computer/src/address.rs @@ -0,0 +1,184 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::{ + coin_entry::CoinAddress, + error::{AddressError, AddressResult}, +}; +use tw_encoding::hex; +use tw_hash::{crc32::crc32, sha2::sha224, H256}; +use tw_keypair::ecdsa::secp256k1::PublicKey; + +use crate::protocol::principal::Principal; + +pub trait IcpAddress: std::str::FromStr + Into { + fn from_str_optional(s: &str) -> AddressResult> { + if s.is_empty() { + return Ok(None); + } + + Self::from_str(s).map(Some) + } +} + +/// The ICP ledger keeps track of accounts using account identifiers. +/// An account identifier is created by `SHA-224` hashing: +/// * \x0Aaccount-id +/// * the owner's principal ID +/// * subaccount (32-bytes) +/// Then, +/// * CRC32 the SHA-224 hash +/// * Prepend the CRC32 to the SHA-224. +/// https://internetcomputer.org/docs/current/references/ledger/#_accounts +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountIdentifier { + bytes: H256, +} + +impl AccountIdentifier { + /// Create a default account identifier from the given principal owner. + pub fn new(owner: &Principal) -> Self { + let mut input = vec![]; + input.extend_from_slice(b"\x0Aaccount-id"); + input.extend_from_slice(owner.as_slice()); + input.extend_from_slice(&H256::new()[..]); + + let hash = sha224(&input); + let crc32_bytes = crc32(&hash).to_be_bytes(); + + let mut bytes = H256::new(); + bytes[0..4].copy_from_slice(&crc32_bytes); + bytes[4..32].copy_from_slice(&hash); + Self { bytes } + } + + /// Return the textual-encoded account identifier. + pub fn to_hex(&self) -> String { + hex::encode(self.bytes, false) + } + + /// Instantiate an account identifier from a hex-encoded string. + pub fn from_hex(hex_str: &str) -> AddressResult { + if hex_str.len() != 64 { + return Err(AddressError::FromHexError); + } + + let hex = H256::try_from( + hex::decode(hex_str) + .map_err(|_| AddressError::FromHexError)? + .as_slice(), + ) + .map_err(|_| AddressError::FromHexError)?; + + if !is_check_sum_valid(hex) { + return Err(AddressError::FromHexError); + } + + Ok(Self { bytes: hex }) + } +} + +impl From<&PublicKey> for AccountIdentifier { + /// Takes a Secp256k1 public key, DER-encodes the public key, + /// and creates a principal from the encoding. + fn from(public_key: &PublicKey) -> Self { + let principal = Principal::from(public_key); + AccountIdentifier::new(&principal) + } +} + +impl std::str::FromStr for AccountIdentifier { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + AccountIdentifier::from_hex(s).map_err(|_| AddressError::FromHexError) + } +} + +impl std::fmt::Display for AccountIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +impl CoinAddress for AccountIdentifier { + fn data(&self) -> tw_memory::Data { + self.bytes.into_vec() + } +} + +impl IcpAddress for AccountIdentifier {} + +impl AsRef<[u8]> for AccountIdentifier { + fn as_ref(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +fn is_check_sum_valid(hash: H256) -> bool { + let found_checksum = &hash[0..4]; + let expected_checksum = crc32(&hash[4..]).to_be_bytes(); + found_checksum == expected_checksum +} + +#[cfg(test)] +mod test { + use tw_keypair::ecdsa::secp256k1::PublicKey; + + use super::*; + + const VALID_ADDRESSES: [&str; 10] = [ + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + "a93fff2708a6305e8946a0a06cbf559da01a758da58a615d404037b08ea96181", + "6e66c78a45cec01bcd0efd6dd142a82dc63b1a591c4ccb3c5877cd4d667747b4", + "c4ca697b46bb89ebf19eef3ad7b5e7bfa73315c0433a68a12a67f60fe017b9ad", + "7c513ec0de7347555b75cfefe29e56689de36636321fb0c8addf24a4f934ff0b", + "f61e15cdcaf0325bbaeb9a23a9f49d5447b33e6feee9763c2fdfe3a986142912", + "bb3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", + ]; + + const TOO_SHORT_ADDRESS: &str = + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b"; + const TOO_LONG_ADDRESS: &str = + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce"; + const INVALID_CHECKSUM_ADDRESS: &str = + "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278"; + + const PUBLIC_KEY_HEX: &str = "048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8"; + + #[test] + fn from_hex() { + assert!(VALID_ADDRESSES + .iter() + .all(|s| AccountIdentifier::from_hex(s).is_ok())); + + assert!( + AccountIdentifier::from_hex(TOO_SHORT_ADDRESS).is_err(), + "Address is too short" + ); + assert!( + AccountIdentifier::from_hex(TOO_LONG_ADDRESS).is_err(), + "Address is too long" + ); + assert!( + AccountIdentifier::from_hex(INVALID_CHECKSUM_ADDRESS).is_err(), + "Invalid checksum" + ); + } + + #[test] + fn from_public_key() { + let public_key = PublicKey::try_from(PUBLIC_KEY_HEX).expect("Failed to populate key"); + let address = AccountIdentifier::from(&public_key); + assert_eq!( + address.to_hex(), + "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211" + ); + } +} diff --git a/rust/tw_internet_computer/src/context.rs b/rust/tw_internet_computer/src/context.rs new file mode 100644 index 00000000000..45e2a3d5e5d --- /dev/null +++ b/rust/tw_internet_computer/src/context.rs @@ -0,0 +1,77 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{ + address::{AccountIdentifier, IcpAddress}, + protocol::principal::Principal, +}; + +pub trait InternetComputerContext { + type Address: IcpAddress; + + fn get_canister_id() -> Principal; +} + +#[derive(Default)] +pub struct StandardInternetComputerContext; + +impl InternetComputerContext for StandardInternetComputerContext { + type Address = AccountIdentifier; + + fn get_canister_id() -> Principal { + // ICP Ledger Canister + Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap() + } +} + +#[cfg(test)] +mod test { + + use std::marker::PhantomData; + + use tw_coin_entry::error::AddressResult; + + use super::*; + + const TEST_OWNER_PRINCIPAL_ID: &str = + "t4u4z-y3dur-j63pk-nw4rv-yxdbt-agtt6-nygn7-ywh6y-zm2f4-sdzle-3qe"; + const TEST_TEXTUAL_ICP_ADDRESS: &str = + "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a"; + const TEXTUAL_ICP_LEDGER_CANISTER_ID: &str = "ryjl3-tyaaa-aaaaa-aaaba-cai"; + + pub struct ContextTest { + _phantom: PhantomData, + } + + impl ContextTest { + fn get_canister_id() -> Principal { + Context::get_canister_id() + } + + fn account_identifier_optional(s: &str) -> AddressResult> { + Context::Address::from_str_optional(s) + } + } + + #[test] + fn standard_internet_computer_context_canister_address() { + let owner = Principal::from_text(TEST_OWNER_PRINCIPAL_ID).unwrap(); + let expected_account_identifier = AccountIdentifier::new(&owner); + let account_identifier = + ContextTest::::account_identifier_optional( + TEST_TEXTUAL_ICP_ADDRESS, + ) + .unwrap() + .unwrap(); + assert_eq!(expected_account_identifier, account_identifier); + } + + #[test] + fn standard_internet_computer_context_canister_type() { + let ledger_canister_id = ContextTest::::get_canister_id(); + assert_eq!(ledger_canister_id.to_text(), TEXTUAL_ICP_LEDGER_CANISTER_ID); + } +} diff --git a/rust/tw_internet_computer/src/entry.rs b/rust/tw_internet_computer/src/entry.rs new file mode 100644 index 00000000000..6fb47ba161c --- /dev/null +++ b/rust/tw_internet_computer/src/entry.rs @@ -0,0 +1,111 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; + +use tw_coin_entry::{ + coin_context::CoinContext, + coin_entry::CoinEntry, + error::{AddressError, AddressResult, SigningError}, + modules::{ + json_signer::NoJsonSigner, message_signer::NoMessageSigner, plan_builder::NoPlanBuilder, + }, + prefix::NoPrefix, + signing_output_error, +}; + +use tw_proto::{ + Common::Proto::SigningError as CommonError, InternetComputer::Proto, + TxCompiler::Proto as CompilerProto, +}; + +use crate::{address::AccountIdentifier, context::StandardInternetComputerContext, signer::Signer}; + +pub struct InternetComputerEntry; + +impl CoinEntry for InternetComputerEntry { + type AddressPrefix = NoPrefix; + + type Address = AccountIdentifier; + + type SigningInput<'a> = Proto::SigningInput<'a>; + + type SigningOutput = Proto::SigningOutput<'static>; + + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + type JsonSigner = NoJsonSigner; + + type PlanBuilder = NoPlanBuilder; + + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Self::Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Self::Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + public_key: tw_keypair::tw::PublicKey, + _derivation: tw_coin_entry::derivation::Derivation, + _prefix: Option, + ) -> tw_coin_entry::error::AddressResult { + let secp256k1_public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Self::Address::from(secp256k1_public_key)) + } + + #[inline] + fn sign( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + fn preimage_hashes( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + _input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + signing_output_error!( + CompilerProto::PreSigningOutput, + SigningError(CommonError::Error_not_supported) + ) + } + + fn compile( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + _input: Self::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> Self::SigningOutput { + signing_output_error!( + Proto::SigningOutput, + SigningError(CommonError::Error_not_supported) + ) + } +} diff --git a/rust/tw_internet_computer/src/lib.rs b/rust/tw_internet_computer/src/lib.rs new file mode 100644 index 00000000000..c32ab978e54 --- /dev/null +++ b/rust/tw_internet_computer/src/lib.rs @@ -0,0 +1,12 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod context; +pub mod entry; +pub mod protocol; +pub mod signer; +pub mod transactions; diff --git a/rust/tw_internet_computer/src/protocol/envelope.rs b/rust/tw_internet_computer/src/protocol/envelope.rs new file mode 100644 index 00000000000..cfa00cdd274 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/envelope.rs @@ -0,0 +1,237 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::collections::BTreeMap; + +use serde::{Serialize, Serializer}; + +use super::{ + principal::Principal, + request_id::{hash_of_map, RawHttpRequestVal, RequestId}, +}; + +pub trait RepresentationHashable { + fn request_id(&self) -> RequestId; +} + +#[derive(Debug, Clone, Serialize)] +pub struct Envelope { + pub content: C, + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_pubkey: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_sig: Option>, +} + +/// A replicated call to a canister method, whether update or query. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "request_type", rename = "call")] +pub struct EnvelopeCallContent { + /// A random series of bytes to uniquely identify this message. + #[serde( + default, + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_nonce" + )] + pub nonce: Option>, + /// A nanosecond timestamp after which this request is no longer valid. + pub ingress_expiry: u64, + /// The principal that is sending this request. + pub sender: Principal, + /// The ID of the canister to be called. + pub canister_id: Principal, + /// The name of the canister method to be called. + pub method_name: String, + /// The argument to pass to the canister method. + #[serde(serialize_with = "serialize_arg")] + pub arg: Vec, +} + +impl RepresentationHashable for EnvelopeCallContent { + fn request_id(&self) -> RequestId { + let mut map = vec![ + ( + "request_type".to_string(), + RawHttpRequestVal::String("call".to_string()), + ), + ( + "canister_id".to_string(), + RawHttpRequestVal::Bytes(self.canister_id.as_slice().to_vec()), + ), + ( + "method_name".to_string(), + RawHttpRequestVal::String(self.method_name.to_string()), + ), + ( + "arg".to_string(), + RawHttpRequestVal::Bytes(self.arg.clone()), + ), + ( + "ingress_expiry".to_string(), + RawHttpRequestVal::U64(self.ingress_expiry), + ), + ( + "sender".to_string(), + RawHttpRequestVal::Bytes(self.sender.as_slice().to_vec()), + ), + ] + .into_iter() + .collect::>(); + + if let Some(some_nonce) = &self.nonce { + map.insert( + "nonce".to_string(), + RawHttpRequestVal::Bytes(some_nonce.clone()), + ); + } + RequestId(hash_of_map(&map)) + } +} + +/// A request for information from the [IC state tree](https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree). +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "request_type", rename = "read_state")] +pub struct EnvelopeReadStateContent { + /// A nanosecond timestamp after which this request is no longer valid. + pub ingress_expiry: u64, + /// The principal that is sending this request. + pub sender: Principal, + /// A list of paths within the state tree to fetch. + pub paths: Vec>, +} + +impl RepresentationHashable for EnvelopeReadStateContent { + fn request_id(&self) -> RequestId { + let map = vec![ + ( + "request_type".to_string(), + RawHttpRequestVal::String("read_state".to_string()), + ), + ( + "ingress_expiry".to_string(), + RawHttpRequestVal::U64(self.ingress_expiry), + ), + ( + "paths".to_string(), + RawHttpRequestVal::Array( + self.paths + .iter() + .map(|p| { + RawHttpRequestVal::Array( + p.iter() + .map(|b| RawHttpRequestVal::Bytes(b.as_slice().to_vec())) + .collect(), + ) + }) + .collect(), + ), + ), + ( + "sender".to_string(), + RawHttpRequestVal::Bytes(self.sender.as_slice().to_vec()), + ), + ] + .into_iter() + .collect::>(); + + RequestId(hash_of_map(&map)) + } +} + +fn serialize_arg(arg: &[u8], s: S) -> Result +where + S: Serializer, +{ + s.serialize_bytes(arg) +} + +fn serialize_nonce(nonce: &Option>, s: S) -> Result +where + S: Serializer, +{ + match nonce { + Some(nonce) => s.serialize_bytes(nonce), + None => s.serialize_none(), + } +} + +#[derive(Debug, Clone)] +pub struct Label(Vec); + +impl Label { + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl From<&str> for Label { + fn from(value: &str) -> Self { + Label(value.as_bytes().to_vec()) + } +} + +impl From for Label { + fn from(value: RequestId) -> Self { + Label(value.0.as_slice().to_vec()) + } +} + +// Serialization +impl serde::Serialize for Label { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(self.0.as_slice()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn representation_independent_hash_call_or_query() { + let content = EnvelopeCallContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + canister_id: Principal::from_slice(&[0, 0, 0, 0, 0, 0, 4, 210]), + method_name: "hello".to_string(), + arg: b"DIDL\x00\xFD*".to_vec(), + nonce: None, + }; + + assert_eq!( + tw_encoding::hex::encode(content.request_id().0, false), + "1d1091364d6bb8a6c16b203ee75467d59ead468f523eb058880ae8ec80e2b101" + ); + } + + #[test] + fn representation_independent_hash_read_state() { + let content = EnvelopeCallContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + canister_id: Principal::from_slice(&[0, 0, 0, 0, 0, 0, 4, 210]), + method_name: "hello".to_string(), + arg: b"DIDL\x00\xFD*".to_vec(), + nonce: None, + }; + let update_request_id = content.request_id(); + + let content = EnvelopeReadStateContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + paths: vec![vec![ + Label::from("request_status"), + Label::from(update_request_id), + ]], + }; + let request_id = content.request_id(); + assert_eq!( + tw_encoding::hex::encode(request_id.0, false), + "3cde0f14a953c3afbe1335f22e861bb62389f1449beca02707ab197e6829c2a3" + ); + } +} diff --git a/rust/tw_internet_computer/src/protocol/identity.rs b/rust/tw_internet_computer/src/protocol/identity.rs new file mode 100644 index 00000000000..ed8ce0f7cbb --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/identity.rs @@ -0,0 +1,116 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_hash::H256; +use tw_keypair::{ecdsa::secp256k1::PrivateKey, traits::SigningKeyTrait, KeyPairError}; + +use super::principal::Principal; + +#[derive(Debug)] +pub enum SigningError { + Failed(KeyPairError), +} + +/// Contains the signature and the associated DER-encoded public key from a call to +/// [Identity::sign]. +pub struct IdentitySignature { + pub signature: Vec, + pub public_key: Vec, +} + +/// An identity is a simple way to abstract away signing for envelopes. +/// When creating a request to the IC, the sender and signature are required +/// for authentication purposes. The sender is derived from a DER-encode public key +/// and the signature is created using the private key. +pub struct Identity { + private_key: PrivateKey, + der_encoded_public_key: Vec, +} + +impl Identity { + /// Gets the public key and DER-encodes it and returns an Identity. + pub fn new(private_key: PrivateKey) -> Self { + let public_key = private_key.public(); + let der_encoded_public_key = public_key.der_encoded(); + + Self { + private_key, + der_encoded_public_key, + } + } + + /// Returns the principal of the private key. + /// Sender represents who is sending the request. + pub fn sender(&self) -> Principal { + Principal::self_authenticating(&self.der_encoded_public_key) + } + + /// Signs the given content with the private key. + /// The signatures are encoded as the concatenation of the 32-byte big endian + /// encodings of the two values r and s. + /// + /// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#ecdsa + pub fn sign(&self, content: H256) -> Result { + let signature = self + .private_key + .sign(content) + .map_err(SigningError::Failed)?; + + let r = signature.r(); + let s = signature.s(); + let mut bytes = [0u8; 64]; + bytes[..32].clone_from_slice(r.as_slice()); + bytes[32..].clone_from_slice(s.as_slice()); + + let signature = IdentitySignature { + public_key: self.der_encoded_public_key.clone(), + signature: bytes.to_vec(), //Signature bytes + }; + + Ok(signature) + } +} + +#[cfg(test)] +mod test { + + use tw_encoding::hex; + + use super::*; + + /// Test that the sender is derived from the private key. + #[test] + fn sender() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let identity = Identity::new(private_key); + let sender = identity.sender(); + assert_eq!( + sender.to_text(), + "hpikg-6exdt-jn33w-ndty3-fc7jc-tl2lr-buih3-cs3y7-tftkp-sfp62-gqe" + ) + } + + /// Test signing with the identity. + #[test] + fn sign() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let der_encoded_public_key = private_key.public().der_encoded(); + let identity = Identity::new(private_key); + let content = H256::new(); + let signature = identity.sign(content).unwrap(); + assert_eq!( + hex::encode(signature.signature, false), + "17c0974ee2ae621099389a5e4d0f960925d2e0e23658df03069308fb8edcb7bb120a338ada3e2ede7f41f6ed2f424a8a4f2c8fb68260f27d4f1bf96d19094b9f" + ); + assert_eq!(der_encoded_public_key, signature.public_key); + } +} diff --git a/rust/tw_internet_computer/src/protocol/mod.rs b/rust/tw_internet_computer/src/protocol/mod.rs new file mode 100644 index 00000000000..f21353b57b7 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/mod.rs @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod envelope; +pub mod identity; +pub mod principal; +pub mod request_id; +pub mod rosetta; + +use std::time::Duration; + +/// This constant defines the maximum amount of time an ingress message can wait +/// to start executing after submission before it is expired. Hence, if an +/// ingress message is submitted at time `t` and it has not been scheduled for +/// execution till time `t+MAX_INGRESS_TTL`, it will be expired. +/// +/// At the time of writing, this constant is also used to control how long the +/// status of a completed ingress message (IngressStatus ∈ [Completed, Failed]) +/// is maintained by the IC before it is deleted from the ingress history. +const MAX_INGRESS_TTL: Duration = Duration::from_secs(5 * 60); + +/// Duration subtracted from `MAX_INGRESS_TTL` by +/// `expiry_time_from_now()` when creating an ingress message. +const PERMITTED_DRIFT: Duration = Duration::from_secs(60); + +/// An upper limit on the validity of the request, expressed in nanoseconds since 1970-01-01. +pub fn get_ingress_expiry(current_timestamp_duration: Duration) -> u64 { + current_timestamp_duration + .saturating_add(MAX_INGRESS_TTL) + .saturating_sub(PERMITTED_DRIFT) + .as_nanos() as u64 +} diff --git a/rust/tw_internet_computer/src/protocol/principal.rs b/rust/tw_internet_computer/src/protocol/principal.rs new file mode 100644 index 00000000000..61680e14f48 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/principal.rs @@ -0,0 +1,256 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +/// Taken from candid crate and modified to rely upon built-in crc32 and SHA224 functionality. +use std::fmt::Write; + +use tw_hash::{crc32::crc32, sha2::sha224}; +use tw_keypair::ecdsa::secp256k1::PublicKey; + +/// An error happened while encoding, decoding or serializing a [`Principal`]. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PrincipalError { + BytesTooLong, + InvalidBase32, + TextTooShort, + TextTooLong, + CheckSequenceNotMatch, + AbnormalGrouped(Principal), +} + +/// Generic ID on Internet Computer. +/// +/// Principals are generic identifiers for canisters, users +/// and possibly other concepts in the future. +/// As far as most uses of the IC are concerned they are +/// opaque binary blobs with a length between 0 and 29 bytes, +/// and there is intentionally no mechanism to tell canister ids and user ids apart. +/// +/// Note a principal is not necessarily tied with a public key-pair, +/// yet we need at least a key-pair of a related principal to sign +/// requests. +/// +/// A Principal can be serialized to a byte array ([`Vec`]) or a text +/// representation, but the inner structure of the byte representation +/// is kept private. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Principal { + /// Length. + len: u8, + + /// The content buffer. When returning slices this should always be sized according to + /// `len`. + bytes: [u8; Self::MAX_LENGTH_IN_BYTES], +} + +impl Principal { + const MAX_LENGTH_IN_BYTES: usize = 29; + const CRC_LENGTH_IN_BYTES: usize = 4; + + const SELF_AUTHENTICATING_TAG: u8 = 2; + const ANONYMOUS_TAG: u8 = 4; + + /// Construct a [`Principal`] of the IC management canister + pub const fn management_canister() -> Self { + Self { + len: 0, + bytes: [0; Self::MAX_LENGTH_IN_BYTES], + } + } + + /// Construct a self-authenticating ID from public key + pub fn self_authenticating>(public_key: P) -> Self { + let public_key = public_key.as_ref(); + let hash = sha224(public_key); + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[..Self::MAX_LENGTH_IN_BYTES - 1].copy_from_slice(hash.as_slice()); + bytes[Self::MAX_LENGTH_IN_BYTES - 1] = Self::SELF_AUTHENTICATING_TAG; + + Self { + len: Self::MAX_LENGTH_IN_BYTES as u8, + bytes, + } + } + + /// Construct an anonymous ID. + pub const fn anonymous() -> Self { + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[0] = Self::ANONYMOUS_TAG; + Self { len: 1, bytes } + } + + /// Construct a [`Principal`] from a slice of bytes. + /// + /// # Panics + /// + /// Panics if the slice is longer than 29 bytes. + pub fn from_slice(slice: &[u8]) -> Self { + match Self::try_from_slice(slice) { + Ok(v) => v, + _ => panic!("slice length exceeds capacity"), + } + } + + /// Construct a [`Principal`] from a slice of bytes. + pub fn try_from_slice(slice: &[u8]) -> Result { + const MAX_LENGTH_IN_BYTES: usize = Principal::MAX_LENGTH_IN_BYTES; + if slice.len() > MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::BytesTooLong); + } + + let mut bytes = [0; MAX_LENGTH_IN_BYTES]; + bytes[0..slice.len()].copy_from_slice(slice); + + Ok(Self { + len: slice.len() as u8, + bytes, + }) + } + + /// Parse a [`Principal`] from text representation. + pub fn from_text>(text: S) -> Result { + // Strategy: Parse very liberally, then pretty-print and compare output + // This is both simpler and yields better error messages + + let mut s = text.as_ref().to_string(); + s.make_ascii_uppercase(); + s.retain(|c| c != '-'); + + let bytes = tw_encoding::base32::decode(&s, None, false) + .map_err(|_| PrincipalError::InvalidBase32)?; + + if bytes.len() < Self::CRC_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooShort); + } + + let crc_bytes = &bytes[..Self::CRC_LENGTH_IN_BYTES]; + let data_bytes = &bytes[Self::CRC_LENGTH_IN_BYTES..]; + if data_bytes.len() > Self::MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooLong); + } + + if crc32(data_bytes).to_be_bytes() != crc_bytes { + return Err(PrincipalError::CheckSequenceNotMatch); + } + + // Already checked data_bytes.len() <= MAX_LENGTH_IN_BYTES + // safe to unwrap here + let result = Self::try_from_slice(data_bytes).unwrap(); + let expected = format!("{result}"); + + // In the Spec: + // The textual representation is conventionally printed with lower case letters, + // but parsed case-insensitively. + if text.as_ref().to_ascii_lowercase() != expected { + return Err(PrincipalError::AbnormalGrouped(result)); + } + Ok(result) + } + + /// Convert [`Principal`] to text representation. + pub fn to_text(&self) -> String { + format!("{self}") + } + + /// Return the [`Principal`]'s underlying slice of bytes. + #[inline] + pub fn as_slice(&self) -> &[u8] { + &self.bytes[..self.len as usize] + } +} + +impl std::str::FromStr for Principal { + type Err = PrincipalError; + + fn from_str(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl From<&PublicKey> for Principal { + /// Takes a Secp256k1 public key, DER-encodes the public key, + /// and creates a principal from the encoding. + fn from(public_key: &PublicKey) -> Self { + Self::self_authenticating(public_key.der_encoded()) + } +} + +impl std::fmt::Display for Principal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let blob: &[u8] = self.as_slice(); + + // calc checksum + let checksum = crc32(blob); + + // combine blobs + let mut bytes = vec![]; + bytes.extend_from_slice(&checksum.to_be_bytes()); + bytes.extend_from_slice(blob); + + // base32 + let mut s = + tw_encoding::base32::encode(&bytes, None, false).map_err(|_| std::fmt::Error)?; + s.make_ascii_lowercase(); + + // write out string with dashes + let mut s = s.as_str(); + while s.len() > 5 { + f.write_str(&s[..5])?; + f.write_char('-')?; + s = &s[5..]; + } + f.write_str(s) + } +} + +impl TryFrom<&str> for Principal { + type Error = PrincipalError; + + fn try_from(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl TryFrom> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&Vec> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&[u8]> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &[u8]) -> Result { + Self::try_from_slice(bytes) + } +} + +impl AsRef<[u8]> for Principal { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +// Serialization +impl serde::Serialize for Principal { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.to_text().serialize(serializer) + } else { + serializer.serialize_bytes(self.as_slice()) + } + } +} diff --git a/rust/tw_internet_computer/src/protocol/request_id.rs b/rust/tw_internet_computer/src/protocol/request_id.rs new file mode 100644 index 00000000000..5f9d2bcf74b --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/request_id.rs @@ -0,0 +1,127 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use tw_hash::{sha2::sha256, H256}; + +const DOMAIN_IC_REQUEST: &[u8; 11] = b"\x0Aic-request"; + +/// When signing requests or querying the status of a request +/// (see Request status) in the state tree, the user identifies +/// the request using a request id, which is the +/// representation-independent hash of the content map of the +/// original request. A request id must have length of 32 bytes. +pub struct RequestId(pub(crate) H256); + +impl RequestId { + /// Create the prehash from the request ID. + /// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#envelope-authentication + pub fn sig_data(&self) -> H256 { + let mut sig_data = vec![]; + sig_data.extend_from_slice(DOMAIN_IC_REQUEST); + sig_data.extend_from_slice(self.0.as_slice()); + H256::try_from(sha256(&sig_data).as_slice()).unwrap_or_else(|_| H256::new()) + } +} + +/// The different types of values supported in `RawHttpRequest`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum RawHttpRequestVal { + Bytes(#[serde(serialize_with = "serialize_bytes")] Vec), + String(String), + U64(u64), + Array(Vec), +} + +fn serialize_bytes(bytes: &[u8], s: S) -> Result +where + S: serde::Serializer, +{ + s.serialize_bytes(bytes) +} + +fn hash_string(value: String) -> Vec { + sha256(value.as_bytes()) +} + +fn hash_bytes(value: Vec) -> Vec { + sha256(value.as_slice()) +} + +fn hash_u64(value: u64) -> Vec { + // We need at most ⌈ 64 / 7 ⌉ = 10 bytes to encode a 64 bit + // integer in LEB128. + let mut buf = [0u8; 10]; + let mut n = value; + let mut i = 0; + + loop { + let byte = (n & 0x7f) as u8; + n >>= 7; + + if n == 0 { + buf[i] = byte; + break; + } else { + buf[i] = byte | 0x80; + i += 1; + } + } + + hash_bytes(buf[..=i].to_vec()) +} + +// arrays, encoded as the concatenation of the hashes of the encodings of the +// array elements. +fn hash_array(elements: Vec) -> Vec { + let mut buffer = vec![]; + elements + .into_iter() + // Hash the encoding of all the array elements. + .for_each(|e| { + let mut hashed_val = hash_val(e); + buffer.append(&mut hashed_val); + }); + sha256(&buffer) +} + +fn hash_val(val: RawHttpRequestVal) -> Vec { + match val { + RawHttpRequestVal::String(string) => hash_string(string), + RawHttpRequestVal::Bytes(bytes) => hash_bytes(bytes), + RawHttpRequestVal::U64(integer) => hash_u64(integer), + RawHttpRequestVal::Array(elements) => hash_array(elements), + } +} + +fn hash_key_val(key: String, val: RawHttpRequestVal) -> Vec { + let mut key_hash = hash_string(key); + let mut val_hash = hash_val(val); + key_hash.append(&mut val_hash); + key_hash +} + +/// Describes `hash_of_map` as specified in the public spec. +/// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#hash-of-map +pub fn hash_of_map(map: &BTreeMap) -> H256 { + let mut hashes: Vec> = Vec::new(); + for (key, val) in map.iter() { + hashes.push(hash_key_val(key.to_string(), val.clone())); + } + + // Computes hash by first sorting by "field name" hash, which is the + // same as sorting by concatenation of H(field name) · H(field value) + // (although in practice it's actually more stable in the presence of + // duplicated field names). Then concatenate all the hashes. + hashes.sort(); + + let buffer = hashes.into_iter().flatten().collect::>(); + let hash = sha256(&buffer); + + H256::try_from(hash.as_slice()).unwrap_or_else(|_| H256::new()) +} diff --git a/rust/tw_internet_computer/src/protocol/rosetta.rs b/rust/tw_internet_computer/src/protocol/rosetta.rs new file mode 100644 index 00000000000..45e2a0be888 --- /dev/null +++ b/rust/tw_internet_computer/src/protocol/rosetta.rs @@ -0,0 +1,54 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use super::envelope::{Envelope, EnvelopeCallContent, EnvelopeReadStateContent}; +use serde::Serialize; + +/// The types of requests that are available from the Rosetta node. +/// This enum is truncated to include support only for the +/// operations that this crate can currently perform. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum RequestType { + // Aliases for backwards compatibility + #[serde(rename = "TRANSACTION")] + #[serde(alias = "Send")] + Send, +} + +/// The type (encoded as CBOR) returned by the Rosetta node's +/// /construction/combine endpoint. It contains the +/// IC calls to submit the transaction and to check the result. +pub type SignedTransaction = Vec; + +/// A vector of update/read-state calls for different ingress windows +/// of the same call. +pub type Request = (RequestType, Vec); + +#[derive(Debug, Clone)] +pub enum EnvelopePairError { + InvalidUpdateEnvelope, + InvalidReadStateEnvelope, +} + +/// A signed IC update call and the corresponding read-state call for +/// a particular ingress window. +#[derive(Debug, Clone, Serialize)] +pub struct EnvelopePair { + update: Envelope, + read_state: Envelope, +} + +impl EnvelopePair { + pub fn new( + update_envelope: Envelope, + read_state_envelope: Envelope, + ) -> Result { + Ok(Self { + update: update_envelope, + read_state: read_state_envelope, + }) + } +} diff --git a/rust/tw_internet_computer/src/signer.rs b/rust/tw_internet_computer/src/signer.rs new file mode 100644 index 00000000000..113dde0c27f --- /dev/null +++ b/rust/tw_internet_computer/src/signer.rs @@ -0,0 +1,78 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::marker::PhantomData; + +use tw_coin_entry::{ + error::{SigningError, SigningResult}, + signing_output_error, +}; +use tw_keypair::ecdsa::secp256k1; +use tw_proto::{Common::Proto::SigningError as CommonError, InternetComputer::Proto}; + +use crate::{ + context::InternetComputerContext, + protocol::identity, + transactions::{self, sign_transaction}, +}; + +impl From for SigningError { + fn from(error: transactions::SignTransactionError) -> Self { + match error { + transactions::SignTransactionError::InvalidArguments => { + SigningError(CommonError::Error_invalid_params) + }, + transactions::SignTransactionError::Identity(identity_error) => match identity_error { + identity::SigningError::Failed(_) => SigningError(CommonError::Error_signing), + }, + transactions::SignTransactionError::InvalidEnvelopePair + | transactions::SignTransactionError::EncodingArgsFailed => { + SigningError(CommonError::Error_internal) + }, + transactions::SignTransactionError::InvalidToAccountIdentifier => { + SigningError(CommonError::Error_invalid_address) + }, + transactions::SignTransactionError::InvalidAmount => { + SigningError(CommonError::Error_invalid_requested_token_amount) + }, + } + } +} + +pub struct Signer { + _phantom: PhantomData, +} + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let private_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref())?; + + let Some(ref transaction) = input.transaction else { + return Err(SigningError(CommonError::Error_invalid_params)); + }; + + let canister_id = Context::get_canister_id(); + let signed_transaction = + sign_transaction(private_key, canister_id, &transaction.transaction_oneof) + .map_err(SigningError::from)?; + + let cbor_encoded_signed_transaction = tw_encoding::cbor::encode(&signed_transaction) + .map_err(|_| SigningError(CommonError::Error_internal))?; + + Ok(Proto::SigningOutput { + signed_transaction: cbor_encoded_signed_transaction.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_internet_computer/src/transactions/mod.rs b/rust/tw_internet_computer/src/transactions/mod.rs new file mode 100644 index 00000000000..e401c7e08f5 --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/mod.rs @@ -0,0 +1,47 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod transfer; + +pub mod proto { + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); +} + +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_proto::InternetComputer::Proto::mod_Transaction::OneOftransaction_oneof as Tx; + +use crate::protocol::{identity, principal::Principal, rosetta}; + +#[derive(Debug)] +pub enum SignTransactionError { + InvalidAmount, + InvalidArguments, + Identity(identity::SigningError), + EncodingArgsFailed, + InvalidToAccountIdentifier, + InvalidEnvelopePair, +} + +pub fn sign_transaction( + private_key: PrivateKey, + canister_id: Principal, + transaction: &Tx, +) -> Result { + match transaction { + Tx::transfer(transfer_args) => transfer::transfer( + private_key, + canister_id, + transfer::TransferArgs { + memo: transfer_args.memo, + amount: transfer_args.amount, + max_fee: None, + to: transfer_args.to_account_identifier.to_string(), + current_timestamp_nanos: transfer_args.current_timestamp_nanos, + }, + ), + Tx::None => Err(SignTransactionError::InvalidArguments), + } +} diff --git a/rust/tw_internet_computer/src/transactions/proto/ledger.proto b/rust/tw_internet_computer/src/transactions/proto/ledger.proto new file mode 100644 index 00000000000..726d2ffd74b --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/proto/ledger.proto @@ -0,0 +1,315 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +// -*- c-basic-offset: 2 -*- +// Source: https://github.com/dfinity/ic/blob/master/rs/rosetta-api/icp_ledger/proto/ic_ledger/pb/v1/types.proto +// Commit Hash: 703eb96fea44ad2c82740e4360a526a2a127a960 +// Striped annotations related to the use of hardware wallets. + +syntax = "proto3"; + +package ic_ledger.pb.v1; + +import "./types.proto"; + +// Annotations related to the use of hardware wallets. The annotated messages are +// parsed on hardware wallets and marked fields are displayed in a trusted user +// interface (TUI). We must not, for instance, add fields that would change the +// semantics of the message such that old hardware wallets would not display +// appropriate information to users. + +// ** LEDGER CANISTER ENDPOINTS + +// Initialise the ledger canister +message LedgerInit { + AccountIdentifier minting_account = 1; + repeated Account initial_values = 2; + ic_base_types.pb.v1.PrincipalId archive_canister = 3; + uint32 max_message_size_bytes = 4; +} + +// The format of values serialized to/from the stable memory during and upgrade +message LedgerUpgrade {} + +// Make a payment +message SendRequest { + Memo memo = 1; + Payment payment = 2; + Tokens max_fee = 3; + Subaccount from_subaccount = 4; + AccountIdentifier to = 5; + BlockIndex created_at = 6; + TimeStamp created_at_time = 7; +} + +message SendResponse { + BlockIndex resulting_height = 1; +} + +// Notify a canister that it has received a payment +message NotifyRequest { + BlockIndex block_height = 1; + Tokens max_fee = 2; + Subaccount from_subaccount = 3; + ic_base_types.pb.v1.PrincipalId to_canister = 4; + Subaccount to_subaccount = 5; +} + +message NotifyResponse {} + +message TransactionNotificationRequest { + ic_base_types.pb.v1.PrincipalId from = 1; + Subaccount from_subaccount = 2; + ic_base_types.pb.v1.PrincipalId to = 3; + Subaccount to_subaccount = 4; + BlockIndex block_height = 5; + Tokens amount = 6; + Memo memo = 7; +} + +message TransactionNotificationResponse { + bytes response = 1; +} + +message CyclesNotificationResponse { + oneof response { + ic_base_types.pb.v1.PrincipalId created_canister_id = 1; + Refund refund = 2; + ToppedUp topped_up = 3; + } +} + +// Get the balance of an account +message AccountBalanceRequest { + AccountIdentifier account = 1; +} + +message AccountBalanceResponse { + Tokens balance = 1; +} + +// Get the length of the chain with a certification +message TipOfChainRequest {} + +message TipOfChainResponse { + Certification certification = 1; + BlockIndex chain_length = 2; +} + +// How many Tokens are there not in the minting account +message TotalSupplyRequest {} + +message TotalSupplyResponse { + Tokens total_supply = 1; +} + +// Archive any blocks older than this +message LedgerArchiveRequest { + TimeStamp timestamp = 1; +} + +// * Shared Endpoints * + +// Get a single block +message BlockRequest { + uint64 block_height = 1; +} + +message EncodedBlock { + bytes block = 1; +} + +message BlockResponse { + oneof block_content { + EncodedBlock block = 1; + ic_base_types.pb.v1.PrincipalId canister_id = 2; + } +} + +// Get a set of blocks +message GetBlocksRequest { + uint64 start = 1; + uint64 length = 2; +} + +message Refund { + BlockIndex refund = 2; + string error = 3; +} + +message ToppedUp {} + +message EncodedBlocks { + repeated EncodedBlock blocks = 1; +} + +message GetBlocksResponse { + oneof get_blocks_content { + EncodedBlocks blocks = 1; + string error = 2; + } +} + +// Iterate through blocks +message IterBlocksRequest { + uint64 start = 1; + uint64 length = 2; +} + +message IterBlocksResponse { + repeated EncodedBlock blocks = 1; +} + +message ArchiveIndexEntry { + uint64 height_from = 1; + uint64 height_to = 2; + ic_base_types.pb.v1.PrincipalId canister_id = 3; +} + +message ArchiveIndexResponse { + repeated ArchiveIndexEntry entries = 1; +} + +// ** ARCHIVE CANISTER ENDPOINTS ** + +// * Archive canister * +// Init the archive canister +message ArchiveInit { + uint32 node_max_memory_size_bytes = 1; + uint32 max_message_size_bytes = 2; +} + +// Add blocks to the archive canister +message ArchiveAddRequest { + repeated Block blocks = 1; +} + +message ArchiveAddResponse {} + +// Fetch a list of all of the archive nodes +message GetNodesRequest {} + +message GetNodesResponse { + repeated ic_base_types.pb.v1.PrincipalId nodes = 1; +} + +// ** BASIC TYPES ** +message Tokens { + uint64 e8s = 1; +} + +message Payment { + Tokens receiver_gets = 1; +} + +message BlockIndex { + uint64 height = 1; +} + +// This is the +message Block { + Hash parent_hash = 1; + TimeStamp timestamp = 2; + Transaction transaction = 3; +} + +message Hash { + bytes hash = 1; +} + +message Account { + AccountIdentifier identifier = 1; + Tokens balance = 2; +} + +message Transaction { + oneof transfer { + Burn burn = 1; + Mint mint = 2; + Send send = 3; + } + Memo memo = 4; + Icrc1Memo icrc1_memo = 7; + BlockIndex created_at = 5; // obsolete + TimeStamp created_at_time = 6; +} + +message Send { + // The meaning of the [from] field depends on the transaction type: + // - Transfer: [from] is the source account. + // - TransferFrom: [from] is the approver. + // - Approve: [from] is the approver. + AccountIdentifier from = 1; + // The meaning of the [to] field depends on the transaction type: + // - Transfer: [to] is the destination account. + // - TransferFrom: [to] is the destination account. + // - Approve: [to] is the default account id of the approved principal. + AccountIdentifier to = 2; + // If the transaction type is Approve, the amount must be zero. + Tokens amount = 3; + Tokens max_fee = 4; + + // We represent metadata of new operation types as submessages for + // backward compatibility with old clients. + oneof extension { + Approve approve = 5; + TransferFrom transfer_from = 6; + } +} + +message TransferFrom { + // The default account id of the principal who sent the transaction. + AccountIdentifier spender = 1; +} + +message Approve { + Tokens allowance = 1; + TimeStamp expires_at = 2; + Tokens expected_allowance = 3; +} + +message Mint { + AccountIdentifier to = 2; + Tokens amount = 3; +} + +message Burn { + AccountIdentifier from = 1; + Tokens amount = 3; +} + +message AccountIdentifier { + // Can contain either: + // * the 32 byte identifier (4 byte checksum + 28 byte hash) + // * the 28 byte hash + bytes hash = 1; +} + +message Subaccount { + bytes sub_account = 1; +} + +message Memo { + uint64 memo = 1; +} + +message Icrc1Memo { + bytes memo = 1; +} + +message TimeStamp { + uint64 timestamp_nanos = 1; +} + +message Certification { + bytes certification = 1; +} + +message TransferFeeRequest {} + +message TransferFeeResponse { + Tokens transfer_fee = 1; +} \ No newline at end of file diff --git a/rust/tw_internet_computer/src/transactions/proto/types.proto b/rust/tw_internet_computer/src/transactions/proto/types.proto new file mode 100644 index 00000000000..2c63ceb555e --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/proto/types.proto @@ -0,0 +1,19 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +// Source: https://github.com/dfinity/ic/blob/master/rs/rosetta-api/icp_ledger/proto/ic_ledger/pb/v1/types.proto +// Commit Hash: 703eb96fea44ad2c82740e4360a526a2a127a960 +// Striped annotations related to the use of hardware wallets. + +syntax = "proto3"; + +package ic_base_types.pb.v1; + +// A PB container for a PrincipalId, which uniquely identifies +// a principal. +message PrincipalId { + bytes serialized_id = 1; +} \ No newline at end of file diff --git a/rust/tw_internet_computer/src/transactions/transfer.rs b/rust/tw_internet_computer/src/transactions/transfer.rs new file mode 100644 index 00000000000..0b57b56d058 --- /dev/null +++ b/rust/tw_internet_computer/src/transactions/transfer.rs @@ -0,0 +1,247 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::time::Duration; + +use tw_keypair::ecdsa::secp256k1::PrivateKey; + +use crate::{ + address::AccountIdentifier, + protocol::{ + envelope::{ + Envelope, EnvelopeCallContent, EnvelopeReadStateContent, Label, RepresentationHashable, + }, + get_ingress_expiry, + identity::Identity, + principal::Principal, + request_id::RequestId, + rosetta, + }, + transactions::proto::ic_ledger::pb::v1::{ + AccountIdentifier as ProtoAccountIdentifier, Memo, Payment, SendRequest, TimeStamp, Tokens, + }, +}; + +use super::SignTransactionError; + +/// Arguments to be used with [transfer] to create a signed transaction enveloper pair. +#[derive(Clone, Debug)] +pub struct TransferArgs { + /// The memo field is used as a method to help identify the transaction. + pub memo: u64, + /// The amount of ICP to send as e8s. + pub amount: u64, + /// The maximum fee will to be paid to complete the transfer. + /// If not provided, the minimum fee will be applied to the transaction. Currently 10_000 e8s (0.00010000 ICP). + pub max_fee: Option, + /// The address to send the amount to. + pub to: String, + /// The current timestamp in nanoseconds. + pub current_timestamp_nanos: u64, +} + +impl TryFrom for SendRequest { + type Error = SignTransactionError; + + fn try_from(args: TransferArgs) -> Result { + let current_timestamp_duration = Duration::from_nanos(args.current_timestamp_nanos); + let timestamp_nanos = current_timestamp_duration.as_nanos() as u64; + + let to_account_identifier = AccountIdentifier::from_hex(&args.to) + .map_err(|_| SignTransactionError::InvalidToAccountIdentifier)?; + let to_hash = to_account_identifier.as_ref().to_vec(); + + let request = Self { + memo: Some(Memo { memo: args.memo }), + payment: Some(Payment { + receiver_gets: Some(Tokens { e8s: args.amount }), + }), + max_fee: args.max_fee.map(|fee| Tokens { e8s: fee }), + from_subaccount: None, + to: Some(ProtoAccountIdentifier { hash: to_hash }), + created_at: None, + created_at_time: Some(TimeStamp { timestamp_nanos }), + }; + Ok(request) + } +} + +/// The endpoint on the ledger canister that is used to make transfers. +const METHOD_NAME: &str = "send_pb"; + +/// Given a secp256k1 private key, the canister ID of an ICP-based ledger canister, and the actual transfer args, +/// this function creates a signed transaction to be sent to a Rosetta API node. +pub fn transfer( + private_key: PrivateKey, + canister_id: Principal, + args: TransferArgs, +) -> Result { + if args.amount < 1 { + return Err(SignTransactionError::InvalidAmount); + } + + let current_timestamp_duration = Duration::from_nanos(args.current_timestamp_nanos); + let ingress_expiry = get_ingress_expiry(current_timestamp_duration); + let identity = Identity::new(private_key); + + // Encode the arguments for the ledger `send_pb` endpoint. + let send_request = SendRequest::try_from(args)?; + let arg = + tw_proto::serialize(&send_request).map_err(|_| SignTransactionError::EncodingArgsFailed)?; + // Create the update envelope. + let (request_id, update_envelope) = + create_update_envelope(&identity, canister_id, arg, ingress_expiry)?; + + // Create the read state envelope. + let (_, read_state_envelope) = + create_read_state_envelope(&identity, request_id, ingress_expiry)?; + + // Create a new EnvelopePair with the update call and read_state envelopes. + let envelope_pair = rosetta::EnvelopePair::new(update_envelope, read_state_envelope) + .map_err(|_| SignTransactionError::InvalidEnvelopePair)?; + + // Create a signed transaction containing the envelope pair. + let request: rosetta::Request = (rosetta::RequestType::Send, vec![envelope_pair]); + Ok(vec![request]) +} + +#[inline] +fn create_update_envelope( + identity: &Identity, + canister_id: Principal, + arg: Vec, + ingress_expiry: u64, +) -> Result<(RequestId, Envelope), SignTransactionError> { + let sender = identity.sender(); + let content = EnvelopeCallContent { + nonce: None, + ingress_expiry, + sender, + canister_id, + method_name: METHOD_NAME.to_string(), + arg, + }; + + let request_id = content.request_id(); + let signature = identity + .sign(request_id.sig_data()) + .map_err(SignTransactionError::Identity)?; + + let env = Envelope { + content, + sender_pubkey: Some(signature.public_key), + sender_sig: Some(signature.signature), + }; + Ok((request_id, env)) +} + +#[inline] +fn create_read_state_envelope( + identity: &Identity, + update_request_id: RequestId, + ingress_expiry: u64, +) -> Result<(RequestId, Envelope), SignTransactionError> { + let sender = identity.sender(); + + let content = EnvelopeReadStateContent { + ingress_expiry, + sender, + paths: vec![vec![ + Label::from("request_status"), + Label::from(update_request_id), + ]], + }; + + let request_id = content.request_id(); + let signature = identity + .sign(request_id.sig_data()) + .map_err(SignTransactionError::Identity)?; + + let env = Envelope { + content, + sender_pubkey: Some(signature.public_key), + sender_sig: Some(signature.signature), + }; + Ok((request_id, env)) +} + +#[cfg(test)] +mod test { + use tw_encoding::hex; + + use crate::address::AccountIdentifier; + + use super::*; + + pub const SIGNED_TRANSACTION: &str = "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"; + + fn make_transfer_args() -> TransferArgs { + let current_timestamp_nanos = Duration::from_secs(1_691_709_940).as_nanos() as u64; + let owner = + Principal::from_text("t4u4z-y3dur-j63pk-nw4rv-yxdbt-agtt6-nygn7-ywh6y-zm2f4-sdzle-3qe") + .unwrap(); + let to_account_identifier = AccountIdentifier::new(&owner); + + TransferArgs { + memo: 0, + amount: 100_000_000, + max_fee: None, + to: to_account_identifier.to_hex(), + current_timestamp_nanos, + } + } + + #[test] + fn transfer_successful() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let transfer_args = make_transfer_args(); + + let signed_transaction = transfer(private_key, canister_id, transfer_args).unwrap(); + // Encode the signed transaction. + let cbor_encoded_signed_transaction = + tw_encoding::cbor::encode(&signed_transaction).unwrap(); + let hex_encoded_signed_transaction = hex::encode(&cbor_encoded_signed_transaction, false); + assert_eq!(hex_encoded_signed_transaction, SIGNED_TRANSACTION); + } + + #[test] + fn transfer_invalid_amount() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let mut transfer_args = make_transfer_args(); + transfer_args.amount = 0; + + let signed_transaction = transfer(private_key, canister_id, transfer_args); + assert!(matches!( + signed_transaction, + Err(SignTransactionError::InvalidAmount) + )); + } + + #[test] + fn transfer_invalid_to_account_identifier() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let mut transfer_args = make_transfer_args(); + transfer_args.to = "invalid".to_string(); + + let signed_transaction = transfer(private_key, canister_id, transfer_args); + assert!(matches!( + signed_transaction, + Err(SignTransactionError::InvalidToAccountIdentifier) + )); + } +} diff --git a/rust/tw_keypair/Cargo.toml b/rust/tw_keypair/Cargo.toml index 0b7a81b8e7d..52b0a350fa8 100644 --- a/rust/tw_keypair/Cargo.toml +++ b/rust/tw_keypair/Cargo.toml @@ -3,7 +3,35 @@ name = "tw_keypair" version = "0.1.0" edition = "2021" +[features] +test-utils = [] + [dependencies] +arbitrary = { version = "1", features = ["derive"], optional = true } +lazy_static = "1.4.0" +serde = { version = "1.0.159", features = ["derive"] } +starknet-crypto = "0.5.0" +starknet-ff = "0.3.2" tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } +zeroize = "1.6.0" +# ECDSA specific: +ecdsa = "0.16.6" der = "0.7.3" +k256 = { version = "0.13.0", features = ["ecdh", "ecdsa", "pkcs8", "schnorr", "std"], default-features = false } +p256 = { version = "0.13.0", features = ["ecdsa", "std"], default-features = false } +pkcs8 = "0.10.2" +rfc6979 = "0.4.0" +# ED25519 specific: +blake2 = "0.9" +curve25519-dalek = "3" +digest = "0.9.0" +sha2 = "0.9" + +[dev-dependencies] +serde_json = "1.0.95" + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +ring = "0.16.20" diff --git a/rust/tw_keypair/fuzz/.gitignore b/rust/tw_keypair/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_keypair/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_keypair/fuzz/Cargo.toml b/rust/tw_keypair/fuzz/Cargo.toml new file mode 100644 index 00000000000..17da9d20ca1 --- /dev/null +++ b/rust/tw_keypair/fuzz/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "tw_keypair-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +[dependencies.tw_keypair] +path = ".." +features = ["arbitrary"] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +# https://github.com/rust-lang/rust/issues/95240 +[profile.release.package.curve25519-dalek] +opt-level = 2 + +[[bin]] +name = "tw_private_sign" +path = "fuzz_targets/tw_private_sign.rs" +test = false +doc = false + +[[bin]] +name = "tw_public_verify" +path = "fuzz_targets/tw_public_verify.rs" +test = false +doc = false + +[[bin]] +name = "tw_private_to_public" +path = "fuzz_targets/tw_private_to_public.rs" +test = false +doc = false diff --git a/rust/tw_keypair/fuzz/fuzz_targets/tw_private_sign.rs b/rust/tw_keypair/fuzz/fuzz_targets/tw_private_sign.rs new file mode 100644 index 00000000000..b7a574f3073 --- /dev/null +++ b/rust/tw_keypair/fuzz/fuzz_targets/tw_private_sign.rs @@ -0,0 +1,23 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_keypair::tw::{Curve, PrivateKey}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct TWPrivateSignInput { + private: Vec, + hash: Vec, + curve: Curve, +} + +fuzz_target!(|input: TWPrivateSignInput| { + if let Ok(private) = PrivateKey::new(input.private) { + private.sign(&input.hash, input.curve).ok(); + } +}); diff --git a/rust/tw_keypair/fuzz/fuzz_targets/tw_private_to_public.rs b/rust/tw_keypair/fuzz/fuzz_targets/tw_private_to_public.rs new file mode 100644 index 00000000000..25de9500029 --- /dev/null +++ b/rust/tw_keypair/fuzz/fuzz_targets/tw_private_to_public.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_keypair::tw::{PrivateKey, PublicKeyType}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct TWPrivateToPublicInput { + private: Vec, + public_key_type: PublicKeyType, +} + +fuzz_target!(|input: TWPrivateToPublicInput| { + if let Ok(private) = PrivateKey::new(input.private) { + private.get_public_key_by_type(input.public_key_type).ok(); + } +}); diff --git a/rust/tw_keypair/fuzz/fuzz_targets/tw_public_verify.rs b/rust/tw_keypair/fuzz/fuzz_targets/tw_public_verify.rs new file mode 100644 index 00000000000..394d2aad416 --- /dev/null +++ b/rust/tw_keypair/fuzz/fuzz_targets/tw_public_verify.rs @@ -0,0 +1,24 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_keypair::tw::{PublicKey, PublicKeyType}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct TWPublicVerifyInput { + public: Vec, + hash: Vec, + signature: Vec, + pubkey_type: PublicKeyType, +} + +fuzz_target!(|input: TWPublicVerifyInput| { + if let Ok(public) = PublicKey::new(input.public, input.pubkey_type) { + public.verify(&input.signature, &input.hash); + } +}); diff --git a/rust/tw_keypair/src/ecdsa/canonical.rs b/rust/tw_keypair/src/ecdsa/canonical.rs new file mode 100644 index 00000000000..fdfc9244479 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/canonical.rs @@ -0,0 +1,143 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 module is a Proof Of Concept that proves the possibility to implement the following in Rust: +//! https://github.com/trustwallet/wallet-core/blob/d9e35ec485b1366dd10509192d02d9dbb6877ab3/src/PrivateKey.cpp#L253-L282 +//! +//! # Warning +//! +//! **Not production ready** + +#![allow(dead_code)] + +use crate::ecdsa::signature::Signature; +use crate::ecdsa::EcdsaCurve; +use crate::{KeyPairError, KeyPairResult}; +use ecdsa::elliptic_curve::generic_array::ArrayLength; +use ecdsa::elliptic_curve::subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use ecdsa::elliptic_curve::{Curve, FieldBytesEncoding, PrimeField, Scalar}; +use ecdsa::hazmat::{bits2field, DigestPrimitive, SignPrimitive}; +use ecdsa::SigningKey; +use k256::sha2::digest::crypto_common::BlockSizeUser; +use k256::sha2::digest::{FixedOutput, FixedOutputReset}; +use k256::sha2::Digest; +use rfc6979::{ByteArray, HmacDrbg}; +use tw_hash::H256; + +type CurveDigest = ::Digest; + +/// Implements https://github.com/trustwallet/wallet-core/blob/d9e35ec485b1366dd10509192d02d9dbb6877ab3/src/PrivateKey.cpp#L253-L282 +pub(crate) fn sign_with_canonical( + signing_key: &SigningKey, + hash_to_sign: H256, + mut is_canonical: F, +) -> KeyPairResult> +where + for<'a> F: FnMut(&'a Signature) -> bool, + Scalar: SignPrimitive, + ::Uint: FieldBytesEncoding, +{ + let priv_scalar = signing_key.as_nonzero_scalar(); + + let nonce = + bits2field::(hash_to_sign.as_slice()).map_err(|_| KeyPairError::InvalidSignMessage)?; + let entropy_input = &priv_scalar.to_repr(); + let n = &C::ORDER.encode_field_bytes(); + let additional_data = &[]; + + let mut hmac_drbg = HmacDrbg::>::new(entropy_input, &nonce, additional_data); + + for _ in 0..10000 { + // The `k` number is different on each iteration due to the `hmac_drbg` mutation. + let k = generate_k::, _>(&mut hmac_drbg, n); + let k_scalar = Scalar::::from_repr(k).unwrap(); + + let (sig, r) = priv_scalar + .try_sign_prehashed(k_scalar, &nonce) + .map_err(|_| KeyPairError::SigningError)?; + let r = r.ok_or(KeyPairError::SigningError)?; + + let signature = Signature::::new(sig, r); + if is_canonical(&signature) { + return Ok(signature); + } + } + + Err(KeyPairError::SigningError) +} + +fn ct_eq>(a: &ByteArray, b: &ByteArray) -> Choice { + let mut ret = Choice::from(1); + + for (a, b) in a.iter().zip(b.iter()) { + ret.conditional_assign(&Choice::from(0), !a.ct_eq(b)); + } + + ret +} + +pub(crate) fn ct_lt>(a: &ByteArray, b: &ByteArray) -> Choice { + let mut borrow = 0; + + // Perform subtraction with borrow a byte-at-a-time, interpreting a + // no-borrow condition as the less-than case + for (&a, &b) in a.iter().zip(b.iter()).rev() { + let c = (b as u16).wrapping_add(borrow >> (u8::BITS - 1)); + borrow = (a as u16).wrapping_sub(c) >> u8::BITS as u8; + } + + !borrow.ct_eq(&0) +} + +pub(crate) fn generate_k(hmac_drbg: &mut HmacDrbg, n: &ByteArray) -> ByteArray +where + D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset, + N: ArrayLength, +{ + loop { + let mut k = ByteArray::::default(); + hmac_drbg.fill_bytes(&mut k); + + let k_is_zero = ct_eq(&k, &ByteArray::default()); + if (!k_is_zero & ct_lt(&k, n)).into() { + return k; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ecdsa::secp256k1::{PrivateKey, Signature}; + use tw_hash::H520; + use tw_misc::traits::ToBytesVec; + + fn is_unsigned(byte: u8) -> bool { + byte & 0x80 == 0 + } + + fn fio_is_canonical(sig: &Signature) -> bool { + let sig = sig.to_vec(); + is_unsigned(sig[0]) + && !(sig[0] == 0 && is_unsigned(sig[1])) + && is_unsigned(sig[32]) + && !(sig[32] == 0 && is_unsigned(sig[33])) + } + + #[test] + fn test_sign_canonical() { + let private = PrivateKey::try_from( + "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035", + ) + .unwrap(); + let hash_to_sign = + H256::from("71b7098e8150cde90f3ec00280815d3069f81c7cdb6d83bbe2b897b1afbe7cd6"); + + let actual = sign_with_canonical(&private.secret, hash_to_sign, fio_is_canonical).unwrap(); + let expected = H520::from("42ceeaa4a3d0ea0429ab09e4d969abd812c65ad4efef9e95e3a19cc3c41be3770ad0222dac6aa1b350cf9273fa922801d11b6142cb0fe639e2fe3fd988e5aec400"); + assert_eq!(actual.to_bytes(), expected); + } +} diff --git a/rust/tw_keypair/src/ecdsa/mod.rs b/rust/tw_keypair/src/ecdsa/mod.rs index 965960c855c..61e49f722c3 100644 --- a/rust/tw_keypair/src/ecdsa/mod.rs +++ b/rust/tw_keypair/src/ecdsa/mod.rs @@ -4,4 +4,25 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use ecdsa::elliptic_curve::bigint::U256; +use ecdsa::elliptic_curve::consts::U32; +use ecdsa::elliptic_curve::CurveArithmetic; +use ecdsa::hazmat::DigestPrimitive; +use ecdsa::PrimeCurve; + +mod canonical; pub mod der; +pub mod nist256p1; +pub mod secp256k1; +pub mod signature; + +/// This is an alias used for convenience. +pub trait EcdsaCurve: + PrimeCurve + CurveArithmetic + DigestPrimitive +{ +} + +impl EcdsaCurve for T where + T: PrimeCurve + CurveArithmetic + DigestPrimitive +{ +} diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/keypair.rs b/rust/tw_keypair/src/ecdsa/nist256p1/keypair.rs new file mode 100644 index 00000000000..df9ff15a8e0 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/nist256p1/keypair.rs @@ -0,0 +1,73 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::nist256p1::private::PrivateKey; +use crate::ecdsa::nist256p1::public::PublicKey; +use crate::ecdsa::nist256p1::{Signature, VerifySignature}; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use tw_hash::H256; +use zeroize::ZeroizeOnDrop; +use zeroize::Zeroizing; + +/// Represents a pair of `nist256p1` private and public keys. +#[derive(ZeroizeOnDrop)] +pub struct KeyPair { + private: PrivateKey, + #[zeroize(skip)] + public: PublicKey, +} + +impl KeyPairTrait for KeyPair { + type Private = PrivateKey; + type Public = PublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for KeyPair { + type SigningMessage = H256; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.private.sign(message) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningMessage = H256; + type VerifySignature = VerifySignature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public.verify(signature, message) + } +} + +impl<'a> TryFrom<&'a [u8]> for KeyPair { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = PrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(KeyPair { private, public }) + } +} + +impl<'a> TryFrom<&'a str> for KeyPair { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/mod.rs b/rust/tw_keypair/src/ecdsa/nist256p1/mod.rs new file mode 100644 index 00000000000..890d77d610a --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/nist256p1/mod.rs @@ -0,0 +1,109 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use p256::NistP256; + +mod keypair; +mod private; +mod public; + +pub use keypair::KeyPair; +pub use private::PrivateKey; +pub use public::PublicKey; + +pub type Signature = crate::ecdsa::signature::Signature; +pub type VerifySignature = crate::ecdsa::signature::VerifySignature; + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; + use tw_encoding::hex; + use tw_hash::sha3::keccak256; + use tw_hash::{H256, H264, H520}; + use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + + #[test] + fn test_key_pair() { + let secret = + hex::decode("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5") + .unwrap(); + let key_pair = KeyPair::try_from(secret.as_slice()).unwrap(); + assert_eq!(key_pair.private().to_zeroizing_vec().as_slice(), secret); + assert_eq!( + key_pair.public().uncompressed(), + H520::from("046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae") + ); + assert_eq!( + key_pair.public().compressed(), + H264::from("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"), + ); + } + + #[test] + fn test_private_key_from() { + let hex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let expected = hex::decode(hex).unwrap(); + + // Test `From<&'static str>`. + let private = PrivateKey::try_from(hex).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), expected); + + // Test `From<&'a [u8]>`. + let private = PrivateKey::try_from(expected.as_slice()).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), expected); + } + + #[test] + fn test_private_key_sign_verify() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let keypair = KeyPair::try_from(secret).unwrap(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + let signature = keypair.sign(hash_to_sign).unwrap(); + + let expected = H520::from("8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401"); + assert_eq!(signature.to_bytes(), expected); + + let verify_signature = VerifySignature::from(signature); + assert!(keypair.verify(verify_signature, hash_to_sign)); + } + + #[test] + fn test_public_key_from() { + let compressed = "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"; + let uncompressed = "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae"; + let expected_compressed = H264::from(compressed); + let expected_uncompressed = H520::from(uncompressed); + + // From extended public key. + let public = PublicKey::try_from(uncompressed).unwrap(); + assert_eq!(public.to_vec(), expected_compressed.into_vec()); + assert_eq!(public.compressed(), expected_compressed); + assert_eq!(public.uncompressed(), expected_uncompressed); + + // From compressed public key. + let public = PublicKey::try_from(compressed).unwrap(); + assert_eq!(public.to_vec(), expected_compressed.into_vec()); + assert_eq!(public.compressed(), expected_compressed); + assert_eq!(public.uncompressed(), expected_uncompressed); + } + + #[test] + fn test_public_key_recover() { + let sign_bytes = H520::from("8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401"); + let sign = Signature::from_bytes(sign_bytes.as_slice()).unwrap(); + + let signed_hash = keccak256(b"hello"); + let signed_hash = H256::try_from(signed_hash.as_slice()).unwrap(); + + let actual = PublicKey::recover(sign, signed_hash).unwrap(); + let expected_compressed = + H264::from("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"); + assert_eq!(actual.compressed(), expected_compressed); + } +} diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/private.rs b/rust/tw_keypair/src/ecdsa/nist256p1/private.rs new file mode 100644 index 00000000000..3149967ce68 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/nist256p1/private.rs @@ -0,0 +1,66 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::nist256p1::public::PublicKey; +use crate::ecdsa::nist256p1::Signature; +use crate::traits::SigningKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use p256::ecdsa::SigningKey; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::{ZeroizeOnDrop, Zeroizing}; + +/// Represents a `nist256p1` private key. +#[derive(ZeroizeOnDrop)] +pub struct PrivateKey { + pub(crate) secret: SigningKey, +} + +impl PrivateKey { + /// Returns an associated `nist256p1` public key. + pub fn public(&self) -> PublicKey { + PublicKey::new(*self.secret.verifying_key()) + } +} + +impl SigningKeyTrait for PrivateKey { + type SigningMessage = H256; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + let (signature, recovery_id) = self + .secret + .sign_prehash_recoverable(message.as_slice()) + .map_err(|_| KeyPairError::SigningError)?; + Ok(Signature::new(signature, recovery_id)) + } +} + +impl<'a> TryFrom<&'a [u8]> for PrivateKey { + type Error = KeyPairError; + + fn try_from(data: &'a [u8]) -> Result { + let secret = SigningKey::from_slice(data).map_err(|_| KeyPairError::InvalidSecretKey)?; + Ok(PrivateKey { secret }) + } +} + +impl<'a> TryFrom<&'a str> for PrivateKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let secret = Zeroizing::new(self.secret.to_bytes()); + Zeroizing::new(secret.as_slice().to_vec()) + } +} diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/public.rs b/rust/tw_keypair/src/ecdsa/nist256p1/public.rs new file mode 100644 index 00000000000..5255e920621 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/nist256p1/public.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::nist256p1::{Signature, VerifySignature}; +use crate::traits::VerifyingKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use p256::ecdsa::signature::hazmat::PrehashVerifier; +use p256::ecdsa::VerifyingKey; +use tw_encoding::hex; +use tw_hash::{H256, H264, H520}; +use tw_misc::traits::ToBytesVec; + +/// Represents a `nist256p1` public key. +#[derive(Clone)] +pub struct PublicKey { + pub(crate) public: VerifyingKey, +} + +/// cbindgen:ignore +impl PublicKey { + /// The number of bytes in a compressed public key. + pub const COMPRESSED: usize = H264::len(); + /// The number of bytes in an uncompressed public key. + pub const UNCOMPRESSED: usize = H520::len(); + + /// Recover a [`PublicKey`] from the given `message` and the signature over that. + pub fn recover(sign: Signature, message: H256) -> KeyPairResult { + VerifyingKey::recover_from_prehash(message.as_slice(), &sign.signature, sign.v) + .map(|public| PublicKey { public }) + .map_err(|_| KeyPairError::InvalidSignature) + } + + /// Creates a public key from the given [`VerifyingKey`]. + pub(crate) fn new(public: VerifyingKey) -> PublicKey { + PublicKey { public } + } + + /// Returns the raw data of the compressed public key (33 bytes). + pub fn compressed(&self) -> H264 { + let compressed = true; + H264::try_from(self.public.to_encoded_point(compressed).as_bytes()) + .expect("Expected 33 byte array Public Key") + } + + /// Returns the raw data of the uncompressed public key (65 bytes). + pub fn uncompressed(&self) -> H520 { + let compressed = false; + H520::try_from(self.public.to_encoded_point(compressed).as_bytes()) + .expect("Expected 65 byte array Public Key") + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningMessage = H256; + type VerifySignature = VerifySignature; + + fn verify(&self, sign: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public + .verify_prehash(message.as_slice(), &sign.signature) + .is_ok() + } +} + +impl<'a> TryFrom<&'a str> for PublicKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?; + Self::try_from(bytes.as_slice()) + } +} + +impl<'a> TryFrom<&'a [u8]> for PublicKey { + type Error = KeyPairError; + + /// Expected either `H264` or `H520` slice. + fn try_from(data: &'a [u8]) -> Result { + Ok(PublicKey { + public: VerifyingKey::from_sec1_bytes(data) + .map_err(|_| KeyPairError::InvalidPublicKey)?, + }) + } +} + +/// Return the compressed bytes representation by default. +/// Consider using [`PublicKey::compressed`] or [`PublicKey::uncompressed`] instead. +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.compressed().to_vec() + } +} diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/keypair.rs b/rust/tw_keypair/src/ecdsa/secp256k1/keypair.rs new file mode 100644 index 00000000000..665d341e8e7 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/secp256k1/keypair.rs @@ -0,0 +1,72 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::secp256k1::private::PrivateKey; +use crate::ecdsa::secp256k1::public::PublicKey; +use crate::ecdsa::secp256k1::{Signature, VerifySignature}; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use tw_hash::H256; +use zeroize::{ZeroizeOnDrop, Zeroizing}; + +/// Represents a pair of `secp256k1` private and public keys. +#[derive(ZeroizeOnDrop)] +pub struct KeyPair { + private: PrivateKey, + #[zeroize(skip)] + public: PublicKey, +} + +impl KeyPairTrait for KeyPair { + type Private = PrivateKey; + type Public = PublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for KeyPair { + type SigningMessage = H256; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.private.sign(message) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningMessage = H256; + type VerifySignature = VerifySignature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public.verify(signature, message) + } +} + +impl<'a> TryFrom<&'a [u8]> for KeyPair { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = PrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(KeyPair { private, public }) + } +} + +impl<'a> TryFrom<&'a str> for KeyPair { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs b/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs new file mode 100644 index 00000000000..ebb0400205c --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs @@ -0,0 +1,153 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use k256::Secp256k1; + +mod keypair; +mod private; +mod public; + +pub use keypair::KeyPair; +pub use private::PrivateKey; +pub use public::PublicKey; + +pub type Signature = crate::ecdsa::signature::Signature; +pub type VerifySignature = crate::ecdsa::signature::VerifySignature; + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; + use tw_encoding::hex; + use tw_hash::sha3::keccak256; + use tw_hash::{H256, H264, H520}; + use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + + #[test] + fn test_key_pair() { + let secret = + hex::decode("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5") + .unwrap(); + let key_pair = KeyPair::try_from(secret.as_slice()).unwrap(); + assert_eq!(key_pair.private().to_zeroizing_vec().as_slice(), secret); + assert_eq!( + key_pair.public().compressed(), + H264::from("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1") + ); + } + + #[test] + fn test_key_pair_sign() { + let key_pair = + KeyPair::try_from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5") + .unwrap(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + let signature = key_pair.sign(hash_to_sign).unwrap(); + + let expected = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + assert_eq!(signature.to_bytes(), expected); + + let verify_signature = VerifySignature::from(signature); + assert!(key_pair.verify(verify_signature, hash_to_sign)); + } + + #[test] + fn test_private_key_from() { + let hex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let expected = hex::decode(hex).unwrap(); + + // Test `From<&'static str>`. + let private = PrivateKey::try_from(hex).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), expected); + + // Test `From<&'a [u8]>`. + let private = PrivateKey::try_from(expected.as_slice()).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), expected); + } + + #[test] + fn test_private_key_sign_verify() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let private = PrivateKey::try_from(secret).unwrap(); + let public = private.public(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + let signature = private.sign(hash_to_sign).unwrap(); + + let expected = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + assert_eq!(signature.to_bytes(), expected); + + let verify_signature = VerifySignature::from(signature); + assert!(public.verify(verify_signature, hash_to_sign)); + } + + #[test] + fn test_public_key_from() { + let compressed = "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"; + let uncompressed = "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"; + let expected_compressed = H264::from(compressed); + let expected_uncompressed = H520::from(uncompressed); + + // From extended public key. + let public = PublicKey::try_from(uncompressed).unwrap(); + assert_eq!(public.to_vec(), expected_compressed.into_vec()); + assert_eq!(public.compressed(), expected_compressed); + assert_eq!(public.uncompressed(), expected_uncompressed); + + // From compressed public key. + let public = PublicKey::try_from(compressed).unwrap(); + assert_eq!(public.to_vec(), expected_compressed.into_vec()); + assert_eq!(public.compressed(), expected_compressed); + assert_eq!(public.uncompressed(), expected_uncompressed); + } + + #[test] + fn test_verify_invalid() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let private = PrivateKey::try_from(secret).unwrap(); + + let signature_bytes = H520::from("375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f0801"); + let verify_sig = VerifySignature::try_from(signature_bytes.as_slice()).unwrap(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + + assert!(!private.public().verify(verify_sig, hash_to_sign)); + } + + #[test] + fn test_shared_key_hash() { + let private = PrivateKey::try_from( + "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0", + ) + .unwrap(); + let public = PublicKey::try_from( + "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992", + ) + .unwrap(); + let actual = private.shared_key_hash(&public); + let expected = + H256::from("ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); + assert_eq!(actual, expected); + } + + #[test] + fn test_public_key_recover() { + let sign_bytes = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + let sign = Signature::from_bytes(sign_bytes.as_slice()).unwrap(); + + let signed_hash = keccak256(b"hello"); + let signed_hash = H256::try_from(signed_hash.as_slice()).unwrap(); + + let actual = PublicKey::recover(sign, signed_hash).unwrap(); + let expected_compressed = + H264::from("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + assert_eq!(actual.compressed(), expected_compressed); + } +} diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/private.rs b/rust/tw_keypair/src/ecdsa/secp256k1/private.rs new file mode 100644 index 00000000000..088b3bd549f --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/secp256k1/private.rs @@ -0,0 +1,89 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::secp256k1::public::PublicKey; +use crate::ecdsa::secp256k1::Signature; +use crate::traits::SigningKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use k256::ecdsa::{SigningKey, VerifyingKey}; +use k256::elliptic_curve::sec1::ToEncodedPoint; +use k256::{AffinePoint, ProjectivePoint}; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::{ZeroizeOnDrop, Zeroizing}; + +/// Represents a `secp256k1` private key. +#[derive(ZeroizeOnDrop)] +pub struct PrivateKey { + pub(crate) secret: SigningKey, +} + +impl PrivateKey { + /// Returns an associated `secp256k1` public key. + pub fn public(&self) -> PublicKey { + PublicKey::new(*self.secret.verifying_key()) + } + + /// Computes an EC Diffie-Hellman secret in constant time. + /// The method is ported from [TW::PrivateKey::getSharedKey](https://github.com/trustwallet/wallet-core/blob/830b1c5baaf90692196163999e4ee2063c5f4e49/src/PrivateKey.cpp#L175-L191). + pub fn shared_key_hash(&self, pubkey: &PublicKey) -> H256 { + let shared_secret = diffie_hellman(&self.secret, &pubkey.public); + + // Get a compressed shared secret (33 bytes with a tag in front). + let compress = true; + let shared_secret_compressed = shared_secret.to_encoded_point(compress); + + let shared_secret_hash = tw_hash::sha2::sha256(shared_secret_compressed.as_bytes()); + H256::try_from(shared_secret_hash.as_slice()).expect("Expected 32 byte array sha256 hash") + } +} + +/// This method is inspired by [elliptic_curve::ecdh::diffie_hellman](https://github.com/RustCrypto/traits/blob/f0dbe44fea56d4c17e625ababacb580fec842137/elliptic-curve/src/ecdh.rs#L60-L70) +fn diffie_hellman(private: &SigningKey, public: &VerifyingKey) -> AffinePoint { + let public_point = ProjectivePoint::from(*public.as_affine()); + let secret_scalar = private.as_nonzero_scalar().as_ref(); + // Multiply the secret and public to get a shared secret affine point (x, y). + (public_point * secret_scalar).to_affine() +} + +impl SigningKeyTrait for PrivateKey { + type SigningMessage = H256; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + let (signature, recovery_id) = self + .secret + .sign_prehash_recoverable(message.as_slice()) + .map_err(|_| KeyPairError::SigningError)?; + Ok(Signature::new(signature, recovery_id)) + } +} + +impl<'a> TryFrom<&'a [u8]> for PrivateKey { + type Error = KeyPairError; + + fn try_from(data: &'a [u8]) -> Result { + let secret = SigningKey::from_slice(data).map_err(|_| KeyPairError::InvalidSecretKey)?; + Ok(PrivateKey { secret }) + } +} + +impl<'a> TryFrom<&'a str> for PrivateKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let secret = Zeroizing::new(self.secret.to_bytes()); + Zeroizing::new(secret.as_slice().to_vec()) + } +} diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs new file mode 100644 index 00000000000..92dc6b0cc52 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs @@ -0,0 +1,123 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::secp256k1::{Signature, VerifySignature}; +use crate::traits::VerifyingKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use der::Document; +use k256::ecdsa::signature::hazmat::PrehashVerifier; +use k256::ecdsa::VerifyingKey; +use tw_encoding::hex; +use tw_hash::{Hash, H256, H264, H512, H520}; +use tw_misc::traits::ToBytesVec; + +/// Represents a `secp256k1` public key. +#[derive(Clone, PartialEq)] +pub struct PublicKey { + pub(crate) public: VerifyingKey, +} + +/// cbindgen:ignore +impl PublicKey { + /// The number of bytes in a compressed public key. + pub const COMPRESSED: usize = H264::len(); + /// The number of bytes in an uncompressed public key. + pub const UNCOMPRESSED: usize = H520::len(); + + /// Recover a [`PublicKey`] from the given `message` and the signature over that. + pub fn recover(sign: Signature, message: H256) -> KeyPairResult { + VerifyingKey::recover_from_prehash(message.as_slice(), &sign.signature, sign.v) + .map(|public| PublicKey { public }) + .map_err(|_| KeyPairError::InvalidSignature) + } + + /// Creates a public key from the given [`VerifyingKey`]. + pub(crate) fn new(public: VerifyingKey) -> PublicKey { + PublicKey { public } + } + + /// Returns the raw data of the compressed public key (33 bytes). + pub fn compressed(&self) -> H264 { + let compressed = true; + H264::try_from(self.public.to_encoded_point(compressed).as_bytes()) + .expect("Expected 33 byte array Public Key") + } + + /// Returns the raw data of the uncompressed public key (65 bytes). + pub fn uncompressed(&self) -> H520 { + let compressed = false; + H520::try_from(self.public.to_encoded_point(compressed).as_bytes()) + .expect("Expected 65 byte array Public Key") + } + + /// Returns the raw data of the uncompressed public key without the tag prefix (64 bytes). + pub fn uncompressed_without_prefix(&self) -> H512 { + let (_prefix, public): (Hash<1>, H512) = self.uncompressed().split(); + public + } + + /// Returns the public key as DER-encoded bytes. + pub fn der_encoded(&self) -> Vec { + let compressed = false; + let public_key_bytes = self.public.to_encoded_point(compressed); + let subject_public_key = + der::asn1::BitStringRef::new(0, public_key_bytes.as_bytes()).unwrap(); + + let oid: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + let associated_oid: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.10"); + let spki = pkcs8::SubjectPublicKeyInfo { + algorithm: pkcs8::spki::AlgorithmIdentifier { + oid, + parameters: Some(associated_oid), + }, + subject_public_key, + }; + + let doc = Document::try_from(&spki).unwrap(); + doc.into_vec() + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningMessage = H256; + type VerifySignature = VerifySignature; + + fn verify(&self, sign: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public + .verify_prehash(message.as_slice(), &sign.signature) + .is_ok() + } +} + +impl<'a> TryFrom<&'a str> for PublicKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?; + Self::try_from(bytes.as_slice()) + } +} + +impl<'a> TryFrom<&'a [u8]> for PublicKey { + type Error = KeyPairError; + + /// Expected either `H264` or `H520` slice. + fn try_from(data: &'a [u8]) -> Result { + Ok(PublicKey { + public: VerifyingKey::from_sec1_bytes(data) + .map_err(|_| KeyPairError::InvalidPublicKey)?, + }) + } +} + +/// Return the compressed bytes representation by default. +/// Consider using [`PublicKey::compressed`] or [`PublicKey::uncompressed`] instead. +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.compressed().to_vec() + } +} diff --git a/rust/tw_keypair/src/ecdsa/signature.rs b/rust/tw_keypair/src/ecdsa/signature.rs new file mode 100644 index 00000000000..64fe61bfe83 --- /dev/null +++ b/rust/tw_keypair/src/ecdsa/signature.rs @@ -0,0 +1,174 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::EcdsaCurve; +use crate::{KeyPairError, KeyPairResult}; +use ecdsa::elliptic_curve::FieldBytes; +use std::ops::{Range, RangeInclusive}; +use tw_hash::{H256, H520}; +use tw_misc::traits::ToBytesVec; + +/// Represents an ECDSA signature. +#[derive(Clone, Debug, PartialEq)] +pub struct Signature { + pub(crate) signature: ecdsa::Signature, + pub(crate) v: ecdsa::RecoveryId, +} + +/// cbindgen:ignore +impl Signature { + /// The number of bytes for a serialized signature representation. + pub const LEN: usize = 65; + pub const R_RANGE: Range = 0..32; + pub const S_RANGE: Range = 32..64; + pub const RECOVERY_LAST: usize = 64; + /// Expected signature with or without recovery byte in the end of the slice. + pub const VERIFY_SIGNATURE_LEN_RANGE: RangeInclusive = 64..=65; + + /// Creates a `secp256k1` recoverable signature from the given [`k256::ecdsa::Signature`] + /// and the `v` recovery byte. + pub(crate) fn new(signature: ecdsa::Signature, v: ecdsa::RecoveryId) -> Self { + Signature { signature, v } + } + + /// Returns the number of bytes for a serialized signature representation. + pub const fn len() -> usize { + Self::LEN + } + + /// Returns an r-coordinate as 32 byte array. + pub fn r(&self) -> H256 { + let (r, _s) = self.signature.split_bytes(); + H256::try_from(r.as_slice()).expect("Expected 'r' 32 byte length array") + } + + /// Returns an s-value as 32 byte array. + pub fn s(&self) -> H256 { + let (_, s) = self.signature.split_bytes(); + H256::try_from(s.as_slice()).expect("Expected 's' 32 byte length array") + } + + /// Returns a recovery ID. + pub fn v(&self) -> u8 { + self.v.to_byte() + } + + /// Tries to create a Signature from the serialized representation. + pub fn from_bytes(sig: &[u8]) -> KeyPairResult { + if sig.len() != Self::len() { + return Err(KeyPairError::InvalidSignature); + } + + let v = ecdsa::RecoveryId::from_byte(sig[Self::RECOVERY_LAST]) + .ok_or(KeyPairError::InvalidSignature)?; + + Ok(Signature { + signature: Self::signature_from_slices(&sig[Self::R_RANGE], &sig[Self::S_RANGE])?, + v, + }) + } + + /// Tries to create a Signature from the parts. + pub fn try_from_parts(r: H256, s: H256, v: u8) -> KeyPairResult { + Ok(Signature { + signature: Self::signature_from_slices(r.as_slice(), s.as_slice())?, + v: ecdsa::RecoveryId::from_byte(v).ok_or(KeyPairError::InvalidSignature)?, + }) + } + + /// Returns a standard binary signature representation: + /// RSV, where R - 32 byte array, S - 32 byte array, V - 1 byte. + pub fn to_bytes(&self) -> H520 { + let (r, s) = self.signature.split_bytes(); + + let mut dest = H520::default(); + dest[Self::R_RANGE].copy_from_slice(r.as_slice()); + dest[Self::S_RANGE].copy_from_slice(s.as_slice()); + dest[Self::RECOVERY_LAST] = self.v.to_byte(); + dest + } + + /// # Panic + /// + /// `r` and `s` must be 32 byte arrays, otherwise the function panics. + fn signature_from_slices(r: &[u8], s: &[u8]) -> KeyPairResult> { + let r = FieldBytes::::clone_from_slice(r); + let s = FieldBytes::::clone_from_slice(s); + + ecdsa::Signature::::from_scalars(r, s).map_err(|_| KeyPairError::InvalidSignature) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + self.to_bytes().to_vec() + } +} + +impl<'a, C: EcdsaCurve> TryFrom<&'a [u8]> for Signature { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + Signature::from_bytes(bytes) + } +} + +/// To verify the signature, it's enough to check `r` and `s` parts without the recovery ID. +pub struct VerifySignature { + pub signature: ecdsa::Signature, +} + +impl<'a, C: EcdsaCurve> TryFrom<&'a [u8]> for VerifySignature { + type Error = KeyPairError; + + fn try_from(sig: &'a [u8]) -> Result { + if !Signature::::VERIFY_SIGNATURE_LEN_RANGE.contains(&sig.len()) { + return Err(KeyPairError::InvalidSignature); + } + + Ok(VerifySignature { + signature: Signature::signature_from_slices( + &sig[Signature::::R_RANGE], + &sig[Signature::::S_RANGE], + )?, + }) + } +} + +impl From> for VerifySignature { + fn from(sig: Signature) -> Self { + VerifySignature { + signature: sig.signature, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use k256::Secp256k1; + + #[test] + fn test_signature() { + let sign_bytes = H520::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a00"); + let sign = Signature::::from_bytes(sign_bytes.as_slice()).unwrap(); + assert_eq!( + sign.r(), + H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47") + ); + assert_eq!( + sign.s(), + H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a") + ); + assert_eq!(sign.v(), 0); + assert_eq!(sign.to_bytes(), sign_bytes); + } + + #[test] + fn test_signature_from_invalid_bytes() { + Signature::::from_bytes(b"123").unwrap_err(); + } +} diff --git a/rust/tw_keypair/src/ed25519/keypair.rs b/rust/tw_keypair/src/ed25519/keypair.rs new file mode 100644 index 00000000000..e9240d2d4d3 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/keypair.rs @@ -0,0 +1,68 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::{private::PrivateKey, public::PublicKey, signature::Signature, Hasher512}; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use zeroize::Zeroizing; + +/// Represents a pair of `ed25519` private and public keys. +#[derive(Debug)] +pub struct KeyPair { + private: PrivateKey, + public: PublicKey, +} + +impl KeyPairTrait for KeyPair { + type Private = PrivateKey; + type Public = PublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for KeyPair { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.private().sign_with_public_key(self.public(), &message) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public().verify(signature, message) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for KeyPair { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = PrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(KeyPair { private, public }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for KeyPair { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/ed25519/mangle.rs b/rust/tw_keypair/src/ed25519/mangle.rs new file mode 100644 index 00000000000..f635cc19a40 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/mangle.rs @@ -0,0 +1,17 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +/// Clamps the scalar by: +/// 1. clearing the 3 lower bits, +/// 2. clearing the highest bit, +/// 3. setting the second highest bit. +/// +/// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/secret.rs#L277-L279C26 +pub fn mangle_scalar(scalar: &mut [u8; 32]) { + scalar[0] &= 0b1111_1000; + scalar[31] &= 0b0011_1111; + scalar[31] |= 0b0100_0000; +} diff --git a/rust/tw_keypair/src/ed25519/mod.rs b/rust/tw_keypair/src/ed25519/mod.rs new file mode 100644 index 00000000000..32c079ad6af --- /dev/null +++ b/rust/tw_keypair/src/ed25519/mod.rs @@ -0,0 +1,265 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use digest::{consts::U64, Digest}; + +mod keypair; +mod mangle; +mod modifications; +mod private; +mod public; +mod secret; +mod signature; + +pub use modifications::{cardano, waves}; +pub use signature::Signature; + +/// Standard `ed25519` implementation. +pub mod sha512 { + use sha2::Sha512; + + pub type KeyPair = crate::ed25519::keypair::KeyPair; + pub type PrivateKey = crate::ed25519::private::PrivateKey; + pub type PublicKey = crate::ed25519::public::PublicKey; +} + +/// `ed25519` implementation using `BLAKE2B` hash function. +pub mod blake2b { + use blake2::Blake2b; + + pub type KeyPair = crate::ed25519::keypair::KeyPair; + pub type PrivateKey = crate::ed25519::private::PrivateKey; + pub type PublicKey = crate::ed25519::public::PublicKey; +} + +/// A hash function that returns 64 length output. +pub trait Hasher512: Digest { + const OUTPUT_LEN: usize = 64; + const HALF_LEN: usize = 32; +} + +impl Hasher512 for T where T: Digest {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; + use tw_encoding::hex; + use tw_hash::sha2::sha256; + use tw_hash::sha3::keccak256; + use tw_hash::{H256, H512}; + use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + + #[test] + fn test_private_from_bytes() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + + let private = sha512::PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let actual = private.to_zeroizing_vec(); + assert_eq!(actual.as_slice(), H256::from(secret).as_slice()); + } + + #[test] + fn test_private_to_public() { + let private = sha512::PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + + let expected = + H256::from("4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); + assert_eq!(public.to_vec(), expected.into_vec()); + } + + #[test] + fn test_private_to_public_blake2b() { + let private = blake2b::PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + + let expected = + H256::from("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + assert_eq!(public.to_bytes(), expected); + } + + #[test] + fn test_keypair_sign_verify() { + let keypair = sha512::KeyPair::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let to_sign = sha256(b"Hello"); + let actual = keypair.sign(to_sign.clone()).unwrap(); + + let expected = H512::from("42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); + assert_eq!(actual.to_bytes(), expected); + + assert!(keypair.verify(actual, to_sign)); + } + + #[test] + fn test_signature_malleability() { + let orig_sign_bytes = hex::decode("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07e7ed7a20a4e2fa1009db3d1443e937e6abb16ff3c3eaecb798faed7fbb40b008").unwrap(); + Signature::try_from(orig_sign_bytes.as_slice()).unwrap(); + + let modified_sign_bytes = hex::decode("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07d4c1707dbe450d69df7735b721e316fbabb16ff3c3eaecb798faed7fbb40b018").unwrap(); + Signature::try_from(modified_sign_bytes.as_slice()).unwrap_err(); + } + + #[test] + fn test_keypair_sign_verify_blake2b() { + let keypair = blake2b::KeyPair::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let to_sign = sha256(b"Hello"); + let actual = keypair.sign(to_sign.clone()).unwrap(); + + let expected = H512::from("5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); + assert_eq!(actual.to_bytes(), expected); + + assert!(keypair.verify(actual, to_sign)); + } + + #[test] + fn test_keypair_sign_verify_waves() { + let keypair = waves::KeyPair::try_from( + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a", + ) + .unwrap(); + let to_sign = hex::decode("0402559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d00000000016372e852120000000005f5e1000000000005f5e10001570acc4110b78a6d38b34d879b5bba38806202ecf1732f8542000766616c6166656c").unwrap(); + let actual = keypair.sign(to_sign.clone()).unwrap(); + + let expected = H512::from("af7989256f496e103ce95096b3f52196dd9132e044905fe486da3b829b5e403bcba95ab7e650a4a33948c2d05cfca2dce4d4df747e26402974490fb4c49fbe8f"); + assert_eq!(actual.to_bytes(), expected); + + assert!(keypair.verify(actual, to_sign)); + } + + #[test] + fn test_private_to_public_waves() { + let private = waves::PrivateKey::try_from( + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a", + ) + .unwrap(); + let actual = private.public(); + let expected = + H256::from("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); + assert_eq!(actual.to_vec().as_slice(), expected.as_slice()); + } + + #[test] + fn test_private_from_bytes_cardano() { + let secret = "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4\ + 639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; + + let private = cardano::ExtendedPrivateKey::try_from(secret).unwrap(); + let actual = private.to_zeroizing_vec(); + assert_eq!(actual.as_slice(), hex::decode(secret).unwrap()); + } + + #[test] + fn test_keypair_sign_verify_extended_cardano() { + let secret = "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4\ + 639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; + + let keypair = cardano::ExtendedKeyPair::try_from(secret).unwrap(); + + let message = keccak256(b"hello"); + let actual = keypair.sign(message.clone()).unwrap(); + + let expected = H512::from("375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08"); + assert_eq!(actual.to_bytes(), expected); + + assert!(keypair.verify(actual, message)); + } + + #[test] + fn test_public_verify_invalid_waves() { + let invalid_sigs = [ + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ]; + + let public = waves::PublicKey::try_from( + "b60076cc30ffff5c29c65af9a13ce01c3affc231d09fccbcd1277319c7911634", + ) + .unwrap(); + let msg = vec![1, 2, 3]; + + for invalid_sig_hex in invalid_sigs { + let invalid_sig_bytes = hex::decode(invalid_sig_hex).unwrap(); + let invalid_sig = waves::Signature::try_from(invalid_sig_bytes.as_slice()).unwrap(); + + public.verify(invalid_sig, msg.clone()); + } + } + + #[test] + fn test_private_to_public_extended_cardano() { + let secret = "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa\ + e0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"; + + let private = cardano::ExtendedPrivateKey::try_from(secret).unwrap(); + let public = private.public(); + + let expected = "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa\ + f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"; + assert_eq!(public.to_vec(), hex::decode(expected).unwrap()); + } + + #[test] + fn test_public_key_from_bytes_extended_cardano() { + let pubkey_hex = "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa\ + f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"; + let pubkey_bytes = hex::decode(pubkey_hex).unwrap(); + + let actual = cardano::ExtendedPublicKey::try_from(pubkey_bytes.as_slice()).unwrap(); + assert_eq!(actual.to_vec(), pubkey_bytes); + } + + #[test] + fn test_signature_from_bytes() { + let signature = "418aff0000000000000000000000000000000000000000000000f600000000000000000000000000000000000000000000000000000000000000000000000010"; + let actual = Signature::try_from(hex::decode(signature).unwrap().as_slice()).unwrap(); + assert_eq!(actual.to_bytes(), H512::from(signature)); + } + + #[test] + fn test_waves_signature_from_bytes() { + let sig_bytes = H512::from("af7989256f496e103ce95096b3f52196dd9132e044905fe486da3b829b5e403bcba95ab7e650a4a33948c2d05cfca2dce4d4df747e26402974490fb4c49fbe8f"); + let signature = waves::Signature::try_from(sig_bytes.as_slice()).unwrap(); + assert_eq!(signature.to_vec(), sig_bytes.into_vec()); + } + + #[test] + fn test_keypair_from_invalid_bytes() { + let invalid = [0; 1]; + let _ = sha512::KeyPair::try_from(&invalid[..]).unwrap_err(); + let _ = sha512::PrivateKey::try_from(&invalid[..]).unwrap_err(); + let _ = sha512::PublicKey::try_from(&invalid[..]).unwrap_err(); + } + + #[test] + fn test_debug() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let sign = H512::from("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07e7ed7a20a4e2fa1009db3d1443e937e6abb16ff3c3eaecb798faed7fbb40b008"); + + let keypair = sha512::KeyPair::try_from(secret).unwrap(); + let sign = Signature::try_from(sign.as_slice()).unwrap(); + + let _ = format!("{:?}", keypair); + let _ = format!("{:?}", keypair.private()); + let _ = format!("{:?}", keypair.public()); + let _ = format!("{:?}", sign); + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_keypair.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_keypair.rs new file mode 100644 index 00000000000..6da604cc660 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_keypair.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::modifications::cardano::{ + extended_private::ExtendedPrivateKey, extended_public::ExtendedPublicKey, +}; +use crate::ed25519::{signature::Signature, Hasher512}; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use zeroize::Zeroizing; + +/// Represents an `ed25519` extended key pair that is used in Cardano blockchain. +pub struct ExtendedKeyPair { + private: ExtendedPrivateKey, + public: ExtendedPublicKey, +} + +impl KeyPairTrait for ExtendedKeyPair { + type Private = ExtendedPrivateKey; + type Public = ExtendedPublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for ExtendedKeyPair { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.private() + .sign_with_public_key(self.public(), message.as_slice()) + } +} + +impl VerifyingKeyTrait for ExtendedKeyPair { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public().verify(signature, message) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedKeyPair { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = ExtendedPrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(ExtendedKeyPair { private, public }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for ExtendedKeyPair { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs new file mode 100644 index 00000000000..5c3ac800c52 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs @@ -0,0 +1,152 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::modifications::cardano::extended_public::{ + ExtendedPublicKey, ExtendedPublicPart, +}; +use crate::ed25519::public::PublicKey; +use crate::ed25519::secret::ExpandedSecretKey; +use crate::ed25519::signature::Signature; +use crate::ed25519::Hasher512; +use crate::traits::SigningKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use std::ops::Range; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::{ZeroizeOnDrop, Zeroizing}; + +/// Represents an `ed25519` extended private key that is used in Cardano blockchain. +pub struct ExtendedPrivateKey { + /// The first half (96 bytes) of the extended secret. + key: ExtendedSecretPart, + /// The second half (96 bytes) of the extended secret. + second_key: ExtendedSecretPart, +} + +/// cbindgen:ignore +impl ExtendedPrivateKey { + /// The number of bytes in a serialized private key (192 bytes). + pub const LEN: usize = ExtendedSecretPart::::LEN * 2; + const KEY_RANGE: Range = 0..ExtendedSecretPart::::LEN; + const SECOND_KEY_RANGE: Range = ExtendedSecretPart::::LEN..Self::LEN; + + /// Returns an associated Cardano extended `ed25519` public key. + pub fn public(&self) -> ExtendedPublicKey { + let key_public = PublicKey::with_expanded_secret_no_mangle(&self.key.expanded_key); + let second_key_public = + PublicKey::with_expanded_secret_no_mangle(&self.second_key.expanded_key); + + let key = ExtendedPublicPart::new(key_public, self.key.chain_code); + let second_key = ExtendedPublicPart::new(second_key_public, self.second_key.chain_code); + + ExtendedPublicKey::new(key, second_key) + } + + /// `ed25519` signing uses a public key associated with the private key. + pub(crate) fn sign_with_public_key( + &self, + public: &ExtendedPublicKey, + message: &[u8], + ) -> KeyPairResult { + self.key + .expanded_key + .sign_with_pubkey(public.key_for_signing(), message) + } +} + +impl SigningKeyTrait for ExtendedPrivateKey { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.sign_with_public_key(&self.public(), message.as_slice()) + } +} + +impl ToBytesZeroizing for ExtendedPrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let mut res = self.key.to_zeroizing_vec(); + res.extend_from_slice(self.second_key.to_zeroizing_vec().as_slice()); + res + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedPrivateKey { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Self::LEN { + return Err(KeyPairError::InvalidSecretKey); + } + let key = ExtendedSecretPart::try_from(&bytes[Self::KEY_RANGE])?; + let second_key = ExtendedSecretPart::try_from(&bytes[Self::SECOND_KEY_RANGE])?; + + Ok(ExtendedPrivateKey { key, second_key }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for ExtendedPrivateKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} + +#[derive(ZeroizeOnDrop)] +struct ExtendedSecretPart { + secret: H256, + extension: H256, + chain_code: H256, + /// An expanded secret key obtained from [`ExtendedSecretPart::secret`] + /// and [`ExtendedSecretPart::extension`]. + /// It's used to generate a public key and sign messages. + expanded_key: ExpandedSecretKey, +} + +/// cbindgen:ignore +impl ExtendedSecretPart { + const LEN: usize = 96; + const SECRET_RANGE: Range = 0..32; + const EXTENSION_RANGE: Range = 32..64; + const CHAIN_CODE_RANGE: Range = 64..96; +} + +impl ToBytesZeroizing for ExtendedSecretPart { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let mut res = Vec::with_capacity(H256::len() * 3); + res.extend_from_slice(self.secret.as_slice()); + res.extend_from_slice(self.extension.as_slice()); + res.extend_from_slice(self.chain_code.as_slice()); + Zeroizing::new(res) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedSecretPart { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Self::LEN { + return Err(KeyPairError::InvalidSecretKey); + } + let secret = H256::try_from(&bytes[Self::SECRET_RANGE]) + .map_err(|_| KeyPairError::InvalidSecretKey)?; + let extension = H256::try_from(&bytes[Self::EXTENSION_RANGE]) + .map_err(|_| KeyPairError::InvalidSecretKey)?; + let chain_code = H256::try_from(&bytes[Self::CHAIN_CODE_RANGE]) + .map_err(|_| KeyPairError::InvalidSecretKey)?; + + let expanded_key = ExpandedSecretKey::with_extended_secret(secret, extension); + Ok(ExtendedSecretPart { + secret, + extension, + chain_code, + expanded_key, + }) + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs new file mode 100644 index 00000000000..1a8eaa65b08 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs @@ -0,0 +1,125 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::public::PublicKey; +use crate::ed25519::signature::Signature; +use crate::ed25519::Hasher512; +use crate::traits::VerifyingKeyTrait; +use crate::KeyPairError; +use std::ops::Range; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; + +/// Represents an `ed25519` extended public key that is used in Cardano blockchain. +#[derive(Clone)] +pub struct ExtendedPublicKey { + /// The first half of the public key (64 bytes). + key: ExtendedPublicPart, + /// The second half of the public key (64 bytes). + second_key: ExtendedPublicPart, +} + +/// cbindgen:ignore +impl ExtendedPublicKey { + /// The number of bytes in a serialized public key. + pub const LEN: usize = ExtendedPublicPart::::LEN * 2; + const KEY_RANGE: Range = 0..ExtendedPublicPart::::LEN; + const SECOND_KEY_RANGE: Range = ExtendedPublicPart::::LEN..Self::LEN; + + /// Creates a public key with the given [`ExtendedPublicPart`] first and second keys. + pub(crate) fn new(key: ExtendedPublicPart, second_key: ExtendedPublicPart) -> Self { + ExtendedPublicKey { key, second_key } + } + + /// Returns a public key bytes (32 length) that is used in signing. + pub(crate) fn key_for_signing(&self) -> H256 { + self.key.public.to_bytes() + } +} + +impl VerifyingKeyTrait for ExtendedPublicKey { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.key.public.verify(signature, message) + } +} + +impl ToBytesVec for ExtendedPublicKey { + fn to_vec(&self) -> Vec { + let mut res = self.key.to_vec(); + res.extend_from_slice(self.second_key.to_vec().as_slice()); + res + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedPublicKey { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Self::LEN { + return Err(KeyPairError::InvalidPublicKey); + } + + let key = ExtendedPublicPart::try_from(&bytes[Self::KEY_RANGE])?; + let second_key = ExtendedPublicPart::try_from(&bytes[Self::SECOND_KEY_RANGE])?; + + Ok(ExtendedPublicKey { key, second_key }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for ExtendedPublicKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?; + Self::try_from(bytes.as_slice()) + } +} + +#[derive(Clone)] +pub(crate) struct ExtendedPublicPart { + public: PublicKey, + chain_code: H256, +} + +/// cbindgen:ignore +impl ExtendedPublicPart { + const LEN: usize = PublicKey::::LEN + H256::len(); + const PUBLIC_RANGE: Range = 0..32; + const CHAIN_CODE_RANGE: Range = 32..64; + + pub(crate) fn new(public: PublicKey, chain_code: H256) -> ExtendedPublicPart { + ExtendedPublicPart { public, chain_code } + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedPublicPart { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Self::LEN { + return Err(KeyPairError::InvalidPublicKey); + } + + let public = PublicKey::try_from(&bytes[Self::PUBLIC_RANGE])?; + let chain_code = H256::try_from(&bytes[Self::CHAIN_CODE_RANGE]) + .map_err(|_| KeyPairError::InvalidPublicKey)?; + + Ok(ExtendedPublicPart { public, chain_code }) + } +} + +impl ToBytesVec for ExtendedPublicPart { + fn to_vec(&self) -> Vec { + let mut res = Vec::with_capacity(H256::len() * 2); + res.extend_from_slice(self.public.as_slice()); + res.extend_from_slice(self.chain_code.as_slice()); + res + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/mod.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/mod.rs new file mode 100644 index 00000000000..d5f0f3d2eae --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/mod.rs @@ -0,0 +1,15 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use sha2::Sha512; + +mod extended_keypair; +mod extended_private; +mod extended_public; + +pub type ExtendedKeyPair = extended_keypair::ExtendedKeyPair; +pub type ExtendedPrivateKey = extended_private::ExtendedPrivateKey; +pub type ExtendedPublicKey = extended_public::ExtendedPublicKey; diff --git a/rust/tw_keypair/src/ed25519/modifications/mod.rs b/rust/tw_keypair/src/ed25519/modifications/mod.rs new file mode 100644 index 00000000000..7f19f608c5d --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod cardano; +pub mod waves; diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/keypair.rs b/rust/tw_keypair/src/ed25519/modifications/waves/keypair.rs new file mode 100644 index 00000000000..890ac668041 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/waves/keypair.rs @@ -0,0 +1,70 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::modifications::waves::private::PrivateKey; +use crate::ed25519::modifications::waves::public::PublicKey; +use crate::ed25519::modifications::waves::Signature; +use crate::ed25519::Hasher512; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use zeroize::Zeroizing; + +/// Represents an `ed25519` key pair that is used in Waves blockchain. +pub struct KeyPair { + private: PrivateKey, + public: PublicKey, +} + +impl KeyPairTrait for KeyPair { + type Private = PrivateKey; + type Public = PublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for KeyPair { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.private.sign(message) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public.verify(signature, message) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for KeyPair { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = PrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(KeyPair { private, public }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for KeyPair { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?); + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/mod.rs b/rust/tw_keypair/src/ed25519/modifications/waves/mod.rs new file mode 100644 index 00000000000..45f0320be09 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/waves/mod.rs @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use sha2::Sha512; + +mod keypair; +mod private; +mod public; +mod signature; + +pub use signature::Signature; + +pub type KeyPair = keypair::KeyPair; +pub type PrivateKey = private::PrivateKey; +pub type PublicKey = public::PublicKey; diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/private.rs b/rust/tw_keypair/src/ed25519/modifications/waves/private.rs new file mode 100644 index 00000000000..569f9dde44c --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/waves/private.rs @@ -0,0 +1,61 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::modifications::waves::public::PublicKey; +use crate::ed25519::modifications::waves::signature::Signature; +use crate::ed25519::{private::PrivateKey as StandardPrivateKey, Hasher512}; +use crate::traits::SigningKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::Zeroizing; + +/// Represents an `ed25519` private key that is used in Waves blockchain. +pub struct PrivateKey { + standard_key: StandardPrivateKey, +} + +impl PrivateKey { + /// Returns an associated Waves `ed25519` public key. + pub fn public(&self) -> PublicKey { + PublicKey::with_standard_pubkey(&self.standard_key.public()) + } +} + +impl SigningKeyTrait for PrivateKey { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + let signature = self.standard_key.sign(message)?; + let standard_pubkey = self.standard_key.public(); + Ok(Signature::new_mangling(&standard_pubkey, &signature)) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for PrivateKey { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let standard_key = StandardPrivateKey::try_from(bytes)?; + Ok(PrivateKey { standard_key }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for PrivateKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + self.standard_key.to_zeroizing_vec() + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/public.rs b/rust/tw_keypair/src/ed25519/modifications/waves/public.rs new file mode 100644 index 00000000000..47e3e2ce638 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/waves/public.rs @@ -0,0 +1,93 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::modifications::waves::signature::Signature; +use crate::ed25519::public::PublicKey as StandardPublicKey; +use crate::ed25519::Hasher512; +use crate::traits::VerifyingKeyTrait; +use crate::KeyPairError; +use curve25519_dalek::montgomery::MontgomeryPoint; +use std::marker::PhantomData; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; + +/// Represents an `ed25519` public key that is used in Waves blockchain. +#[derive(Clone)] +pub struct PublicKey { + /// A public key that is calculated through converting + /// a standard [`curve25519_dalek::edwards::EdwardsPoint`] to [`MontgomeryPoint`]. + curve25519_pk: H256, + _phantom: PhantomData, +} + +/// cbindgen:ignore +impl PublicKey { + /// The number of bytes in a serialized public key. + pub const LEN: usize = H256::len(); + + /// Creates a public key from the given standard [`StandardPublicKey`]. + pub(crate) fn with_standard_pubkey(standard: &StandardPublicKey) -> PublicKey { + let montgomery_point = standard.edwards_point().to_montgomery(); + PublicKey { + curve25519_pk: H256::from(montgomery_point.0), + _phantom: PhantomData, + } + } + + /// Returns the raw data of the public key (32 bytes). + pub fn to_bytes(&self) -> H256 { + self.curve25519_pk + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + let Ok(standard_signature) = signature.to_standard_signature() else { + return false; + }; + + let montgomery_point = MontgomeryPoint(self.curve25519_pk.take()); + let pubkey_sign = signature.get_pubkey_sign(); + + let Some(ed25519_pk) = montgomery_point.to_edwards(pubkey_sign) else { + return false; + }; + let standard_public = StandardPublicKey::::with_edwards_point(ed25519_pk); + + standard_public.verify(standard_signature, message) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for PublicKey { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let curve25519_pk = H256::try_from(bytes).map_err(|_| KeyPairError::InvalidPublicKey)?; + Ok(PublicKey { + curve25519_pk, + _phantom: PhantomData, + }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for PublicKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?; + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.curve25519_pk.into_vec() + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs b/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs new file mode 100644 index 00000000000..8c1819a6a73 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs @@ -0,0 +1,82 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::public::PublicKey as StandardPublicKey; +use crate::ed25519::signature::Signature as StandardSignature; +use crate::ed25519::Hasher512; +use crate::{KeyPairError, KeyPairResult}; +use tw_hash::{H256, H512}; +use tw_misc::traits::ToBytesVec; + +/// cbindgen:ignore +/// Equals to 0x80. +const PUBKEY_SIGN_MASK: u8 = 0b1000_0000; + +/// cbindgen:ignore +/// Equals to 127. +const DROP_SIGN_BIT_MASK: u8 = 0b0111_1111; + +pub struct Signature { + bytes: H512, +} + +impl Signature { + /// Creates a signature by mangling a standard [`StandardSignature`]. + /// Ported: https://github.com/trustwallet/wallet-core/blob/3.1.31/src/PrivateKey.cpp#L225-L230 + pub(crate) fn new_mangling( + standard_pubkey: &StandardPublicKey, + standard_sign: &StandardSignature, + ) -> Signature { + let pubkey_bytes: H256 = standard_pubkey.to_bytes(); + let sign_bit = pubkey_bytes[31] & PUBKEY_SIGN_MASK; + + let mut bytes = standard_sign.to_bytes(); + bytes[63] &= DROP_SIGN_BIT_MASK; + bytes[63] |= sign_bit; + + Signature { bytes } + } + + /// Returns the signature data (64 bytes). + pub fn to_bytes(&self) -> H512 { + self.bytes + } + + /// Tries to convert the signature to a standard `ed25519` representation by removing the sign bit. + /// Ported: https://github.com/trustwallet/wallet-core/blob/3.1.31/src/PublicKey.cpp#L159-L162 + pub(crate) fn to_standard_signature(&self) -> KeyPairResult { + let mut bytes = self.bytes; + bytes[63] &= DROP_SIGN_BIT_MASK; + StandardSignature::try_from(bytes.as_slice()) + } + + /// Returns a public key sign bit. + /// Either `1` or `0`. + /// Ported: https://github.com/trustwallet/wallet-core/blob/3.1.31/src/PublicKey.cpp#L157 + pub(crate) fn get_pubkey_sign(&self) -> u8 { + let sign_bit = self.bytes[63] & PUBKEY_SIGN_MASK; + if sign_bit == 0 { + 0 + } else { + 1 + } + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let bytes = H512::try_from(bytes).map_err(|_| KeyPairError::InvalidSignature)?; + Ok(Signature { bytes }) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + self.bytes.into_vec() + } +} diff --git a/rust/tw_keypair/src/ed25519/private.rs b/rust/tw_keypair/src/ed25519/private.rs new file mode 100644 index 00000000000..9384bc66096 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/private.rs @@ -0,0 +1,88 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::public::PublicKey; +use crate::ed25519::secret::ExpandedSecretKey; +use crate::ed25519::signature::Signature; +use crate::ed25519::Hasher512; +use crate::traits::SigningKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use std::fmt; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::{ZeroizeOnDrop, Zeroizing}; + +/// Represents an `ed25519` private key. +#[derive(ZeroizeOnDrop)] +pub struct PrivateKey { + secret: H256, + /// An expanded secret key obtained from [`PrivateKey::secret`]. + /// It's used to generate a public key and sign messages. + expanded_key: ExpandedSecretKey, +} + +impl fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PrivateKey") + .field("secret", &self.secret) + .finish() + } +} + +impl PrivateKey { + /// Returns an associated `ed25519` public key. + pub fn public(&self) -> PublicKey { + PublicKey::with_expanded_secret(&self.expanded_key) + } + + /// `ed25519` signing uses a public key associated with the private key. + pub(crate) fn sign_with_public_key( + &self, + public: &PublicKey, + message: &[u8], + ) -> KeyPairResult { + self.expanded_key + .sign_with_pubkey(public.to_bytes(), message) + } +} + +impl SigningKeyTrait for PrivateKey { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.sign_with_public_key(&self.public(), &message) + } +} + +impl TryFrom<&[u8]> for PrivateKey { + type Error = KeyPairError; + + fn try_from(data: &[u8]) -> Result { + let secret = H256::try_from(data).map_err(|_| KeyPairError::InvalidSecretKey)?; + let expanded_key = ExpandedSecretKey::::with_secret(secret); + Ok(PrivateKey { + secret, + expanded_key, + }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for PrivateKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + Zeroizing::new(self.secret.to_vec()) + } +} diff --git a/rust/tw_keypair/src/ed25519/public.rs b/rust/tw_keypair/src/ed25519/public.rs new file mode 100644 index 00000000000..883147a92c8 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/public.rs @@ -0,0 +1,169 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::mangle::mangle_scalar; +use crate::ed25519::secret::ExpandedSecretKey; +use crate::ed25519::signature::Signature; +use crate::ed25519::Hasher512; +use crate::traits::VerifyingKeyTrait; +use crate::KeyPairError; +use curve25519_dalek::constants; +use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; +use curve25519_dalek::scalar::Scalar; +use std::fmt; +use std::marker::PhantomData; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; +use tw_misc::try_or_false; + +/// Represents an `ed25519` public key. +#[derive(Clone, PartialEq)] +pub struct PublicKey { + compressed: CompressedEdwardsY, + point: EdwardsPoint, + _phantom: PhantomData, +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PublicKey") + .field("compressed", &self.as_slice()) + .finish() + } +} + +/// cbindgen:ignore +impl PublicKey { + /// The number of bytes in a serialized public key. + pub const LEN: usize = H256::len(); + + /// Creates a public key with the given [`ExpandedSecretKey`]. + pub(crate) fn with_expanded_secret(secret: &ExpandedSecretKey) -> Self { + let bits = secret.key.to_bytes(); + + PublicKey::mangle_scalar_bits_and_multiply_by_basepoint_to_produce_public_key(bits) + } + + /// Creates a public key with the given [`ExpandedSecretKey`]. + /// + /// This function is useful when the given secret is mangled already. + /// For example, created via [`ExpandedSecretKey::with_extended_secret`]. + pub(crate) fn with_expanded_secret_no_mangle(secret: &ExpandedSecretKey) -> Self { + let bits = secret.key.to_bytes(); + + PublicKey::multiply_by_basepoint_to_produce_public_key(bits) + } + + /// Creates a public key with the given [`EdwardsPoint`]. + pub(crate) fn with_edwards_point(point: EdwardsPoint) -> Self { + let compressed = point.compress(); + PublicKey { + compressed, + point, + _phantom: PhantomData, + } + } + + /// Returns the raw data of the public key (32 bytes). + pub fn to_bytes(&self) -> H256 { + H256::from(self.compressed.to_bytes()) + } + + /// Returns the raw data of the data of the public key. + pub fn as_slice(&self) -> &[u8] { + self.compressed.as_bytes() + } + + /// Returns a reference to the [`EdwardsPoint`]. + pub(crate) fn edwards_point(&self) -> &EdwardsPoint { + &self.point + } + + /// Internal utility function for mangling the bits of a (formerly + /// mathematically well-defined) "scalar" and multiplying it to produce a + /// public key. + /// + /// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/public.rs#L147-L161 + fn mangle_scalar_bits_and_multiply_by_basepoint_to_produce_public_key( + mut bits: [u8; 32], + ) -> PublicKey { + mangle_scalar(&mut bits); + + Self::multiply_by_basepoint_to_produce_public_key(bits) + } + + /// Internal utility function for multiplying the given bits to produce a public key. + /// + /// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/public.rs#L157-L160 + fn multiply_by_basepoint_to_produce_public_key(bits: [u8; 32]) -> PublicKey { + let point = &Scalar::from_bits(bits) * &constants::ED25519_BASEPOINT_TABLE; + PublicKey::with_edwards_point(point) + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningMessage = Vec; + type VerifySignature = Signature; + + /// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/public.rs#L220-L319 + #[allow(non_snake_case)] + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + let mut h = H::new(); + let minus_A: EdwardsPoint = -self.point; + + let signature_R = try_or_false!(signature.R.decompress()); + + // Logical OR is fine here as we're not trying to be constant time. + if signature_R.is_small_order() || self.point.is_small_order() { + return false; + } + + h.update(signature.R.as_bytes()); + h.update(self.as_slice()); + h.update(&message); + + let k = Scalar::from_hash(h); + let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s); + + R == signature_R + } +} + +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } +} + +impl<'a, H: Hasher512> TryFrom<&'a [u8]> for PublicKey { + type Error = KeyPairError; + + /// Inspired by: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/public.rs#L92-L145 + fn try_from(pubkey: &'a [u8]) -> Result { + let pubkey = H256::try_from(pubkey).map_err(|_| KeyPairError::InvalidPublicKey)?; + + let compressed = CompressedEdwardsY(pubkey.take()); + let point = compressed + .decompress() + .ok_or(KeyPairError::InvalidPublicKey)?; + + Ok(PublicKey { + compressed, + point, + _phantom: PhantomData, + }) + } +} + +impl<'a, H: Hasher512> TryFrom<&'a str> for PublicKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?; + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/ed25519/secret.rs b/rust/tw_keypair/src/ed25519/secret.rs new file mode 100644 index 00000000000..ed39c3e7a34 --- /dev/null +++ b/rust/tw_keypair/src/ed25519/secret.rs @@ -0,0 +1,129 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ed25519::mangle::mangle_scalar; +use crate::ed25519::signature::Signature; +use crate::ed25519::Hasher512; +use crate::KeyPairResult; +use curve25519_dalek::constants; +use curve25519_dalek::scalar::Scalar; +use std::marker::PhantomData; +use std::ops::DerefMut; +use tw_hash::{H256, H512}; +use zeroize::ZeroizeOnDrop; + +/// Represents an "expanded" secret key produced by using a hash function +/// with 512-bits output to digest a `PrivateKey`. +/// +/// Represents [ed25519_extsk](https://github.com/trustwallet/wallet-core/blob/423f0e34725f69c0a9d535e1a32534c99682edea/trezor-crypto/crypto/ed25519-donna/ed25519.c#L23-L32). +#[derive(ZeroizeOnDrop)] +pub(crate) struct ExpandedSecretKey { + /// 32 byte scalar. Represents `extsk[0..32]`. + pub key: Scalar, + /// 32 byte nonce. Represents `extsk[32..64]`. + pub nonce: H256, + _phantom: PhantomData, +} + +impl ExpandedSecretKey { + /// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/secret.rs#L246-L282 + /// Ported: https://github.com/trustwallet/wallet-core/blob/423f0e34725f69c0a9d535e1a32534c99682edea/trezor-crypto/crypto/ed25519-donna/ed25519.c#L23-L32 + pub(crate) fn with_secret(secret: H256) -> Self { + let mut h = H::new(); + let mut hash = H512::default(); + + h.update(secret.as_slice()); + hash.copy_from_slice(h.finalize().as_slice()); + + let (mut lower, upper): (H256, H256) = hash.split(); + mangle_scalar(lower.deref_mut()); + + ExpandedSecretKey { + key: Scalar::from_bits(lower.take()), + nonce: upper, + _phantom: PhantomData, + } + } + + /// Ported: https://github.com/trustwallet/wallet-core/blob/423f0e34725f69c0a9d535e1a32534c99682edea/trezor-crypto/crypto/ed25519-donna/ed25519.c#L93-L94C30 + /// + /// Here we use `Scalar::from_bytes_mod_order` instead of `Scalar::from_bits` + /// because `Scalar::from_bits` modifies the last 32th byte in some cases. + /// Although, `Scalar::from_bytes_mod_order` changes the given `secret` significantly, + /// but the result signature seems to be correct. + /// Unfortunately, there are no public functions to create `Scalar` without mangling the secret. + /// + /// TODO make sure if this is the right way to create an extended secret key (extsk). + pub(crate) fn with_extended_secret(secret: H256, extension: H256) -> Self { + let key = Scalar::from_bytes_mod_order(secret.take()); + ExpandedSecretKey { + key, + nonce: extension, + _phantom: PhantomData, + } + } + + /// Signs a message with this `ExpandedSecretKey`. + /// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/secret.rs#L389-L412 + /// Ported: https://github.com/trustwallet/wallet-core/blob/423f0e34725f69c0a9d535e1a32534c99682edea/trezor-crypto/crypto/ed25519-donna/ed25519.c#L97-L130 + #[allow(non_snake_case)] + pub(crate) fn sign_with_pubkey( + &self, + pubkey: H256, + message: &[u8], + ) -> KeyPairResult { + let mut h = H::new(); + + h.update(self.nonce.as_slice()); + h.update(message); + + let r = Scalar::from_hash(h); + let R = (&r * &constants::ED25519_BASEPOINT_TABLE).compress(); + + h = H::new(); + h.update(R.as_bytes()); + h.update(pubkey); + h.update(message); + + let k = Scalar::from_hash(h); + let s = k * self.key + r; + + Ok(Signature { R, s }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sha2::Sha512; + use tw_encoding::hex; + + #[test] + fn test_ed25519_expanded_secret_from_extended_key() { + let secret = H256::from("b52a1a9f4ae3bff2d16a06e144bcdd4147a35aa9843cfd0edf458afdf5ba1a3b"); + let nonce = H256::from("b33b86344897745b35bb3ef8ca8fe8a3758bd31a537280a6b8c60e42a1f3a00d"); + + let secret_key: ExpandedSecretKey = + ExpandedSecretKey::with_extended_secret(secret, nonce); + + // In `trezor-crypto` implementation, `a = mod(secret)` has the following: + // https://github.com/trustwallet/wallet-core/blob/423f0e34725f69c0a9d535e1a32534c99682edea/trezor-crypto/crypto/ed25519-donna/ed25519.c#L109-L111 + let expected_mod_a = + H256::from("eeae3808eee7222aee44f9013eaa33100347a31aa512f234eff05d24627fbd2e"); + // On the other hand, [`ExpandedSecretKey::key`] represents the same `a` value. + // But it differs from that - `eeae3888fbb988ea4e941ff8a8ce400347a35aa9843cfd0edf458afdf5ba1a0b`. + // TODO probably, `secret_key.key.to_bytes()` should be the same as `expected_mod_a.take()`. + assert_ne!(secret_key.key.to_bytes(), expected_mod_a.take()); + + let public = H256::from("7950119e049a53a9eaa6ecfbfe354337287056ba0ea054130c1b0c97f1b69697"); + let message = hex::decode("f0").unwrap(); + + // Anyway, the result signature has an expected value. + let sign = secret_key.sign_with_pubkey(public, &message).unwrap(); + let expected = H512::from("ed55bce14a845a275e7a3a7242420ed1eeaba79dc3141bebf42ca0d12169e209a6e56b6981a336f711ae3aaea8d063b72b0e79a8808311d08cb42cabfdd0450d"); + assert_eq!(sign.to_bytes(), expected); + } +} diff --git a/rust/tw_keypair/src/ed25519/signature.rs b/rust/tw_keypair/src/ed25519/signature.rs new file mode 100644 index 00000000000..cb45fcce1fc --- /dev/null +++ b/rust/tw_keypair/src/ed25519/signature.rs @@ -0,0 +1,96 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{KeyPairError, KeyPairResult}; +use curve25519_dalek::edwards::CompressedEdwardsY; +use curve25519_dalek::scalar::Scalar; +use tw_hash::{concat, H256, H512}; +use tw_misc::traits::ToBytesVec; + +/// Represents an `ed25519` signature. +/// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/signature.rs#L22-L53 +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct Signature { + /// `R` is an `EdwardsPoint`, formed by using an hash function with + /// 512-bits output to produce the digest of: + /// + /// - the nonce half of the `ExpandedSecretKey`, and + /// - the message to be signed. + /// + /// This digest is then interpreted as a `Scalar` and reduced into an + /// element in ℤ/lℤ. The scalar is then multiplied by the distinguished + /// basepoint to produce `R`, and `EdwardsPoint`. + pub(crate) R: CompressedEdwardsY, + + /// `s` is a `Scalar`, formed by using an hash function with 512-bits output + /// to produce the digest of: + /// + /// - the `r` portion of this `Signature`, + /// - the `PublicKey` which should be used to verify this `Signature`, and + /// - the message to be signed. + /// + /// This digest is then interpreted as a `Scalar` and reduced into an + /// element in ℤ/lℤ. + pub(crate) s: Scalar, +} + +impl Signature { + /// Returns the signature data (64 bytes). + pub fn to_bytes(&self) -> H512 { + let left = H256::from(self.R.to_bytes()); + let right = H256::from(self.s.to_bytes()); + concat(left, right) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + self.to_bytes().into_vec() + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = KeyPairError; + + /// Construct a `Signature` from a slice of bytes. + /// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/signature.rs#L115-L190 + fn try_from(sign: &'a [u8]) -> Result { + let bytes = H512::try_from(sign).map_err(|_| KeyPairError::InvalidSignature)?; + + let (lower, upper): (H256, H256) = bytes.split(); + + let s = get_scalar(upper)?; + Ok(Signature { + R: CompressedEdwardsY(lower.take()), + s, + }) + } +} + +/// Source: https://github.com/dalek-cryptography/ed25519-dalek/blob/1.0.1/src/signature.rs#L83-L102 +fn get_scalar(bytes: H256) -> KeyPairResult { + /// Equals to 240 decimal. + const SIGNIFICANT_BITS_MASK: u8 = 0b1111_0000; + + // Since this is only used in signature deserialisation (i.e. upon + // verification), we can do a "succeed fast" trick by checking that the most + // significant 4 bits are unset. If they are unset, we can succeed fast + // because we are guaranteed that the scalar is fully reduced. However, if + // the 4th most significant bit is set, we must do the full reduction check, + // as the order of the basepoint is roughly a 2^(252.5) bit number. + // + // This succeed-fast trick should succeed for roughly half of all scalars. + let last_byte = bytes.last().expect("H256 is exactly 32 length"); + if last_byte & SIGNIFICANT_BITS_MASK == 0 { + return Ok(Scalar::from_bits(bytes.take())); + } + + match Scalar::from_canonical_bytes(bytes.take()) { + Some(x) => Ok(x), + None => Err(KeyPairError::InvalidSignature), + } +} diff --git a/rust/tw_keypair/src/ffi/mod.rs b/rust/tw_keypair/src/ffi/mod.rs index 9a641758528..15cac0a8cf7 100644 --- a/rust/tw_keypair/src/ffi/mod.rs +++ b/rust/tw_keypair/src/ffi/mod.rs @@ -5,3 +5,5 @@ // file LICENSE at the root of the source code distribution tree. pub mod asn; +pub mod privkey; +pub mod pubkey; diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs new file mode 100644 index 00000000000..30f68811458 --- /dev/null +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -0,0 +1,133 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::ffi::pubkey::TWPublicKey; +use crate::tw::{Curve, PrivateKey, PublicKeyType}; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; + +pub struct TWPrivateKey(pub(crate) PrivateKey); + +impl RawPtrTrait for TWPrivateKey {} + +/// Create a private key with the given block of data. +/// +/// \param input *non-null* byte array. +/// \param input_len the length of the `input` array. +/// \note Should be deleted with \tw_private_key_delete. +/// \return Nullable pointer to Private Key. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_create_with_data( + input: *const u8, + input_len: usize, +) -> *mut TWPrivateKey { + let bytes_ref = CByteArrayRef::new(input, input_len); + let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); + + PrivateKey::new(bytes) + .map(|private| TWPrivateKey(private).into_ptr()) + // Return null if the private key is invalid. + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Delete the given private key. +/// +/// \param key *non-null* pointer to private key. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_delete(key: *mut TWPrivateKey) { + // Take the ownership back to rust and drop the owner. + let _ = TWPrivateKey::from_ptr(key); +} + +/// Determines if the given private key is valid or not. +/// +/// \param key *non-null* byte array. +/// \param key_len the length of the `key` array. +/// \param curve Eliptic curve of the private key. +/// \return true if the private key is valid, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_is_valid( + key: *const u8, + key_len: usize, + curve: u32, +) -> bool { + let curve = try_or_false!(Curve::from_raw(curve)); + let priv_key_slice = try_or_false!(CByteArrayRef::new(key, key_len).as_slice()); + PrivateKey::is_valid(priv_key_slice, curve) +} + +/// Signs a digest using ECDSA and given curve. +/// +/// \param key *non-null* pointer to a Private key +/// \param message *non-null* byte array. +/// \param message_len the length of the `input` array. +/// \param curve Eliptic curve. +/// \return Signature as a C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_sign( + key: *mut TWPrivateKey, + message: *const u8, + message_len: usize, + curve: u32, +) -> CByteArray { + let curve = try_or_else!(Curve::from_raw(curve), CByteArray::default); + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); + let message_to_sign = try_or_else!( + CByteArrayRef::new(message, message_len).as_slice(), + CByteArray::default + ); + + // Return an empty signature if an error occurs. + let sig = private.0.sign(message_to_sign, curve).unwrap_or_default(); + CByteArray::from(sig) +} + +/// Returns the public key associated with the given pubkeyType and privateKey +/// +/// \param key *non-null* pointer to the private key. +/// \param pubkey_type type of the public key to return. +/// \return *non-null* pointer to the corresponding public key. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_get_public_key_by_type( + key: *mut TWPrivateKey, + pubkey_type: u32, +) -> *mut TWPublicKey { + let ty = try_or_else!(PublicKeyType::from_raw(pubkey_type), std::ptr::null_mut); + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), std::ptr::null_mut); + private + .0 + .get_public_key_by_type(ty) + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +// #[no_mangle] +// pub unsafe extern "C" fn tw_private_key_get_shared_key( +// key: *mut TWPrivateKey, +// hash: *const u8, +// hash_len: usize, +// curve: u32, +// ) -> CByteArray { +// let curve = match Curve::from_raw(curve) { +// Some(curve) => curve, +// None => return CByteArray::empty(), +// }; +// let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::empty); +// +// let hash = CByteArrayRef::new(hash, hash_len); +// let hash_to_sign = match hash.as_slice() { +// Some(hash) => hash, +// None => return CByteArray::empty(), +// }; +// +// // Return an empty signature if an error occurs. +// let sig = private.sign(hash_to_sign, curve).unwrap_or_default(); +// CByteArray::from(sig) +// } diff --git a/rust/tw_keypair/src/ffi/pubkey.rs b/rust/tw_keypair/src/ffi/pubkey.rs new file mode 100644 index 00000000000..e1c86476b07 --- /dev/null +++ b/rust/tw_keypair/src/ffi/pubkey.rs @@ -0,0 +1,104 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::tw::{PublicKey, PublicKeyType}; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; + +pub struct TWPublicKey(pub(crate) PublicKey); + +impl AsRef for TWPublicKey { + fn as_ref(&self) -> &PublicKey { + &self.0 + } +} + +impl RawPtrTrait for TWPublicKey {} + +/// Create a public key with the given block of data and specified public key type. +/// +/// \param input *non-null* byte array. +/// \param input_len the length of the `input` array. +/// \param ty type of the public key. +/// \note Should be deleted with \tw_public_key_delete. +/// \return Nullable pointer to the public key. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_create_with_data( + input: *const u8, + input_len: usize, + ty: u32, +) -> *mut TWPublicKey { + let bytes_ref = CByteArrayRef::new(input, input_len); + let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); + let ty = try_or_else!(PublicKeyType::from_raw(ty), std::ptr::null_mut); + PublicKey::new(bytes, ty) + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Delete the given public key. +/// +/// \param key *non-null* pointer to public key. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_delete(key: *mut TWPublicKey) { + // Take the ownership back to rust and drop the owner. + let _ = TWPublicKey::from_ptr(key); +} + +/// Verify the validity of a signature and a message using the given public key. +/// +/// \param key *non-null* pointer to a Public key. +/// \param sig *non-null* pointer to a block of data corresponding to the signature. +/// \param sig_len the length of the `sig` array. +/// \param msg *non-null* pointer to a block of data corresponding to the message. +/// \param msg_len the length of the `msg` array. +/// \return true if the signature and the message belongs to the given public key, otherwise false. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_verify( + key: *mut TWPublicKey, + sig: *const u8, + sig_len: usize, + msg: *const u8, + msg_len: usize, +) -> bool { + let public = try_or_false!(TWPublicKey::from_ptr_as_ref(key)); + let sig = try_or_false!(CByteArrayRef::new(sig, sig_len).as_slice()); + let msg = try_or_false!(CByteArrayRef::new(msg, msg_len).as_slice()); + public.0.verify(sig, msg) +} + +/// Returns the raw data of a given public-key. +/// +/// \param key *non-null* pointer to a public key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_data(key: *mut TWPublicKey) -> CByteArray { + let public = try_or_else!(TWPublicKey::from_ptr_as_ref(key), CByteArray::default); + CByteArray::from(public.0.to_bytes()) +} + +// #[no_mangle] +// pub unsafe extern "C" fn tw_public_key_is_valid( +// pubkey: *const u8, +// pubkey_len: usize, +// pubkey_type: u32, +// ) -> bool { +// let ty = match TWPublicKeyType::from_raw(pubkey_type) { +// Some(ty) => ty, +// None => return false, +// }; +// +// let pubkey_slice = match CByteArrayRef::new(pubkey, pubkey_len).as_slice() { +// Some(pubkey) => pubkey, +// None => return false, +// }; +// +// TWPublicKey::is_valid(pubkey_slice, ty) +// } diff --git a/rust/tw_keypair/src/lib.rs b/rust/tw_keypair/src/lib.rs index bf7edad42ab..945222877e5 100644 --- a/rust/tw_keypair/src/lib.rs +++ b/rust/tw_keypair/src/lib.rs @@ -4,12 +4,64 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +//! `tw_keypair` crate defines the keypairs, private and public keys that are used to sign messages, +//! verify signatures and more. +//! +//! # Usage - Generic TW solution +//! +//! If you plan to work with different curves in the same app by using the same private key, +//! consider using [`tw::PrivateKey`], [`tw::PublicKey`], [`tw::KeyPair`] (TODO). +//! +//! ```rust,ignore +//! use tw_keypair::{tw::PrivateKey, Curve}; +//! +//! let private = PrivateKey::try_from(YOUR_SECRET_BYTES).unwrap(); +//! +//! // Sign an ETH transaction hash with the `private` key. +//! let eth_signature = private.sign(ETH_TX_HASH, Curve::Secp256k1).unwrap(); +//! assert_eq(eth_signature.len(), 65); +//! +//! // Sign a SUI transaction hash with the same `private` key, but different `curve`. +//! let sui_signature = private.sign(SUI_TX_HASH, Curve::Ed25519).unwrap(); +//! ``` +//! +//! # Usage - Specific curve +//! +//! If you plan to work with only one curve, consider using a specific curve implementation. +//! For example, if you work with ETH, therefore the`secp256k1` curve: +//! +//! ```rust,ignore +//! use tw_keypair::secp256k1::KeyPair; +//! +//! let keypair = KeyPair::try_from(YOUR_SECRET_BYTES).unwrap(); +//! +//! // Sign an ETH transaction hash. +//! // [`tw_keypair::secp256k1::KeyPair::sign`] returns a [`tw_keypair::secp256k1::Signature`]. +//! let eth_signature = private.sign(ETH_TX_HASH, Curve::Secp256k1).unwrap(); +//! +//! assert_eq(eth_signature.r, H256::from(EXPECTED_R_HEX)); +//! assert_eq(eth_signature.s, H256::from(EXPECTED_S_HEX)); +//! assert_eq(eth_signature.v, H256::from(EXPECTED_V)); +//! ``` + pub mod ecdsa; +pub mod ed25519; pub mod ffi; +pub mod starkex; +pub mod traits; +pub mod tw; + +#[cfg(feature = "test-utils")] +pub mod test_utils; pub type KeyPairResult = Result; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum KeyPairError { + InvalidSecretKey, + InvalidPublicKey, InvalidSignature, + InvalidSignMessage, + SignatureVerifyError, + SigningError, } diff --git a/rust/tw_keypair/src/starkex/keypair.rs b/rust/tw_keypair/src/starkex/keypair.rs new file mode 100644 index 00000000000..b827bdb80db --- /dev/null +++ b/rust/tw_keypair/src/starkex/keypair.rs @@ -0,0 +1,69 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::starkex::private::PrivateKey; +use crate::starkex::public::PublicKey; +use crate::starkex::signature::Signature; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use tw_encoding::hex; +use zeroize::Zeroizing; + +/// Represents a pair of private and public keys that are used in `starknet` context. +pub struct KeyPair { + private: PrivateKey, + public: PublicKey, +} + +impl KeyPairTrait for KeyPair { + type Private = PrivateKey; + type Public = PublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for KeyPair { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + self.private.sign(message) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + self.public.verify(signature, message) + } +} + +impl<'a> TryFrom<&'a [u8]> for KeyPair { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = PrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(KeyPair { private, public }) + } +} + +impl<'a> TryFrom<&'a str> for KeyPair { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} diff --git a/rust/tw_keypair/src/starkex/mod.rs b/rust/tw_keypair/src/starkex/mod.rs new file mode 100644 index 00000000000..35db9f4e98c --- /dev/null +++ b/rust/tw_keypair/src/starkex/mod.rs @@ -0,0 +1,124 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod keypair; +mod private; +mod public; +mod signature; + +pub use keypair::KeyPair; +pub use private::PrivateKey; +pub use public::PublicKey; +pub use signature::Signature; +use starknet_ff::FieldElement; + +fn field_element_from_bytes_be(bytes: &[u8]) -> Result { + const FIELD_ELEMENT_LEN: usize = 32; + + if bytes.len() > FIELD_ELEMENT_LEN { + return Err(()); + } + let mut buffer = [0u8; FIELD_ELEMENT_LEN]; + buffer[(FIELD_ELEMENT_LEN - bytes.len())..].copy_from_slice(bytes); + FieldElement::from_bytes_be(&buffer).map_err(|_| ()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; + use tw_encoding::hex; + use tw_hash::{H256, H512}; + use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + + #[test] + fn test_key_pair_sign_verify() { + let keypair = + KeyPair::try_from("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79") + .unwrap(); + + let hash_to_sign = + hex::decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") + .unwrap(); + let actual = keypair.sign(hash_to_sign.clone()).unwrap(); + + let expected = H512::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); + assert_eq!(actual.to_vec(), expected.into_vec()); + + assert!(keypair.verify(actual, hash_to_sign)); + } + + #[test] + fn test_key_pair_get_private_public() { + let privkey_bytes = + hex::decode("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe") + .unwrap(); + let pubkey_bytes = + hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd") + .unwrap(); + + let keypair = KeyPair::try_from(privkey_bytes.as_slice()).unwrap(); + assert_eq!( + keypair.private().to_zeroizing_vec().as_slice(), + privkey_bytes + ); + assert_eq!(keypair.public().to_vec(), pubkey_bytes); + } + + #[test] + fn test_field_element_from_bytes_be_invalid() { + let invalid_element = + hex::decode("00058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe") + .unwrap(); + field_element_from_bytes_be(&invalid_element).unwrap_err(); + } + + #[test] + fn test_private_key_to_from_bytes() { + let bytes = hex::decode("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe") + .unwrap(); + let private = PrivateKey::try_from(bytes.as_slice()).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), bytes); + } + + #[test] + fn test_public_key_to_from_bytes() { + let bytes = hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd") + .unwrap(); + let public = PublicKey::try_from(bytes.as_slice()).unwrap(); + assert_eq!(public.to_vec(), bytes); + } + + #[test] + fn test_signature_to_from_bytes() { + let bytes = hex::decode("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a").unwrap(); + let sign = Signature::try_from(bytes.as_slice()).unwrap(); + assert_eq!(sign.to_vec(), bytes); + + assert_eq!( + sign.r(), + H256::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f") + ); + assert_eq!( + sign.s(), + H256::from("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + ); + } + + // https://github.com/xJonathanLEI/starknet-rs/issues/365 + #[test] + fn test_verify_panic() { + let public = + PublicKey::try_from("03ee9bffffffffff26ffffffff60ffffffffffffffffffffffffffff004accff") + .unwrap(); + let hash = hex::decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") + .unwrap(); + let signature_bytes = hex::decode("06ffffffffffffffffffffffffffffffffffffffffffff06ffff5dffff9bffdf00ffffff9b9b9b9b9b9b9b9bbb9bff9b9bbb9bff9b9b9b9b9b9b9b9b9b9b9b33").unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + + assert!(!public.verify(signature, hash)); + } +} diff --git a/rust/tw_keypair/src/starkex/private.rs b/rust/tw_keypair/src/starkex/private.rs new file mode 100644 index 00000000000..973d2c06699 --- /dev/null +++ b/rust/tw_keypair/src/starkex/private.rs @@ -0,0 +1,124 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::starkex::field_element_from_bytes_be; +use crate::starkex::public::PublicKey; +use crate::starkex::signature::Signature; +use crate::traits::SigningKeyTrait; +use crate::{KeyPairError, KeyPairResult}; +use starknet_crypto::{ + get_public_key, rfc6979_generate_k, sign, SignError, Signature as EcdsaSignature, +}; +use starknet_ff::FieldElement; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::Zeroizing; + +/// The maximum number of attempts to sign a message. +/// As the number is coming from `rfc6979_generate_k` so the probability is lower. +const SIGN_RETRIES: usize = 5; + +/// Represents a private key that is used in `starknet` context. +pub struct PrivateKey { + secret: FieldElement, +} + +impl PrivateKey { + /// Returns an associated `starknet` public key. + pub fn public(&self) -> PublicKey { + let public_scalar = get_public_key(&self.secret); + PublicKey::from_scalar(public_scalar) + } +} + +impl SigningKeyTrait for PrivateKey { + type SigningMessage = Vec; + type Signature = Signature; + + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + let hash_to_sign = + field_element_from_bytes_be(&message).map_err(|_| KeyPairError::InvalidSignMessage)?; + let signature = + ecdsa_sign(&self.secret, &hash_to_sign).map_err(|_| KeyPairError::SigningError)?; + Ok(Signature::new(signature)) + } +} + +impl<'a> TryFrom<&'a [u8]> for PrivateKey { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let bytes = H256::try_from(bytes).map_err(|_| KeyPairError::InvalidSecretKey)?; + let secret = FieldElement::from_bytes_be(&bytes.take()) + .map_err(|_| KeyPairError::InvalidSecretKey)?; + Ok(PrivateKey { secret }) + } +} + +impl<'a> TryFrom<&'a str> for PrivateKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = Zeroizing::new(hex::decode(hex).map_err(|_| KeyPairError::InvalidSecretKey)?); + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let secret = Zeroizing::new(self.secret.to_bytes_be()); + Zeroizing::new(secret.to_vec()) + } +} + +/// `starknet-core` depends on an out-dated `starknet-crypto` crate. +/// We need to reimplement the same but using the latest `starknet-crypto` version. +/// https://github.com/xJonathanLEI/starknet-rs/blob/0c78b365c2a7a7d4138553cba42fa69d695aa73d/starknet-core/src/crypto.rs#L34-L59 +pub fn ecdsa_sign( + private_key: &FieldElement, + message_hash: &FieldElement, +) -> Result { + // Seed-retry logic ported from `cairo-lang` + let mut seed = None; + for _ in 0..SIGN_RETRIES { + let k = rfc6979_generate_k(message_hash, private_key, seed.as_ref()); + + match sign(private_key, message_hash, &k) { + Ok(sig) => { + return Ok(EcdsaSignature { r: sig.r, s: sig.s }); + }, + Err(SignError::InvalidMessageHash) => return Err(SignError::InvalidMessageHash), + Err(SignError::InvalidK) => { + // Bump seed and retry + seed = match seed { + Some(prev_seed) => Some(prev_seed + FieldElement::ONE), + None => Some(FieldElement::ONE), + }; + }, + }; + } + Err(SignError::InvalidMessageHash) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_starknet_sign_invalid_k() { + let private = PrivateKey { + secret: field_element_from_bytes_be(&[0; 32]).unwrap(), + }; + let hash = vec![0; 32]; + + // Return value does not implement Debug, so we cannot unwrap here. + match private.sign(hash) { + Ok(_) => panic!("Retry limit expected"), + Err(err) => assert_eq!(err, KeyPairError::SigningError), + } + } +} diff --git a/rust/tw_keypair/src/starkex/public.rs b/rust/tw_keypair/src/starkex/public.rs new file mode 100644 index 00000000000..16dad099952 --- /dev/null +++ b/rust/tw_keypair/src/starkex/public.rs @@ -0,0 +1,66 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::starkex::field_element_from_bytes_be; +use crate::starkex::signature::Signature; +use crate::traits::VerifyingKeyTrait; +use crate::KeyPairError; +use starknet_crypto::verify as ecdsa_verify; +use starknet_ff::FieldElement; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; +use tw_misc::try_or_false; + +#[derive(Clone)] +pub struct PublicKey { + public: FieldElement, +} + +impl PublicKey { + /// Creates a public key from the given [`FieldElement`]. + pub(crate) fn from_scalar(public: FieldElement) -> PublicKey { + PublicKey { public } + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningMessage = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + let hash = try_or_false!(field_element_from_bytes_be(&message)); + let ecdsa_signature = signature.inner(); + ecdsa_verify(&self.public, &hash, &ecdsa_signature.r, &ecdsa_signature.s) + .unwrap_or_default() + } +} + +impl<'a> TryFrom<&'a [u8]> for PublicKey { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let bytes = H256::try_from(bytes).map_err(|_| KeyPairError::InvalidPublicKey)?; + let public_scalar = FieldElement::from_bytes_be(&bytes.take()) + .map_err(|_| KeyPairError::InvalidPublicKey)?; + Ok(PublicKey::from_scalar(public_scalar)) + } +} + +impl<'a> TryFrom<&'a str> for PublicKey { + type Error = KeyPairError; + + fn try_from(hex: &'a str) -> Result { + let bytes = hex::decode(hex).map_err(|_| KeyPairError::InvalidPublicKey)?; + Self::try_from(bytes.as_slice()) + } +} + +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.public.to_bytes_be().to_vec() + } +} diff --git a/rust/tw_keypair/src/starkex/signature.rs b/rust/tw_keypair/src/starkex/signature.rs new file mode 100644 index 00000000000..9ca0de029b6 --- /dev/null +++ b/rust/tw_keypair/src/starkex/signature.rs @@ -0,0 +1,83 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::KeyPairError; +use starknet_ff::FieldElement; +use std::ops::Range; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; + +/// cbindgen:ignore +const R_RANGE: Range = 0..32; +/// cbindgen:ignore +const S_RANGE: Range = 32..64; + +/// Represents a `starknet` signature. +pub struct Signature { + pub(crate) signature: starknet_crypto::Signature, +} + +/// cbindgen:ignore +impl Signature { + /// The number of bytes for a serialized signature representation. + pub const LEN: usize = 64; + + /// Returns the number of bytes for a serialized signature representation. + pub const fn len() -> usize { + Self::LEN + } + + /// Creates a `starknet` signature from the given [`starknet_crypto::Signature`]. + pub(crate) fn new(signature: starknet_crypto::Signature) -> Signature { + Signature { signature } + } + + /// Returns a reference to the inner [`starknet_crypto::Signature`]. + pub(crate) fn inner(&self) -> &starknet_crypto::Signature { + &self.signature + } + + /// Returns an r-coordinate as 32 byte array. + pub fn r(&self) -> H256 { + H256::from(self.signature.r.to_bytes_be()) + } + + /// Returns an s-value as 32 byte array. + pub fn s(&self) -> H256 { + H256::from(self.signature.s.to_bytes_be()) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + let mut to_return = Vec::with_capacity(Signature::len()); + to_return.extend_from_slice(self.r().as_slice()); + to_return.extend_from_slice(self.s().as_slice()); + to_return + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Signature::len() { + return Err(KeyPairError::InvalidSignature); + } + + let r_bytes = H256::try_from(&bytes[R_RANGE]).expect("Expected a valid r range"); + let s_bytes = H256::try_from(&bytes[S_RANGE]).expect("Expected a valid s range"); + + let r = FieldElement::from_bytes_be(&r_bytes.take()) + .map_err(|_| KeyPairError::InvalidSignature)?; + let s = FieldElement::from_bytes_be(&s_bytes.take()) + .map_err(|_| KeyPairError::InvalidSignature)?; + + Ok(Signature { + signature: starknet_crypto::Signature { r, s }, + }) + } +} diff --git a/rust/tw_keypair/src/test_utils/mod.rs b/rust/tw_keypair/src/test_utils/mod.rs new file mode 100644 index 00000000000..2e2d49008ed --- /dev/null +++ b/rust/tw_keypair/src/test_utils/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod tw_private_key_helper; +pub mod tw_public_key_helper; diff --git a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs new file mode 100644 index 00000000000..c8cc3795578 --- /dev/null +++ b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::privkey::{tw_private_key_create_with_data, tw_private_key_delete, TWPrivateKey}; +use tw_encoding::hex; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::Data; + +pub struct TWPrivateKeyHelper { + ptr: *mut TWPrivateKey, +} + +impl TWPrivateKeyHelper { + pub fn with_bytes>(bytes: T) -> TWPrivateKeyHelper { + let priv_key_raw = CByteArray::from(bytes.into()); + let ptr = + unsafe { tw_private_key_create_with_data(priv_key_raw.data(), priv_key_raw.size()) }; + TWPrivateKeyHelper { ptr } + } + + pub fn with_hex(hex: &str) -> TWPrivateKeyHelper { + let priv_key_data = hex::decode(hex).unwrap(); + TWPrivateKeyHelper::with_bytes(priv_key_data) + } + + pub fn ptr(&self) -> *mut TWPrivateKey { + self.ptr + } + + pub fn is_null(&self) -> bool { + self.ptr.is_null() + } +} + +impl Drop for TWPrivateKeyHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_private_key_delete(self.ptr) }; + } +} diff --git a/rust/tw_keypair/src/test_utils/tw_public_key_helper.rs b/rust/tw_keypair/src/test_utils/tw_public_key_helper.rs new file mode 100644 index 00000000000..daecadc61aa --- /dev/null +++ b/rust/tw_keypair/src/test_utils/tw_public_key_helper.rs @@ -0,0 +1,51 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::pubkey::{tw_public_key_create_with_data, tw_public_key_delete, TWPublicKey}; +use crate::tw::PublicKeyType; +use tw_encoding::hex; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::Data; + +pub struct TWPublicKeyHelper { + ptr: *mut TWPublicKey, +} + +impl TWPublicKeyHelper { + pub fn with_bytes>(bytes: T, ty: PublicKeyType) -> TWPublicKeyHelper { + let public_key_raw = CByteArray::from(bytes.into()); + let ptr = unsafe { + tw_public_key_create_with_data(public_key_raw.data(), public_key_raw.size(), ty as u32) + }; + TWPublicKeyHelper { ptr } + } + + pub fn with_hex(hex: &str, ty: PublicKeyType) -> TWPublicKeyHelper { + let priv_key_data = hex::decode(hex).unwrap(); + TWPublicKeyHelper::with_bytes(priv_key_data, ty) + } + + pub fn wrap(ptr: *mut TWPublicKey) -> TWPublicKeyHelper { + TWPublicKeyHelper { ptr } + } + + pub fn ptr(&self) -> *mut TWPublicKey { + self.ptr + } + + pub fn is_null(&self) -> bool { + self.ptr.is_null() + } +} + +impl Drop for TWPublicKeyHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_public_key_delete(self.ptr) }; + } +} diff --git a/rust/tw_keypair/src/traits.rs b/rust/tw_keypair/src/traits.rs new file mode 100644 index 00000000000..7b32076f7fe --- /dev/null +++ b/rust/tw_keypair/src/traits.rs @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::KeyPairResult; +use tw_misc::traits::{FromSlice, ToBytesVec, ToBytesZeroizing}; + +pub trait KeyPairTrait: FromSlice + SigningKeyTrait + VerifyingKeyTrait { + type Private: FromSlice + ToBytesZeroizing; + type Public: FromSlice + ToBytesVec; + + /// Returns the reference to the public key. + fn public(&self) -> &Self::Public; + + /// Returns the reference to the private key. + fn private(&self) -> &Self::Private; +} + +pub trait SigningKeyTrait { + type SigningMessage: FromSlice; + type Signature: ToBytesVec; + + /// Signs the given `hash` using the private key. + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult; +} + +pub trait VerifyingKeyTrait { + type SigningMessage: FromSlice; + type VerifySignature: FromSlice; + + /// Verifies if the given `hash` was signed using the private key. + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool; +} diff --git a/rust/tw_keypair/src/tw/mod.rs b/rust/tw_keypair/src/tw/mod.rs new file mode 100644 index 00000000000..114affc4ce6 --- /dev/null +++ b/rust/tw_keypair/src/tw/mod.rs @@ -0,0 +1,131 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; + +mod private; +mod public; + +pub use private::PrivateKey; +pub use public::PublicKey; + +pub type Signature = Vec; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum Curve { + Secp256k1 = 0, + Ed25519 = 1, + Ed25519Blake2bNano = 2, + /// Waves blockchain specific `curve25519`. + Curve25519Waves = 3, + Nist256p1 = 4, + /// Cardano blockchain specific `ed25519` extended key. + Ed25519ExtendedCardano = 5, + Starkex = 6, +} + +impl Curve { + /// Returns `None` if the given curve is not supported in Rust yet. + pub fn from_raw(curve: u32) -> Option { + match curve { + 0 => Some(Curve::Secp256k1), + 1 => Some(Curve::Ed25519), + 2 => Some(Curve::Ed25519Blake2bNano), + 3 => Some(Curve::Curve25519Waves), + 4 => Some(Curve::Nist256p1), + 5 => Some(Curve::Ed25519ExtendedCardano), + 6 => Some(Curve::Starkex), + _ => None, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum PublicKeyType { + #[serde(rename = "secp256k1")] + Secp256k1 = 0, + #[serde(rename = "secp256k1Extended")] + Secp256k1Extended = 1, + #[serde(rename = "nist256p1")] + Nist256p1 = 2, + #[serde(rename = "nist256p1Extended")] + Nist256p1Extended = 3, + #[serde(rename = "ed25519")] + Ed25519 = 4, + #[serde(rename = "ed25519Blake2b")] + Ed25519Blake2b = 5, + /// Waves blockchain specific public key. + #[serde(rename = "curve25519")] + Curve25519Waves = 6, + /// Cardano blockchain specific extended public key. + #[serde(rename = "ed25519Cardano")] + Ed25519ExtendedCardano = 7, + #[serde(rename = "starkex")] + Starkex = 8, +} + +impl PublicKeyType { + /// Returns `None` if the given pubkey type is not supported in Rust yet. + pub fn from_raw(ty: u32) -> Option { + match ty { + 0 => Some(PublicKeyType::Secp256k1), + 1 => Some(PublicKeyType::Secp256k1Extended), + 2 => Some(PublicKeyType::Nist256p1), + 3 => Some(PublicKeyType::Nist256p1Extended), + 4 => Some(PublicKeyType::Ed25519), + 5 => Some(PublicKeyType::Ed25519Blake2b), + 6 => Some(PublicKeyType::Curve25519Waves), + 7 => Some(PublicKeyType::Ed25519ExtendedCardano), + 8 => Some(PublicKeyType::Starkex), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_curve_from_raw() { + let tests = [ + (0, Some(Curve::Secp256k1)), + (1, Some(Curve::Ed25519)), + (2, Some(Curve::Ed25519Blake2bNano)), + (3, Some(Curve::Curve25519Waves)), + (4, Some(Curve::Nist256p1)), + (5, Some(Curve::Ed25519ExtendedCardano)), + (6, Some(Curve::Starkex)), + (7, None), + ]; + for (raw, expected) in tests { + assert_eq!(Curve::from_raw(raw), expected); + } + } + + #[test] + fn test_public_key_type_from_raw() { + let tests = [ + (0, Some(PublicKeyType::Secp256k1)), + (1, Some(PublicKeyType::Secp256k1Extended)), + (2, Some(PublicKeyType::Nist256p1)), + (3, Some(PublicKeyType::Nist256p1Extended)), + (4, Some(PublicKeyType::Ed25519)), + (5, Some(PublicKeyType::Ed25519Blake2b)), + (6, Some(PublicKeyType::Curve25519Waves)), + (7, Some(PublicKeyType::Ed25519ExtendedCardano)), + (8, Some(PublicKeyType::Starkex)), + (9, None), + ]; + for (raw, expected) in tests { + assert_eq!(PublicKeyType::from_raw(raw), expected); + } + } +} diff --git a/rust/tw_keypair/src/tw/private.rs b/rust/tw_keypair/src/tw/private.rs new file mode 100644 index 00000000000..c0e5fb5ae48 --- /dev/null +++ b/rust/tw_keypair/src/tw/private.rs @@ -0,0 +1,195 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::{nist256p1, secp256k1}; +use crate::traits::SigningKeyTrait; +use crate::tw::{Curve, PublicKey, PublicKeyType}; +use crate::{ed25519, starkex, KeyPairError, KeyPairResult}; +use std::ops::Range; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; +use zeroize::ZeroizeOnDrop; + +/// Represents a private key that can be used to sign messages with different elliptic curves. +/// +/// TODO add `secp256k1: Once` for each curve. +#[derive(ZeroizeOnDrop)] +pub struct PrivateKey { + bytes: Vec, +} + +/// cbindgen:ignore +impl PrivateKey { + /// The number of bytes in a private key. + const SIZE: usize = 32; + const CARDANO_SIZE: usize = ed25519::cardano::ExtendedPrivateKey::LEN; + + const KEY_RANGE: Range = 0..Self::SIZE; + const EXTENDED_CARDANO_RANGE: Range = 0..Self::CARDANO_SIZE; + + /// Validates the given `bytes` secret and creates a private key. + pub fn new(bytes: Vec) -> KeyPairResult { + if !Self::is_valid_general(&bytes) { + return Err(KeyPairError::InvalidSecretKey); + } + Ok(PrivateKey { bytes }) + } + + /// Returns the 32 byte array - the essential private key data. + pub fn key(&self) -> H256 { + assert!( + self.bytes.len() >= Self::SIZE, + "'PrivateKey::bytes' has an unexpected length" + ); + H256::try_from(&self.bytes[Self::KEY_RANGE]) + .expect("H256 and KEY_RANGE must be 32 byte length") + } + + /// Returns the 192 byte array - the essential cardano extended private key data. + pub fn extended_cardano_key(&self) -> KeyPairResult<&[u8]> { + if self.bytes.len() != Self::CARDANO_SIZE { + return Err(KeyPairError::InvalidSecretKey); + } + Ok(&self.bytes[Self::EXTENDED_CARDANO_RANGE]) + } + + /// Checks if the given `bytes` secret is valid in general (without a concrete curve). + pub fn is_valid_general(bytes: &[u8]) -> bool { + if bytes.len() != Self::SIZE && bytes.len() != Self::CARDANO_SIZE { + return false; + } + // Check for zero address. + !bytes.iter().all(|byte| *byte == 0) + } + + /// Checks if the given `bytes` secret is valid. + pub fn is_valid(bytes: &[u8], curve: Curve) -> bool { + if !Self::is_valid_general(bytes) { + return false; + } + match curve { + Curve::Secp256k1 => secp256k1::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), + Curve::Ed25519 => { + ed25519::sha512::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok() + }, + Curve::Ed25519Blake2bNano => { + ed25519::blake2b::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok() + }, + Curve::Curve25519Waves => { + ed25519::waves::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok() + }, + Curve::Nist256p1 => nist256p1::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), + Curve::Ed25519ExtendedCardano => { + ed25519::cardano::ExtendedPrivateKey::try_from(&bytes[Self::EXTENDED_CARDANO_RANGE]) + .is_ok() + }, + Curve::Starkex => starkex::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), + } + } + + /// Signs a `message` with using the given elliptic curve. + pub fn sign(&self, message: &[u8], curve: Curve) -> KeyPairResult> { + fn sign_impl(signing_key: Key, message: &[u8]) -> KeyPairResult> + where + Key: SigningKeyTrait, + { + let hash_to_sign = ::SigningMessage::try_from(message) + .map_err(|_| KeyPairError::InvalidSignMessage)?; + signing_key.sign(hash_to_sign).map(|sig| sig.to_vec()) + } + + match curve { + Curve::Secp256k1 => sign_impl(self.to_secp256k1_privkey()?, message), + Curve::Ed25519 => sign_impl(self.to_ed25519()?, message), + Curve::Ed25519Blake2bNano => sign_impl(self.to_ed25519_blake2b()?, message), + Curve::Curve25519Waves => sign_impl(self.to_curve25519_waves()?, message), + Curve::Nist256p1 => sign_impl(self.to_nist256p1_privkey()?, message), + Curve::Ed25519ExtendedCardano => { + sign_impl(self.to_ed25519_extended_cardano()?, message) + }, + Curve::Starkex => sign_impl(self.to_starkex_privkey()?, message), + } + } + + /// Returns the public key associated with the `self` private key and `ty` public key type. + pub fn get_public_key_by_type(&self, ty: PublicKeyType) -> KeyPairResult { + match ty { + PublicKeyType::Secp256k1 => { + let privkey = self.to_secp256k1_privkey()?; + Ok(PublicKey::Secp256k1(privkey.public())) + }, + PublicKeyType::Secp256k1Extended => { + let privkey = self.to_secp256k1_privkey()?; + Ok(PublicKey::Secp256k1Extended(privkey.public())) + }, + PublicKeyType::Nist256p1 => { + let privkey = self.to_nist256p1_privkey()?; + Ok(PublicKey::Nist256p1(privkey.public())) + }, + PublicKeyType::Nist256p1Extended => { + let privkey = self.to_nist256p1_privkey()?; + Ok(PublicKey::Nist256p1Extended(privkey.public())) + }, + PublicKeyType::Ed25519 => { + let privkey = self.to_ed25519()?; + Ok(PublicKey::Ed25519(privkey.public())) + }, + PublicKeyType::Ed25519Blake2b => { + let privkey = self.to_ed25519_blake2b()?; + Ok(PublicKey::Ed25519Blake2b(privkey.public())) + }, + PublicKeyType::Curve25519Waves => { + let privkey = self.to_curve25519_waves()?; + Ok(PublicKey::Curve25519Waves(privkey.public())) + }, + PublicKeyType::Ed25519ExtendedCardano => { + let privkey = self.to_ed25519_extended_cardano()?; + Ok(PublicKey::Ed25519ExtendedCardano(Box::new( + privkey.public(), + ))) + }, + PublicKeyType::Starkex => { + let privkey = self.to_starkex_privkey()?; + Ok(PublicKey::Starkex(privkey.public())) + }, + } + } + + /// Tries to convert [`PrivateKey::key`] to [`secp256k1::PrivateKey`]. + fn to_secp256k1_privkey(&self) -> KeyPairResult { + secp256k1::PrivateKey::try_from(self.key().as_slice()) + } + + /// Tries to convert [`PrivateKey::key`] to [`nist256p1::PrivateKey`]. + fn to_nist256p1_privkey(&self) -> KeyPairResult { + nist256p1::PrivateKey::try_from(self.key().as_slice()) + } + + /// Tries to convert [`PrivateKey::key`] to [`ed25519::sha512::PrivateKey`]. + fn to_ed25519(&self) -> KeyPairResult { + ed25519::sha512::PrivateKey::try_from(self.key().as_slice()) + } + + /// Tries to convert [`PrivateKey::key`] to [`ed25519::blake2b::PrivateKey`]. + fn to_ed25519_blake2b(&self) -> KeyPairResult { + ed25519::blake2b::PrivateKey::try_from(self.key().as_slice()) + } + + /// Tries to convert [`PrivateKey::key`] to [`ed25519::waves::PrivateKey`]. + fn to_curve25519_waves(&self) -> KeyPairResult { + ed25519::waves::PrivateKey::try_from(self.key().as_slice()) + } + + /// Tries to convert [`PrivateKey::extended_cardano_key`] to [`ed25519::cardano::ExtendedPrivateKey`]. + fn to_ed25519_extended_cardano(&self) -> KeyPairResult { + ed25519::cardano::ExtendedPrivateKey::try_from(self.extended_cardano_key()?) + } + + /// Tries to convert [`PrivateKey::key`] to [`starkex::PrivateKey`]. + fn to_starkex_privkey(&self) -> KeyPairResult { + starkex::PrivateKey::try_from(self.key().as_slice()) + } +} diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs new file mode 100644 index 00000000000..754ed022870 --- /dev/null +++ b/rust/tw_keypair/src/tw/public.rs @@ -0,0 +1,160 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ecdsa::{nist256p1, secp256k1}; +use crate::traits::VerifyingKeyTrait; +use crate::tw::PublicKeyType; +use crate::{ed25519, starkex, KeyPairError, KeyPairResult}; +use tw_misc::traits::ToBytesVec; +use tw_misc::try_or_false; + +/// Represents a public key that can be used to verify signatures and messages. +#[derive(Clone)] +pub enum PublicKey { + Secp256k1(secp256k1::PublicKey), + Secp256k1Extended(secp256k1::PublicKey), + Nist256p1(nist256p1::PublicKey), + Nist256p1Extended(nist256p1::PublicKey), + Ed25519(ed25519::sha512::PublicKey), + Ed25519Blake2b(ed25519::blake2b::PublicKey), + Curve25519Waves(ed25519::waves::PublicKey), + Ed25519ExtendedCardano(Box), + Starkex(starkex::PublicKey), +} + +impl PublicKey { + /// Validates the given `bytes` using the `ty` public key type and creates a public key from it. + pub fn new(bytes: Vec, ty: PublicKeyType) -> KeyPairResult { + match ty { + PublicKeyType::Secp256k1 if secp256k1::PublicKey::COMPRESSED == bytes.len() => { + let pubkey = secp256k1::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Secp256k1(pubkey)) + }, + PublicKeyType::Secp256k1Extended + if secp256k1::PublicKey::UNCOMPRESSED == bytes.len() => + { + let pubkey = secp256k1::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Secp256k1Extended(pubkey)) + }, + PublicKeyType::Nist256p1 if nist256p1::PublicKey::COMPRESSED == bytes.len() => { + let pubkey = nist256p1::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Nist256p1(pubkey)) + }, + PublicKeyType::Nist256p1Extended + if nist256p1::PublicKey::UNCOMPRESSED == bytes.len() => + { + let pubkey = nist256p1::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Nist256p1Extended(pubkey)) + }, + PublicKeyType::Ed25519 if ed25519::sha512::PublicKey::LEN == bytes.len() => { + let pubkey = ed25519::sha512::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Ed25519(pubkey)) + }, + PublicKeyType::Ed25519Blake2b if ed25519::blake2b::PublicKey::LEN == bytes.len() => { + let pubkey = ed25519::blake2b::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Ed25519Blake2b(pubkey)) + }, + PublicKeyType::Curve25519Waves if ed25519::waves::PublicKey::LEN == bytes.len() => { + let pubkey = ed25519::waves::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Curve25519Waves(pubkey)) + }, + PublicKeyType::Ed25519ExtendedCardano + if ed25519::cardano::ExtendedPublicKey::LEN == bytes.len() => + { + let pubkey = ed25519::cardano::ExtendedPublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Ed25519ExtendedCardano(Box::new(pubkey))) + }, + PublicKeyType::Starkex => { + let pubkey = starkex::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Starkex(pubkey)) + }, + _ => Err(KeyPairError::InvalidPublicKey), + } + } + + /// Checks if the given `bytes` is valid using `ty` public key type. + pub fn is_valid(bytes: Vec, ty: PublicKeyType) -> bool { + PublicKey::new(bytes, ty).is_ok() + } + + /// Verifies if the given `message` was signed using a private key associated with the public key. + pub fn verify(&self, sig: &[u8], message: &[u8]) -> bool { + fn verify_impl(verifying_key: &Key, sig: &[u8], message: &[u8]) -> bool + where + Key: VerifyingKeyTrait, + { + let verify_sig = + try_or_false!(::VerifySignature::try_from(sig)); + let message = try_or_false!(::SigningMessage::try_from( + message + )); + verifying_key.verify(verify_sig, message) + } + + match self { + PublicKey::Secp256k1(secp) | PublicKey::Secp256k1Extended(secp) => { + verify_impl(secp, sig, message) + }, + PublicKey::Nist256p1(nist) | PublicKey::Nist256p1Extended(nist) => { + verify_impl(nist, sig, message) + }, + PublicKey::Ed25519(ed) => verify_impl(ed, sig, message), + PublicKey::Ed25519Blake2b(blake) => verify_impl(blake, sig, message), + PublicKey::Curve25519Waves(waves) => verify_impl(waves, sig, message), + PublicKey::Ed25519ExtendedCardano(cardano) => { + verify_impl(cardano.as_ref(), sig, message) + }, + PublicKey::Starkex(stark) => verify_impl(stark, sig, message), + } + } + + /// Returns the raw data of the public key. + pub fn to_bytes(&self) -> Vec { + match self { + PublicKey::Secp256k1(secp) => secp.compressed().into_vec(), + PublicKey::Secp256k1Extended(secp) => secp.uncompressed().into_vec(), + PublicKey::Nist256p1(nist) => nist.compressed().into_vec(), + PublicKey::Nist256p1Extended(nist) => nist.uncompressed().into_vec(), + PublicKey::Ed25519(ed) => ed.to_vec(), + PublicKey::Ed25519Blake2b(blake) => blake.to_vec(), + PublicKey::Curve25519Waves(waves) => waves.to_vec(), + PublicKey::Ed25519ExtendedCardano(cardano) => cardano.to_vec(), + PublicKey::Starkex(stark) => stark.to_vec(), + } + } + + /// Returns a `secp256k1` public key if the key type is matched. + pub fn to_secp256k1(&self) -> Option<&secp256k1::PublicKey> { + match self { + PublicKey::Secp256k1(secp256k1) | PublicKey::Secp256k1Extended(secp256k1) => { + Some(secp256k1) + }, + _ => None, + } + } + + pub fn to_ed25519(&self) -> Option<&ed25519::sha512::PublicKey> { + match self { + PublicKey::Ed25519(ed25519) => Some(ed25519), + _ => None, + } + } + + /// Returns a public key type. + pub fn public_key_type(&self) -> PublicKeyType { + match self { + PublicKey::Secp256k1(_) => PublicKeyType::Secp256k1, + PublicKey::Secp256k1Extended(_) => PublicKeyType::Secp256k1Extended, + PublicKey::Nist256p1(_) => PublicKeyType::Nist256p1, + PublicKey::Nist256p1Extended(_) => PublicKeyType::Nist256p1Extended, + PublicKey::Ed25519(_) => PublicKeyType::Ed25519, + PublicKey::Ed25519Blake2b(_) => PublicKeyType::Ed25519Blake2b, + PublicKey::Curve25519Waves(_) => PublicKeyType::Curve25519Waves, + PublicKey::Ed25519ExtendedCardano(_) => PublicKeyType::Ed25519ExtendedCardano, + PublicKey::Starkex(_) => PublicKeyType::Starkex, + } + } +} diff --git a/rust/tw_keypair/tests/ed25519_blake2b_sign.json b/rust/tw_keypair/tests/ed25519_blake2b_sign.json new file mode 100644 index 00000000000..f8bed662a02 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_blake2b_sign.json @@ -0,0 +1,1002 @@ +[ + { + "msg": "", + "secret": "fda53a4f1f7f8361e79499b9c4614afb0810d609c9a379f6140e238623ad8d40", + "signature": "aba55cd80c67a7cbdb2a02872513bb6c17a3c1cf17c9ea6711a30dff872cc71a63ae40aea20a6823505187aec5f7a7c4c353da11bb7f0efd363453b907cd0d0e" + }, + { + "msg": "dd", + "secret": "750e4878e05923801ab8cce8212539aceb09c96b0ce3b46c96e5fd7a53f9d95d", + "signature": "4de9e23f62f538408beceb48dc43c27e30cdd7bb65e2cfa1e876722db6017e525df94292f6222c71ee22e42818686a80634db61099f3aee4336d90f854ad930d" + }, + { + "msg": "39ff", + "secret": "a816462a487a2c7276b36babb42cf795a6d1902471175daf8295c8141dd468f4", + "signature": "5de642c554a644880f1dfdc909f0749213a1f35f7cbad83f5c224126f91a567888e92446612dd632345d062186132c4ec2eae6da1330234446e4fc9ff4897c07" + }, + { + "msg": "6dfc7b", + "secret": "f7073cc5544d056e1a34fbdefad229bac342cc96905d139476f70b2beafc5608", + "signature": "df409c4686d1da327a2f1ada96e672e4ba9c0430e0f229d7e48ba2d3a15118bca8e008b31a85212332d48c8dbba46750b2ef96c2736242d5a66b966597253705" + }, + { + "msg": "62e7acf4", + "secret": "a9b2ea7114d55795488da696be343b0feb9585b979753a835b94880b55032db1", + "signature": "43232c680dc241380827b48367f3a4c4847fe462cbe92c2369ab1ffdb3492c151822e5c64781568a8a70c73ffe0623b8ca88c0f694c0eb84df75cb503edd490d" + }, + { + "msg": "f5254c8d72", + "secret": "6721136b51d0dda9f68b97c1687aecfa4b78b80b80d9d4dbfa88ad0269d7d3ca", + "signature": "c0f1023054428b31a597826705fdb0ca8214e0718777e4db0b1442f7728adbaa5e4185b521d006f89837d1ec84888620c4d320ddcb554201f6a8869f24743a09" + }, + { + "msg": "beb08b836c68", + "secret": "e45654103bd263dd7c25632b7bf8fad59dbe888d57a35ba0aaf68ea062abb144", + "signature": "3f665af8767484baf32e636d749a01fc496c1e1adc390197fc16030b834bf444e97dbef34aed083b58e81f76e1829a04d12c32e6e9974253bfe1691cd5d53605" + }, + { + "msg": "e6d60266f8c3a0", + "secret": "edd10e2484b20ad3dbf7c4372736cf27ad78d7100d457efffe912690420dd701", + "signature": "5c8509c07ed1e02c63d8ac0b42b8bd81b086a136b8d246af41103b8588997da42b3c1595d397fd0877556d16d148b4d71f01ff7c1ffbfc4480fcf6fe43205b04" + }, + { + "msg": "b82bc420aaba3c3f", + "secret": "4c295addb346646a2d14fa54f87e181c594b3c9d310cdc47d2d3610f99f6977f", + "signature": "9a50b76f6497ff29b483ea517fe46813fa31ba27efbcead4a5ec7da8a5aec513025397fc5d93264341a2bf0a3417f53ca14f4a5a56f841849cbaff6492974406" + }, + { + "msg": "3c5c3a79b56ceb6b70", + "secret": "d14de53a47d7b125d897c23a2f99c483df7c80e45c951e7fa1007810835c383b", + "signature": "e3fcdfd7720dfa833c7941eb8ecc86f2a5a193784f0f8916e42a8b135701cd1950f52eaeb98455919a5e184e4efcae26113f05843bfe35ccfd104ec64149d001" + }, + { + "msg": "aa688c4202d26d1ee9c8", + "secret": "906ac18066baeae8230bdc788e3e365cb2d2c67603d1a96c10a59b05baf5eeea", + "signature": "0542b5e24c611f2bfe8b837b4ee3c15891e18755888be5afb767f401d3a231269e2a292b7ccfcec5db5a7b25092556711f06fd289d8ae7c15e0741a1a1effc02" + }, + { + "msg": "0c8bdec7564ed702770cd5", + "secret": "824e4e5a43d797727b76d079ef748f6969479cfee28471fec3d104f58888231e", + "signature": "b1e75b49235239013fd4044096bdbdce81c404cb6ac733feaa190e6a5bc5c6a5664676f236ef2d3c2a4e13dc120f507d917e2a38840ace2dcf107c8899d0df0b" + }, + { + "msg": "099dc2d90d9c2d181859abbc", + "secret": "0665d6a654b04aa97be29fcb9468db8d738781cfa8ec6888f24216b713ec8338", + "signature": "215a701bc9cb2dd6322c02c008ca8279d404ffb70e50e07c2073eb5a8dc4e44d10cecebddee1614ed362d0cb02aa2e14f3da805e0d88153a7db148d7bce74a0b" + }, + { + "msg": "de6e254e3bed4893b082b08932", + "secret": "c340a9557695fc9ad374c15e1cf46c5c1acaa548547f785469094fa41de28707", + "signature": "93ce56942b61557f464f4b64254d271d75cc51034065f8ab443286d41944906a5034d9d3422461c43d7b9f673aaf22f239b6f432dd35b787c4734402e96cf10d" + }, + { + "msg": "baf997758807f1724bd1c9618950", + "secret": "fd3a9fc4c82523062dfb529167c131360463037ab4ef57922abc2edd316dd0b9", + "signature": "c8129ba6e45aa0f6930135261501e500d36c5033074a1f732618159c26ef69c00fcc49967101156b7dd32ee2b5b4cc365134ffe64d1b9ae81887c025ecce0800" + }, + { + "msg": "55eef7db5c2a7d80f23512cc17bd53", + "secret": "91221539b17f92623bb38b5b8911d7e43b584191573d1243e5f3b88336c5b75e", + "signature": "360227427879255c1a4a5d02377085f3fd731b51c72ea51108302e7a98d557abb184587752390da90bec4b0e891f478679f5bd7a0db7adff4334b5c042eb1406" + }, + { + "msg": "bdd1bb669e2fccca51ad2036b9600746", + "secret": "612054d8d7d81c537cd87d1c0484e3214e93ab7fff9e6e3af563c7b4b1cd775d", + "signature": "ead78e7a0fb3fe757ba291d3614757c7a58a8fc97e2ffbf540aaa5e74e473a4c7187ac365db87918a0c92dd9533faf17ec69ecbe39ab4acec4e0f0056d2f020c" + }, + { + "msg": "d38763b90a01ae15946330bc25f6279b84", + "secret": "82143dd0a1a7cf8914aad078a7f7eb25f395ed39a71bf8fdf2425ca77dc783a8", + "signature": "25719a12f17f5e9b92d94e64f337fd4b3687889a1001dc8426d88d72917bb05745d951ad4d2c3da6bc6b6548a3b86fab3c1c1619075ba6aec2064a566b82630d" + }, + { + "msg": "b5a9c5fee75c06a8ca275671884650b49c06", + "secret": "f4e8391d09b8379190621adf4906fb2dbb15e2c70b869204843fc1de280d7beb", + "signature": "e32efd63936ae162755911d594b9ff85e870615d451bb6e212ef27df7e56c2d4e1da511564824a23f6df1f69036cd287438f05ef47858620cc442f2222053101" + }, + { + "msg": "475a79be6f5ecbb16c375a1011f7b23059a92b", + "secret": "eb9376f74ec402d86dee0eb419313b2427f434cacd89782ff1acffed08b89cff", + "signature": "18082aebbdd5bb8e819ff207cb32a2347bc2666f2acd1627ac7926471f46355b5bbf45ac8b33709a922c75029b5d2a3a97651b807bbbb748e10cf3bafe65f709" + }, + { + "msg": "89b8565d6971c5eb6d1c618b02a81371eed8a035", + "secret": "9c5fc4e636a867fd6bebcbb5c91c571b05c1557880548da4770589a7249ddeab", + "signature": "a27915f2b0f323b0e780611dc44b5311a173e5cb905a8e5391d811232fd2ce5c7ce7e56db022b04357686868838d482850b57f0220b42b02ab03290b93521407" + }, + { + "msg": "b166dc5d545da6a0c40c5efa4a6fbb9b662db684dc", + "secret": "488e113e78a940f04df5fc411132f6b41f1f99fb71505b62b939ea2f3a5bf787", + "signature": "ea1638e435aa44cbf1fd4e4b2bf86813af25be8f1b3db56b15c7c2ee9e5b3d5d0c545d2e317b2525b7ff39cb36fb06f7608650cd52df8b508ab24a0ff954d407" + }, + { + "msg": "6fba6099d8e2245b30e38efd39b464a51fc6c0d845e3", + "secret": "a939dd49d3ce0d14401bc09490bed3500f29a28ad23076057e9814b9faea62f0", + "signature": "182ba7680551ab0bd8d2a881abdef961cb9a43b1d7ef6a3f8cc8ae4a7f0bb3022164d3a2f64c7a1814d5ee71838ec1b4599988a86dff2ea730e71fabcbfbcd06" + }, + { + "msg": "aa4e0ff3fca92d3661815658a51ef9b85622ad6015a504", + "secret": "87f33c433bff439de255372af056b1360ef67b289ed3e054e2ffa827d7c5bce7", + "signature": "da4cca55fcf5d1f81c6cf260c7f161c9af5db66102f49b10a0479ea52c50efd03a57badab1750ef68436eb37324064c9d2b51f4100bf3b42aa65a7d8a91be106" + }, + { + "msg": "ad6eedde28ff07393affe4cbbe72c4bbad275f3e892e37c5", + "secret": "3554acc8330edd0a7bbd17408b1dce9ac09a60b5f56c9c6f4e94cc43a0618da2", + "signature": "3a87a357bac8da376d8dc980a7f632431a6d8620246195face393c86c21ca911cf38885c69a3cded025217c192bd55c25b195ed72ced1a2334ad6d563afcc404" + }, + { + "msg": "60bebfdeb03a0ca369e97eef25bb0f4cf3a2661ff8c9b7713c", + "secret": "2800fce83436536ebe1b622472a4a207beafbffa5b729d18f3e154f23f51125c", + "signature": "602bfca670c7ba82972539d545cf8140264cc9861601bdc392ff28080ec7a408f28a31b4ed002ea6490379178a7901b01ab4e1e9a81d1aee70d2a7dec0eb750f" + }, + { + "msg": "76ad988c91c3e21dc03cd5fa37f8e7e213e2cc25558a9c3d6e48", + "secret": "24e07ee4bb95ee5133ef128a622e1dc6adb523f9e1a1a5aa1f5fcc1c869a0621", + "signature": "f80c2e504941a4de74d8a3ae7e1269c1a846b11c898cccae9a82bcc4aabed5423749578e659aaa0a8088c78af28647b49a728214411ae6947d49a7bce7bfd808" + }, + { + "msg": "857b29b170ede8817b8d37fe56ee3a4b4103c22db1f41264d6044b", + "secret": "0c6f87d32fbbcad37a6693b9d8ed011bbc2d7aa60f6d835f807374a2a3521a55", + "signature": "d83e96de3ca39f8e8756ad7acd3d2a491661519605b68af0123ec5092c0a570951c856a5b7d4a833108d34b6623a8d91302f623f39b6a371448287b473ec8d0d" + }, + { + "msg": "c36dd9e48f817428f37537c3d88a57eaab9a97e6f4a6d26901bd88a7", + "secret": "8d88e77647d10a0e014eda49e4a5a5fac88a5814da1bdb3a64b86cae9c47c7c7", + "signature": "1230bf7dc2f76bdef826e7f153605bcc5e2270e39d073febc5803f3c65a836f9e59eea3187a024ca34c04d9eb30622658ab1028bd372582bf2e179c02d491109" + }, + { + "msg": "f798e56441150cd8085bbba8137a8293d4a3d25c9f3de900289505486e", + "secret": "9b7439917bf1ec65b7fc4a9618922c6b441bb14575dd27a9854aaaa31fcdb417", + "signature": "148ada5c9a12d96136696161105336e33eb95fd1169aa81cf0e77984b374819ecf4d36ec4680a6b4e14118acd70d4647ef0c284014630012155c954844787c01" + }, + { + "msg": "80daf165cd68f723f31f33fc40776a907f416ca878ccbd4b76b8c0b9e1fb", + "secret": "2069a87ecca9c9d29a2095477949c127f8a1a70b4e1b4340640d16401af2ee8b", + "signature": "ffe92dc4edee96224f82e3e0ab6d79cfbde43a1a5a7eb103c98399716f950a5ecfb475ecd738c3471e0c7d787be737a711084d36da82fa14b2ef636c247c2300" + }, + { + "msg": "ad94dd59e2d94feb1bf1f9cf7dedbc284681c7dfb6f8af7a2f06ce81b07c17", + "secret": "759c801eef52bbe4e680aa49aea0dfa1f5ec322847bf88eae72df6d0aa2aa063", + "signature": "8dda6f16d6f2144bdfa1a02ca9422b365ac468af254e6feb6e44a5643af6d03b2e1e94240aab7d7f77abe484e7ac0147098f129c2edb5d9cc41a8349b0225e00" + }, + { + "msg": "79dea9acf45141f648ab86f2971ad4394d4f7a54df16ad4ab56ee17417a58784", + "secret": "45bec12dee32857ae9a70009e316882648e231cb747511fed2a728316da0491d", + "signature": "1ab6bb5ca12a059e462b340aab9e500c92e13be4437e547e99da723fa81fcc16813cf5d84dc10bfe7131688b2a9739679969b0861f38a05be666ab38d08e270f" + }, + { + "msg": "8e78bc8c558c3aa9ebefd6678098ccd49327ab9209d8bdd8d011d67a0a10d4686c", + "secret": "5f497c44c327bf656fad471d8d2bb76040909f816fc9e70be75083fd7988ccce", + "signature": "60606f5addebfd182df43654724d08e8b99a5778ae9a333e8218ee87bb27d00aab28d918556879019f803d6c17fb9a8ff67aee917707ffebadcbfcf8440fc602" + }, + { + "msg": "e28a6b53c78614ba7ae05865c2c695ec280d299afdb7dcd602788bf1147c85c562e8", + "secret": "4d96fc8f18dba850c6c5eb55de4ad9d2a5fd161e159b33261cb5238fb2fa4bc8", + "signature": "57baf0124b0964e5625faf548eeb9c2e3bc1995261e0c27c7967d39eb743f19003060009a7782c683ff1e3c4f46777ba399f83cc1582eaccf3ad63ac3504560e" + }, + { + "msg": "11021bd7007dc3b3299b837fa43d9661e5c513b6e64cc59713441598cc08b9adc97e77", + "secret": "2e23ed6e663ceb5aea669f7bfec40774a333ef0945fa93fb40b6c36e55eef11a", + "signature": "f8277bd2957848f12c5b227316219c365f25afe862a9572de1943b26a8fa0562a9a2ab50bae3fd87adbaa8a8a510b718e6629cb4a34713b706797bf4563e6308" + }, + { + "msg": "c6451d0d36c6650233f8e27b1d51909751349ca73b0bb2670983fdfcd01e571812e71093", + "secret": "7919086d46b9078ccff124d8e19a285c03e72a4d02650ef7843b8d3733f409ce", + "signature": "a18444e1e255c2530d283adeb151ded083b6a06bfed384f3cc759e1d93963cd371e8a14485928df1aebce7fc2f3ace62c5b9a073efa60e8d773845a142840c04" + }, + { + "msg": "3e84b7645980295f9fbfba3c3d975240e2f5a6aae8d5386365901bac4da0c4d0cca0aaa394", + "secret": "8f1b5c1aa4ed5d06a4f42883af2e96c98e394e15f02a556a3b87b649c027e56f", + "signature": "487d3141ae45a2260c3604bf1c9cf918d64e097c0f61407dcfa1dbe56bc32b6a21c0ffb3c2217e4fa17acdbe85fffa174c27e7d93bbc42cf5a43550749029a01" + }, + { + "msg": "a0877608dc1f76df885b16e6f394977b5df6c35d89776839dc8ca1dcba1b570e807cca1b7ba2", + "secret": "f1643ad34f54e089afc91ff06d6c19caa38ef7cd72eadbee3e3cd2054548fd5f", + "signature": "407b96a05da2aef298cfb045d1ed7545465f5cb4ca42a80c0e0bf372a81de26de2058fd01304a6730caa1a59d1121c65ccf1a047a0e3197bc27bd52c18e80a01" + }, + { + "msg": "ac8b1331fed75c7ae8afefb2d84d5b8e504c880f82fe9b10c486172b08ac9766f0b8ad158c205c", + "secret": "47c4a61a557d476509b97f8a788602c49290988bad073fb86479deeb6bc15147", + "signature": "89d209692f43a918a2ae1582c4e87cd0a9d51edb7b556733ce898c1e14fdb542b9d5d6263423b6825a8ff3a16f0ac139e739168e03a176994a8d1c81f3ea3f0a" + }, + { + "msg": "e70a019079527b552bb9bc0735f00d2bd863382e721b176dad9a9038dd728a5da371ce6234447165", + "secret": "18ef2c77e74f2cda45ba4b8e47c1f4303ca18c84f3983c98970005c50aeb2c29", + "signature": "391fd18d5e49052acec4ad33bad06ab44e1878a0df51df8c7a972fdf7ac0ba50e7d2fb5c73102a789d5922b67623eacfd5c19a8152575d244674806d9208ae0a" + }, + { + "msg": "d3adff1fa99e5e4450c85c1103fd7a5ea881771dba9a2d40aec65fda6a3cf88442f5627839022bbc14", + "secret": "324a9b71f1507a05e80c9305d18ef3b1b31bb0ede7b90bc1c190b1b54af49fc2", + "signature": "8843cc02f99f7c1910d7cd3e42a9dfe8c8beb5e2e8715de18713b447487ca73ef54b79388e10afc1b59c21d3280cce2d7ffe59bbc2d4b79f4aa0d9be60d49a0a" + }, + { + "msg": "2588486c5960831cf6b095309398b995e141af1043497d3c445f895d0cf0df638bcd625c63d1972db4c9", + "secret": "0cabad2998043b72074f5d043072eec6c647147f3c70b81dd56668dcb094c5de", + "signature": "0393155b8062587c95c98075f0ec9520104d0c8daaaeda776aaba8a4665347b4b57a47c0165734ab69f14ec6e6add928140708f49e3284b1bbe391c19d885808" + }, + { + "msg": "4e5f0a551fa320669deb39c51be1f19f1974cf6ed36cfcd95df1c3ddfedcbf9fd4ea0456c9b5ffdd9f1688", + "secret": "8e691b297853d6f0678d70b3a8400be40a2ed6f1cf1124c119b0b853c0e09dd3", + "signature": "af1793849734dc4a2b179bf2f8b05498a44eea9777683dab2a6fbeec5d64c71ab4583689a8dbc78f563c2cece0601ece836643f99ef35acb723b105c3fd90409" + }, + { + "msg": "918bb077a5bea3e9e2481dbe3b1b8a50e7cc9cd666f77310937ef05829805d1ea996b441fec992e1f4c8722d", + "secret": "44d7e4e0d3fce508408f47686f517ffa71d0c2e67a074bad4d50f9c618c1809d", + "signature": "19e0bc2e2116b2b4df4e899ade6adba9aae00f8be2aba47dd1dee77b46f49363f0a55e74ccd1d8f30cbac4e39b09012e81654ef275cb4f4d6658dd18dcc1de07" + }, + { + "msg": "8f180dbe77eebb6edc353ac9af510608ce55e4c661d32ac1a3fc21b8e77e191478d95b7d115d5d30f33a64bc24", + "secret": "e4b2137b74520ba65090959711575340d4f0a0dd4c525746ea3109354b2394e8", + "signature": "4f84fdbc0ebf4831a024854f90c9797c0e260c0b8114b65ae293e49007a9663964dc568ba32d1544a282a58dd280b6d3dc10abef2c6b848c67a634869a01880c" + }, + { + "msg": "d23a61d1ee186e3efa620a173a6372f64fd8ccf6f7a49c4506c27f736074ebd69466c35a15be0b1589a1d9d28b96", + "secret": "0a77612d779e49ab425f6b1f19232c95c72d703522086ae865af4f038f1098e7", + "signature": "d372138d762cd6cb5300521bcde7fdb12ae69c3808b26960b888342c78deb825bc34e03533dbb4bc5e86d5ad13c44e266907b832bd52e0d76784f59d86d3c001" + }, + { + "msg": "c6aaef4bab3c28af36c05402863e9cf7fd029ed86509f78dd045d3aa1f3760fbc66588288fa42e402b8e7e323d44e4", + "secret": "ad5d2dc106e164495d142abea045ace165c9c01c19ef81afcbdc841dfdc4fa3f", + "signature": "fcad011d7f29fb324854edd8105cdd49b6cddba95b2919df162b46d36e4c4b924baebde53ca9bf698be59b6b7e7df1f88968e4333b8ae5640eb9bd462f348d0a" + }, + { + "msg": "b00c76b0e166b376582e6694d505a423bd83e075a03c351bc3a73e768010713ddc442324e0e851fe79b16d60ff0ad78e", + "secret": "64b5997fed822631a0d7d36c13cdc10e4bac8def99320e5f4008adece4308f4b", + "signature": "13cce775f825c94bfa51d0e0ca221bb21be6b15056fdbce97f1b37e8ef39357bb42d3e0c0ac39a22750dfc8d9862ff9e86193b45424f29df5fc41d18ed28e502" + }, + { + "msg": "3f5a761d6c51106f55af39ed5511dbde9ee07c5a5d517c3857e287d3a8fb4d87661b80efd522145063f4c56f8789742ab2", + "secret": "4671da9d0d339aa43d7da911fcbeb733b0184860b62135904d618ff54bcaa55b", + "signature": "d956ccfce30b505d386a733a3f4c5d44c234d91a6fa3c932060dd7321bc60e7e4106cac3ccb0add3693fbec18360fa389654a2b93f453a144021ced61603e507" + }, + { + "msg": "bb15e2732919c7941d7e4bb5d9faf5951c9765fc761fbe9ae2ff308b44d40d4f17a02167fd2c3ff3732837db6611525c9eb4", + "secret": "b3441dcf5d2839c7ebc35d0aa977223fe94be8a98c8ff616eb88b787606ceadc", + "signature": "e4fa37751ef2d3b7a9b3848517c65bdb342dea2096be88d03831c8426073ccca06ea80f66f9b71db5316912e20328f9c36e3e29b1932ecc0ef956eaa4fefb801" + }, + { + "msg": "09", + "secret": "425c7f124a2cee88d9446cb6f701f729aabef142ab032dc718163d216dddb4b6", + "signature": "6876cfb1472d54630230d5655e4ea0c957339abd0d3ca99ad76a379135db5a833eafba3ecbb58815ea0945a852bf2d90c9693b3e5a83f291a9e940f445bb0504" + }, + { + "msg": "6ccc", + "secret": "7d58a0fa673f51e00e40d4aa4b21321681fa6edcb6f3e2f4b2a64392b1e8b1e4", + "signature": "549d947d2a2703afc91072638465fc63fc9f90a12e63f5fde5188647965e7c09b23eed1810c6be7f4d47a4192a414ae54fa89b003924c74ca771eb5ca287140b" + }, + { + "msg": "fe20cc", + "secret": "dde5445994b45a6aa07ef2db44aeaca78ffa5454acffa22270e85f72bc3c718d", + "signature": "a04f533968be2be4e98a28c948651961f25440d20b226e622507019e0837d5a4bfcf38eccd6f45473975f53d7d61cb4e2ae2f4c041e85ec8ebedd97145820d04" + }, + { + "msg": "ad2b1bf8", + "secret": "8a16d75784b5b7aa7d063f7bd5eaa0516a6c93104e13d8e98f4be590ab6e05d4", + "signature": "853ca2855f8f2c26f9e9b735056321677733b9d5a9cf7647f16b242d4e56f972a9703812a75974646d59aad5dbad9a3570d6c780af364def09b7b2c2026f8d0c" + }, + { + "msg": "31cabd24c9", + "secret": "fe6b88bd90eb8866f17197819e75c6a18c450bd00cc1b16a6a1f5df2a90d4989", + "signature": "d6892c516a0e6540a96e6fa9a9bb4b75d85317aefd2c6aa82100db73ed6d8c3c396275c93d377d0d3fad9de16ab59e223f0cc5c9c6e078d1d6e4902163d41607" + }, + { + "msg": "0419e6ee3416", + "secret": "c51dff716c97a680bbeeab2b9c235f1f9b8cf493858e3def87f4fba553d4c3c0", + "signature": "755659118fe54ac9488d2a330044458494a6161b86c0a1e76540286c8c911a82620383edaa83297541b16a0fbac42515cce4be86e3dccc05611791245f84c00a" + }, + { + "msg": "00031c7010bb21", + "secret": "7a184761207b3448c46f2f54a2fc7094e616fec9be0d0e06cbf74e8ecf9ad458", + "signature": "91466f60b906a3235dc686e072a9244f9de090d9af23e5df180859227db6b031f7013c71bfc083884bbc49105b088938e4d8666753c2438538699db3fef4f304" + }, + { + "msg": "9e97b1719062f239", + "secret": "1f3782573f9fe179ec2d1f6ad8c7b979d85ba24fb53ace751e46dbfef64ea4ba", + "signature": "01711a7cccfc540941b363410d353082a52eba46699e863e5ad69cd82f7d0aa0db1e04f63990d8f63759a6aa481800c5b110eab104645c7443139d1c4f1eb709" + }, + { + "msg": "ef205ef7f7387b8abb", + "secret": "94d23732d8267966ad938f1767f87cdb4677e07b4b13254745ed0895aa3baf5c", + "signature": "7f81876fb67cc5bc25f79636f7a377939ef147eee8467ec2b41098a0fafecbf9b52ceb8adfc038c325900ef279fcbe44c494b1abffb5bfeb965a3f2514f21c00" + }, + { + "msg": "f0cdb192b2a14023e2f6", + "secret": "f2cb1e404eea5d8ea9bfe729560b8bd2d57728f6b4aa52b4c111b14807258b85", + "signature": "e09d2b9689c4b25bf5875370d8ca5030b7ea695d6bb95265faa19aeb90bd38b2af301c1532e67de0352c6dc92a13393407aa0465e23d021cd96ee1d0ce6b620b" + }, + { + "msg": "689db47e1f9d36191e3501", + "secret": "2f9dcd117c2b0dc7eb9bed41d2d19ba950c53d0b24249844956da28be497d948", + "signature": "9017f8567fde0bd6a85f9c3347b26b77c6cbe784ebb19a2bfe56251402b4039f0e9803a6b1a69d018765dc2a781887302591d4ca7301a83d95ed902ab084e602" + }, + { + "msg": "263fa571ee3eb4e6f49b5f22", + "secret": "bdb192b5daa7db1955c7def70fbb0423ea7550f3cbae5a0997118f531d2863d9", + "signature": "407ebae7fe15f461dd9d7cc4430cc8662544cb76f2c332692cbd466b1316e3014b49a7244a067c6661d62e914203981e666f15eec348b3fbadb72b3720c5510f" + }, + { + "msg": "f19b8c8ea3a001a60de324ca5d", + "secret": "ac16fcd231564a40f7b0d8eddddca79e28ae3b62ad73d3bbfff47f13cc00068f", + "signature": "0a05f1bb48552f8bc0e05bed94db39c70b34e8e8ace71825857bf70abb50bf47cf76bcf3c6d789e95cfc5914f860ac1fd4a924df2ece2dee556eeb9677a37200" + }, + { + "msg": "8d3d5b30087ced930214e6db75a5", + "secret": "f1e9f5ea79a98e2aac6be31be7352e0d82bb232e95b70856d1611f2338325e1e", + "signature": "a457db77dcb2ec9f4ba59d23a220abcc7e37334514ddf79a4cab4ed9ae3369aebf99c8d5aaa0ef83ec503341df8c398642264742659390cc7f6c90376f821f04" + }, + { + "msg": "120bde3bb0d3279487c79676301654", + "secret": "75c23a7cc7829d363b4d85dc86a823f6252f7641decce6923fc8aed6c7954aa1", + "signature": "1670d47bf9df18e3bb2ec4387ac2de7cbc4e670c8b4e2089cbf9929fe031dd6f8f218bd6e59bea521bf6bed0e05f5ae7ed0ee987aeadc7f192daecb8be74340f" + }, + { + "msg": "7d58b0c0d0a3370fdf0e77f199e3150b", + "secret": "1ceb01711fcf728edc31dd45c1f36e6d99c0315a6c9f62d278d5959ce26151dd", + "signature": "68850838b0271ec376f80f4819c4e6f312cad8efae894dea2276f3e85aba7e4743f1ff1613469f5bd5dac8a1bdc4aa9ea1c330156d183c3671a0826a1580e609" + }, + { + "msg": "41082316450f0174d8576e577fb8a72065", + "secret": "fab8682cc8fe0c4d9892cb7979db10fe22bdb83fd18bbe420aa19aebc26dd2fa", + "signature": "30aff36a2b088bf87f81e621a4b0aa04f4911b2ee2da882b8d06ac116588a1968994dbf98f659712c5dc5b0c21b1943752732761c55f70bacac1834632cccf02" + }, + { + "msg": "92f5d8045cab4aefcd72fe818b5dbe3ecbc0", + "secret": "2d5e664ee4db6919e1de1e27361d2824ea9489babfc7950c98a994a6f25a6ccd", + "signature": "434e5cde5bd1288cce0d9a0847e28aec1d9d55fdb954e592c662fdbc52e2950a18a80025516742035b25e3838aef4f1f8c9b28ae8394cffd0de6da112d122c03" + }, + { + "msg": "a79d909de16c1a0577df16eec9cfecdfda7bf1", + "secret": "eac99ebd16239c8b4b8fe01175f2c8a1d955874161b8517b8023cf4efd24f53b", + "signature": "8e1222cda5c610443060f2c7704bafeabbe2150ee8343e7e4b8325dcb764b1cb57497f044aa80e1edfeba1cd89b2b0f646004e321b892523a4f4919791951b07" + }, + { + "msg": "11ec0e73f26cc7b28f4eb4f6130c3d38821c45a2", + "secret": "8dec565327c1d3f294027d108387ccdc8f8a32e7a54553d88e4e1bd5ceaa8cd2", + "signature": "711f0e64637ebee5a1d164dafa3d22e3aa322b420b75aac69d8ac090ddba33dc9f190c7cde2074301d0f692a961d1f9e4af033092dd783bbed6e3b0afebb6c07" + }, + { + "msg": "a9b3b5b712f5cbcba41c43df6e7d8311be4d22809f", + "secret": "8c10dbe83fe9ce4734fd99c56275a0cf2b29a8a26de7ff6edff926c37c05102e", + "signature": "db402f386a70f8a81842cad19981f820ddf1e65be39e8b12df5aefccbd1f753b12f6b9a9f776efdf8f9bf8c940d7fdac3cd1250ebbddccae524636ba59e56c01" + }, + { + "msg": "4444a413b3eba41e5f77184c43c214332c8a92d0529b", + "secret": "92de0794d99fb17a48408a11f6f8b7a0549424aca0d0c20df5a9f5dd5d84f00f", + "signature": "cb1553f6d37ae917d9ec51bcfbba8b4737530cbbb4ad10970eafe5f0a56db15857fbff4c8b0282d4ac75b050c854e0d071f8d8902fb906a9b635ff52a420b406" + }, + { + "msg": "9c2911abf8f03afe1ec172d379099032949e6b72de6eef", + "secret": "42427b150c2fb03c372d624c17d16f07fed09e293e5b48d79848d2734138163f", + "signature": "5ee13d4c7d18a3abfac02bfe20fc5592d0d5664ebcb8569524fb61a18d4eba85c60f7a11f9314b42850d383cc1e8e939ad0a0df6e67d223d3855cdbedd319603" + }, + { + "msg": "4dd24b5156bd4c17f59893a25da4b580094cbfdf4b5ba72d", + "secret": "75701135a80450980e423032c0d9a90dc1022f1a3cb56fad212d288d523cc7b6", + "signature": "ba55bee15094bc8cc4d38bec04e93e7431bc1729fb46ddd83c92b70982ac51904e93d8c6cbb39feed9e074680330ddd25c27411c637326f74d301b6a3b34e101" + }, + { + "msg": "c135fb0cf523479dcf114ffc662553083cc9dbf911ea651472", + "secret": "65dbe47d6d2bbb5b770297537f40834d9d41b53cb7b81cacf4bfab2f5cd9cc21", + "signature": "51209851e39187313c58dca2a59895445fd682ee81165abd51f9144415bc70dbfff64414b66c6188bec70aee15cee31231c73a2ae4a182902620fbf4aade6208" + }, + { + "msg": "41cf555f66bfa1ac45a44a8c58d58c1d26aaf6c67f048cf448c4", + "secret": "20ad7f07a1b73daf76525b12403c8f03d0b813d16cdc3c260fea9676eb2565cd", + "signature": "37a6eb5c396079cf113f7875fe31fa69936d479acecbb4cbd1a66dd59173ffe269b920b73a4898573fa75794466d3c7126f4a6890ef4c7ec8e99042dcecd2c01" + }, + { + "msg": "ef047627fba787e3637a960320539ea467d73f1fc989999bbfb860", + "secret": "285060061709806df36712efd810490a35be932b7a7903b8955f77a99d2cf252", + "signature": "95e91347e4d2f7e046a4567d588dd73bd4583e1788c04942b87e354e3a870ed61a646a6421536179060c00c9e82a8f8f525898a96b42fe884832f459116e850d" + }, + { + "msg": "accdaf34bfdc3e14a459c0015e2456f91d05ebb75c00cc10025aad27", + "secret": "ea6fc1023adb4149d76ca1311416c362095f679e2d073333f60568dbbb54f55d", + "signature": "d0a313051b4bc8fece3dde94e9a98e584b771c7a2bd488be0f366518add9178473acb188156f3a170f835f801adb7c773a540642a236982356c5fdd96a453c06" + }, + { + "msg": "f6952a61326d0976013741bc54b66b844bdbe1e8382be9f71ba5b49c39", + "secret": "df3747aa2689e41bde810e20f1fb03a890d4f126e80a2b694613ae61ee96389e", + "signature": "6e9ef71b5753c0ca0aa03561d11f4a59da751db623202e7e63ba8cc7764c806019cab7b47065d339bda1edcd3be1be30b9b75ad647e9d7a69559cbbd6e3adf01" + }, + { + "msg": "14abedd6d83d959a68fca5f85990001321d1b70ddf696c219f6250a6dc48", + "secret": "a2c136a31e0b25402f64251945a562e3a0214b0194fc6c1389df5480041be8f8", + "signature": "3d617d134f22070143776ed26478e8acea806cad784014ab36114f388c295b072e112bf4259c1dab5e8fc691a745837706ef5511e4483f95664d5a76568f0f05" + }, + { + "msg": "1b32abec6a27d76a5259bf8dad7f4c9542c5751af5ea9c4eb1a90767d999cb", + "secret": "4845201cb48b7cce5b4157e97580da40d7d757cfb33414608ecd4e2f16341c9e", + "signature": "9bc26d1dea6ad0f85f87f3ec97d5fe9b9b7c7af5ed62940ab5625d8ccd3afb668f70d3ffbea713b62c1128cfcc52bf39446c12f08413a03c423c7be3d5fc6605" + }, + { + "msg": "d08565d04b2e20723b1cf844d6a9c572a72687f3888b52d6fe9105a73721437b", + "secret": "7266e81ca4f78860441e2f0cad2b87734395a0e38c384af36e2db65f3cff3f85", + "signature": "167f63ff714212a2948aa536efd5c95f645848be19020bd7c0e513906108344ffeaef994a24c667841e81ad31c6efdf715d4b24e3c97354fdb9a9cf943bc1708" + }, + { + "msg": "1422c58a7b0ddc50b8ba906f8908deb1aebc53d3087f5735ea8e307dda1be8ffe9", + "secret": "70194ac3dee1cf30a93b3d5ced53aa23eac41b9d36b493222e59be18c6492c35", + "signature": "c4a7fe00984487a86c0aa4e7dcec2f0aa3803107a6dfe084d7254e921ecb7e20303f371ca23677dbf7c91a07e2d8881d7dcdcc5655e3f353127ea02bdd1cbd02" + }, + { + "msg": "9ad96b0dc9cdb7c2eccb0cfa8b497ba36260dd540cd5e0ac37548056fd37e9eabf4c", + "secret": "0abfda7daead06eac29db130e6936d85e8896ee7fd71fc3de40a3736546f134f", + "signature": "4e59e718690661cd7eb3d88ff599521f6fac1431a4c02c5c7c40066b51da9700bee6bc7e13cc7d442faa619e442d9ed34906b590c42690bab61edb2cf2ec0c08" + }, + { + "msg": "e44ac974a8beebaea615385c2efba586ba2c0a465bf80d2949b56adbf4aa4afaf96c1b", + "secret": "f8e9ab423f1ca7532e483ad7a7de9f34671568b704bb37ac042f18ab3c0df7dc", + "signature": "38d9dad19cdaabd9f6fe757902a55fcbcd3fa9b5604b36bd45716b49632134ab922c23ba438b1819e4df91df8aec3c844bd6641e0f41db00ac426e67ad8ee108" + }, + { + "msg": "d86313e5377157723f3ede8ab1e4c51423ca56dae002352efc29615c445b636b81cfd91b", + "secret": "87319d671ed78c2e7c24166771744738dd91f736e10c3cffa63b33a1e307d7c5", + "signature": "d63b56e5116f26f6ae91fe6ce6efa3c352bab893e4987e734f603cfb2d8ec8bf1c18fa21baaf0ceecc21f2157fdc3b108b51641bade4ab71412e37ffe12fc509" + }, + { + "msg": "4eed93eba775affbabb2b58f2798120e6b0ec04ea4775eae0e1c57d069fbca6989dd2c1ee8", + "secret": "e7c4c8cf18f072fa054628c5d8be22102665a7d9f71183143433f2d012e47996", + "signature": "d368434fe1824034b85c2b9dd7858252fac10660a434be421cc0df1af8c66c7772e6d8f107fc8f0244f3be2a990df0d8121307c199ee4efee8a7857e423dd50a" + }, + { + "msg": "5c05676ee455351f81973c9b4c1ec4d96c8af4b74986bfa0acee5b5106684f4df9a9a23bc12b", + "secret": "cc1de6ac3bf904e31ed7aa0319087c75b9110d78062eeae45594ef776a95da05", + "signature": "aa548bee20f1c4773fb8e75109472a3e73d88e270c317665ee5c316797943685e77697973b67852dda04c51288b7f41eb3f65a0e74ce46ff3284319816eb1906" + }, + { + "msg": "8e090ae89ce9ab91cc1ba5edf066f9c447a837ea1df448783e957c9de704cc1d5f5cbb10347dca", + "secret": "441816f870c636be2c5056e0adbcf50bf79510c8fb7cbbe4d6d1bf33433902ae", + "signature": "51644382aa396c18d2f4476c70bb8214fe65773e6cde4f2d3cde837ed9fc02f608a6fc5bd593b6fc19c9160b357554a94c0baa8ff700a89ba3e02865cec4150f" + }, + { + "msg": "dcabf3c0f91f347a0910de451177b85e67918349c790935c5b02724bbeb39b6de345727316d924b1", + "secret": "63a3f782413795e1f85e2792e0a232b407994cb2d53755fcc29d4aa7cdb4f9dd", + "signature": "067aee4382ae07f037b703b3cf6241fe1ba0e49a63946d8208e68f62da0cc8d085465b7a507a71d07dadbedd7ffa78a20f4db5936a2b45a127a7963e2215c607" + }, + { + "msg": "5ecd05f55f9d7c8759c643c3cfb5bbcdb419f2aea2d20eb42c7fa608edeb0b98174a70939be407a3d8", + "secret": "6ca91dcf0fb234616ecf53075e5d8562a83b33baa7b5fd86a4b5a4ecfa1acdbf", + "signature": "4d6810113eeac878e7fce611b4b8ac083664c32ba73356af2512161b9ab556b375489fce565f09495b71603d251dacaf2786142330eb1a3613c2c631679a4008" + }, + { + "msg": "46dd2959010b4c3f7ab4d58e11a00c42d85c95801d74425627b35afccf59ecda59ceb5f301694ad582c8", + "secret": "907825d15aafae6fb66e14e32da813ffaab707caae1d97479fd56dc97963f345", + "signature": "61c6b79d06501ad71d88736f665f50b3c81328a64cd9ab83fdd13fae3640832b88b8b1778291728ad575e91fde48b462609d5c5bc09c687cb7a154ba52ae340f" + }, + { + "msg": "802095e38b664a0a671e8697d3c8f95257995272578c89f4803872eee9046cb6b1231f087af1b76cf8f33f", + "secret": "18f57880dddff966f6656d0c71bdc44c8da86bb9ffb7d2ea06cd6722e061a611", + "signature": "298231b3dd131bcb464cc2867c45783cbf39273b736762340b52aeb8db38109ee201bf9e77c832d35d1c182c109006c3f8b25c75c9eb59ac65c27908a5399e05" + }, + { + "msg": "17aa19cd4079e1bff74cc08e0f7394a43f2b9f5bffcbe24a1239f58b652b601e707b7a30999da24549de740f", + "secret": "628973dc709961b3f654d1112d9bb5cee8b722ba1767fedbe47476288e5f963a", + "signature": "88452119fd9aab40f52438bfb96b55c880f8c44f9707b267ba21346db70b58e0730e40b3e8abf8ec7e095e313b63f09986d1489f253b645a943b3d7ca9940b0b" + }, + { + "msg": "0825e24307cea8a7d592ac5ee95bd6c1195762ee48be6801379c955ca9241615e0614708baa1fca64be2bf4faf", + "secret": "4143614b2d7c40e45826aeca4fccfca798492014333f6955d73203e2513423dc", + "signature": "05b7a6cec0361d967e0b30e23470fcac2ceb93dd6f093ad3e161545edb6b773c89439f1f1174bf698d640350c90869931e8298017519665588f558a2a14ffd0a" + }, + { + "msg": "4f67972a306263d9283e81f4933f9e93cb91701568062b801878178f7cc8b8db5754e3fa5884a7a3f5b534b33f48", + "secret": "dd12b27b2d8e2354f038129e8e32b943b0bfa2a18d3562e4e750254ffa6e341c", + "signature": "f5ff191aa06d4817cb9b891dc6cee60e27a1f68d46a5b4d8d31064cde81f3c4bf6e65a5c5348f2b1833156075d019cd18156e4f0c0fb8bef6eeec629a7560c04" + }, + { + "msg": "0b38dc19a4961c5862dbd4a72360378eef5e74bef3f6f36e66186a6dae5d62d13b013d9c82d4bd3a03019734597309", + "secret": "4c6b3d5c6acaf2fe97f0821934448e68feb4acc6610a4d3b0f53a5612fcd6017", + "signature": "ad5d6390a9078473c334fb5db9e1291a99d2b73fadc66607cbcb9aac44a065d322a8aa553b8c8cf05ce9b5fb211a5b5a45b5799c38b1e60f1ae9572a3d3df009" + }, + { + "msg": "06f29605ff7c3915238611911c42174ba60f518ffbd82fc7bda06e6ee0700b598f75d5eca702567192236656331722cd", + "secret": "1d05268763f655dd43cad53b90c9f1e076f6d5bfb25891b7adfc0db56e4ae367", + "signature": "fbc62aa456225505ed74fba70b91d9e71c98ba800a31787f5c53d31787dba203c843dfe779f703725deeaad49b60cf45f11bf162e6e3edad6f9c2fb470fb7103" + }, + { + "msg": "2edf54337e62d84504fbf716c211123def11c593fbb82b5db140394f32bd7119daadd7ebebe9a582bb2962362187e92459", + "secret": "a5a0815594d0fb0ae7489aeb872a9f86e9c3cf5e4b3251cc5c742db20f9d9e60", + "signature": "475d7d8e0630b67b559e633f1e11890f85ad996ca368d28e1b483607d4be916626ad417ebd71f74429320bcdad24f2614534e3e46a42a0e875f18d3ef2a7c704" + }, + { + "msg": "", + "secret": "2bc79b2c5e1f9da1b7e5ef4b0a63348abb7cd143096ed8b646091f110706a823", + "signature": "ffced1d14e3b15874d33e17b2a65f0baa5f430b819b06e387b9770aec2d4fea8bc45047c1ee666cef8a01c90735b470735df5a3f416760fbbe7b12cff947760c" + }, + { + "msg": "15", + "secret": "005e9dfa7d924dfbf81f124967648f3511445d3c7785560b5c3c7300f00473ed", + "signature": "e94579da3c9c51294c82a5a2c3e2d976e206625381c5adc37207f792740b3d42d6aed03c17f76d5657ef0ac3a3fbb3d9d7d1601005ef59df54a46037d8163909" + }, + { + "msg": "7700", + "secret": "77fbf59fcf9cd985141119093b069cc1dbbfcc87b3a7438a24bd5d5087d45b73", + "signature": "36f67c15aa0d8063b24bf1c7818153ccfff8956825de9320a8ee4b0764d3db2eb23edf55d6ea3276d639f0c1e9cfc2653d76c154589237e8c664920c0cc85d02" + }, + { + "msg": "c6704e", + "secret": "8901e92efe1b85330050c097a4d86e5616d6584452c5d1b5d5674a31af24494b", + "signature": "3673621bdead6f25e7595afaa59fee21c3910923d35725e633b54b5cbda0de57a8637f12761d6e51abceb3615d42c5b20bcbb7973ec4c3c95a40195bd0ca1607" + }, + { + "msg": "1268912c", + "secret": "551896b6d68b5343052f37df5b0fda391a1bd9092ae434b65c109e4a99ea8b0e", + "signature": "6a6671ab03eefe450affe619be0f7434b7627dae820333e24c906f0c835e741b4b8ed2bd42839e03e75916da80672e7e72cd3d28e1f960394f8f2a0519099f05" + }, + { + "msg": "ea5ceab348", + "secret": "ac7c38cd8d69f7c167c6e0c41c465de2d58e361b2f90ee88abbd13acd0d38988", + "signature": "0766fbd2e35175bfd6e0979b744c9a5a3842b56e5a7fb3ff629e32d1dc6c58a353ef9e659394c0d5958b7b43a9eac3b0045d74c25b0bc023b6c63a792be85301" + }, + { + "msg": "84b1b81db406", + "secret": "7d0ba62f80e1f29a9e4d48d09072cfe602ba0594b2a5b7ef4550c0202799c448", + "signature": "d3261db6eae8de661e232c0b09af93c15c852f285b835c4468f9dd5c7698b00ddf5cf534ac1c832eba9967c704129452a8daa0104decbd6feddac27fc5fc5f02" + }, + { + "msg": "c5113fd8351885", + "secret": "bd352aaa7d54dfe3cb9f9a73fc03c612e206ee6bcf122e93d20bf39d85cea720", + "signature": "ff1d07ed55237deccd1501bab4b9db2de8a57aeca58be19905b2ab7431a06f81942169e84c34d50d9bc78c980727123d59b4d8a25fd5ea6f0a4c0d48e6f13706" + }, + { + "msg": "ea3e8afbc7134e50", + "secret": "3e1102decde67e0523c55a37abea8091b7adeebc964c7787d80e2cd9d8a0b3d4", + "signature": "0fce5e6d2a70815122b62ca510c513f99a994fa27871165b8dc8b29df532929a0b25b29591435075d047701bf3397d2647fa764533b51c09141358c27672b205" + }, + { + "msg": "8e546647aa4ddd302e", + "secret": "89afac616fcaac740e10e26da36dc99547cf6a080cf55611b261e7ab54f46d2c", + "signature": "931715e2b75bb0e0e741d4223556bc46090a9d1e07a53c0eaa6dfb4a9a0b1118957ff666031b4746bc1b96252c73425c81166ed2d0f57c03628cf8a180404b0b" + }, + { + "msg": "5731f264d258bd6b470e", + "secret": "a35a6bc5261240d7184bd1fed2258d4104ea56405e464bb7cfd7ec2d3e3d32f5", + "signature": "6da44fbb0cd4608decc5b32277f8a9bb16f33a31efce58c47444791af87eca3a1c1ad972504c4b183883f8baece7203d62e20e0f05ed8ac4f5147a1523c62c07" + }, + { + "msg": "837b91252ea4edadcb3cb6", + "secret": "eddc007c156544c257f290d535c99c6c28b8ebf2190915e77a086a5d85f20117", + "signature": "6c3f4043459f8fc3c294422cadcc497c241fea21e93e921e4326cc8dc0c0d8572659f1d79a070fba8bdde72d9778f25e0b3ab1afb74aa6df8f6db6dca077350c" + }, + { + "msg": "6ed8180813e185b1e00cf00b", + "secret": "7ed2d142e76434a52ba3083a057ef39ed3f7fa1dab98fac26b6a4cb416cf40b6", + "signature": "ed12ae2dde8e3c14a2f8c432ecf55561cea069c380a2841c7e95b436031bd91b056bab4d726b72de65b86e31a15232b0800e8bda98b0cb0f126dc6d3e8fa5a0f" + }, + { + "msg": "f91aadd09f46a84d58c22fe00e", + "secret": "198b6f5d7aca4e49229ff53b11d292563dda6b950f6bd23bf1d5a87d3778bbd6", + "signature": "c1fcfaf2ae741b3dcbd7b87af98b14f98afc98f260fda1f16b54215ba55ca73a0d8f3c99fe6ec867dcc4a9e0206cbc8f95e8d60b612e636d60dacbf5f7cb110b" + }, + { + "msg": "b68aa81da8800755d4efb9d7defd", + "secret": "98d4d3ee424fc2d01f65bf60eed223862dbfd1efd8d5771f1f3ed6350e2ca30f", + "signature": "41a369ea3204548d7d09c7f5e9c3e8111448ca80527ce6818ae606e9f0b4f4ee693149ba349df4d98cad0e8a57f9e36ce880ba0eed374d5082b51271affda80f" + }, + { + "msg": "9afd7016f4975f838e63f2fd6e7585", + "secret": "8236d0768b2a475907a8c747a4e7d83fc9690a49358531938c8022117feec2fd", + "signature": "92c5b4d63f064f90c2a6cdea5764f44ec3ba8cd9bcb986ad43ce92e2e058666ede07158ef3ac44c1da48b915381717d722211f196d83c3eb4a84acc0f1857205" + }, + { + "msg": "b71a5af398fd5be3b4ecdae59f88fdee", + "secret": "547f853a4f8dea7de28f6d3af5c8c077c4287399839fd5a0e8816508f26116fd", + "signature": "028cd8f41ac0f8a0c51b43492cfc4b99d7e4884ed9031978b8a72fe18410d1af0034f3597765c777168ebb9908e1e01617da701dde4c3baeccffebb07747710e" + }, + { + "msg": "ed6104a35130477175a0141885fa00833b", + "secret": "b21dfb61b16d96e78ddbf8db8044e04cb97f63b86010bef90ca3bfd83708578f", + "signature": "b646e23ce36ece52c893a40a709e6a8dd80dbe23ce66d1c86df315234337daa7f09493a3e1a27fd6b4a5b613d79ef3605fac93db07b6a3f692b15f01b8567909" + }, + { + "msg": "388ee13864adc1ee643661ac250f07f81ede", + "secret": "a3d70d1b6668ff2070d07d6e2e58b0072ae76bf155124a650bf5f82307dbc022", + "signature": "3892e2538db569d39af09f15c40f6efa83ccbf4bc72f5560a667f382c62621b0daf6fba1d536b12b330e343b1ee0ad851a20660731e06139530f76d2941fea0a" + }, + { + "msg": "1517844bfb0c0e58d7d4bccedd0b8b57b6032f", + "secret": "2042eae92fe7bc36b99f2c459c8a8418c9b238f31f827eaf5ee255d831c05c1f", + "signature": "3f75eb39d9db14fde28dce006f0750ca1abe62704100d3b0aa578777bc02ff770530079dbd6d5e31d5c48805af69e85cfe4eeaed55464cc4cc9ee2863afa8c00" + }, + { + "msg": "d79f2e0f0a33013deb71cc2978ee1d7246251408", + "secret": "597b5ac93f03f85281d74b5f57450ee294159f5fe1da9aeea8477442594d5908", + "signature": "3392db1c13639c90fe1ed2fd69a125dda2beb15d3fc92aad1e32816c6bdca01b429505d307bf745adda1de0d9c8ab4d23fdf561858f54b7485fe2e866c747203" + }, + { + "msg": "9e2e41a59d252efbd4b16e66d15b46b2bca120b6a3", + "secret": "cef558c5e37165c090cc354251e05e0e13e50451eb3ff3b675c236c19a29a378", + "signature": "c68dfd2283bb939441864180348375e9090cfec7fdd988715674428b2f8530cd7d65abd90d06983199c1b558078f4c80414da0a656fbf93be35d10097d56720e" + }, + { + "msg": "4d9c3fd816935485e810517a79503b5d44e9e00fb914", + "secret": "a2b3f03c0a5c34444a814b0a399ad0fe65f4181fcff6a59f2d3ca061c53f452e", + "signature": "3a2bef91bc5fc230a7b99a5eaa88054497ca9644531857f7c7aa0ff99b3e9fdad01e12222d68810901e39739a1be89840f0b6b9fb72504e52115349af6d3a70e" + }, + { + "msg": "b4d18e314a0649996388540bd0ce67d7cf4d890ad7f2c2", + "secret": "a3e1d7e3957039114f63db9b5a9ff7e38ce0c85c679096707dc6900441311c7c", + "signature": "3444c1a59a87a08de6d5da1fb8612f93ed4f6f9b5d61313769115a91265cad6a9006b85137b72c9aa64fb4c401cc075a3e884667cedb933f36617b2748046006" + }, + { + "msg": "71524c8eb7ac5defb4812589ceafa852a33442832931ccde", + "secret": "829c2addc95af0a91a400b1414a4d7c8eeb310a50883f6aef12f429dddce422e", + "signature": "14d822257b811ecb3649624cf0078d9b05871c34612bcf84464a11d3d442a399142a5c574fbd9da392084dce2635a206465996c751bc14d819b5dcb74082e30c" + }, + { + "msg": "083d009ccf4bcc9d562e6bfc34c515f0433426d5bc2aee6491", + "secret": "956dacf5ca973d8d0a80172bc87eccce6bab0de83f275b22b3b8b380c470f713", + "signature": "8c0d2c4fee7a5e24db678e4307294d13f79cdffdf6f59229a830206b404d6df53fb4918809b86a2f93a50f48b56a9e1de85bd5a8c8a75bc650b7d77f143f8a09" + }, + { + "msg": "7d68a31eacd5a6de1fed51a6a78dacda4714095d4ead6b3803c5", + "secret": "54a63c9142170aa9b8cf664233d0a13e08e9d945f315b154e4dac2ca7f24e6c4", + "signature": "04f9b5a835997a94bb60516caa92e238931c23379bcdbfd7315d283312decfa4403a81a61109b20348507e9eb5026d266fa7e0b9f01513ccbc50301521e6a301" + }, + { + "msg": "909d8cedd4dd6fd844ea48979bbf6d6eb1500ea35b0a1e03fe42f9", + "secret": "808dd5c5c6a09bc1999d563f61c11744d02e2db292dcca1d35b28b8e95b648d9", + "signature": "8bfb815806a6d537a130905161910264746ea4d3ae3f21d00241cb951a33ed37fb321767c0c0e99508785beedc7ebb1b70c0159a79ac52ba723768578f32f109" + }, + { + "msg": "d8a90c2076459a6a3cb028454072304255b0160f111842edf3e86c9f", + "secret": "9e77296d4eab9b71ad1d0571d07062ae9e04e9ce7a82a5adcd501c234943f6c0", + "signature": "532af5836a18addbe867ec6d466b8236b72ef0442a39fbe757a4d92c39c3e42c68fbd405b4f2bfacf6716bc503bcb491398c4d7a53bceeff0b27c084445aba0b" + }, + { + "msg": "75329831073147d0117e47ecc5f1f4d8350574d796fba818c202eb490f", + "secret": "d3885adf94b44b433e839997e0ea2bd75ae55577369983a81461c1ccdd132422", + "signature": "4996c2f686f3b4334115ed1b5a160e844038301ee8846c4bf8a775099966b6a0fde9f3bdd30d7fd5fe3ee3e2e28ea4082546ab14cbfa9d8ec6849d01539e1d0f" + }, + { + "msg": "78dbe7ec4799ab24cc0d7e8e0bab374114e776e6ee381d6506ab830031cc", + "secret": "40fc403dd4f6657f3eb82db5f6c4320ccfd93550d2b8e056d0f290230527d62e", + "signature": "4f226823f2ff34b2f13bc7e0b88a2003581977d6537750e740d4d2bf2f7aba55fdfa730008e37bf083b54f6b8c19e889e4630c62b4f51517d364c979a86df10b" + }, + { + "msg": "4cf9d398df60f62f40f1bbe2bea8bfbc44ba7c1a20d3958e4aad9b5c2514e8", + "secret": "a9dbcbe1df7cde66c8a39acb64ac2341acfd24c78ebb7ead74761c058531f4d0", + "signature": "726564c95dcb3d5aeb4bf82737a14eaf36b131d80e85da5580a0cfbd7aa24fe40c0326895b5188f574a3f22f9cd538ea933191e022214f6f3945e2ec57d56005" + }, + { + "msg": "b0daf0583c1c08418b792b875860b6303fd5e2db999cd20c97121d0afc262c98", + "secret": "d9196bccc5dbab9e7ed0868224a97bab2bcfae7f9ec2cc2c27f8a44e1f1beb9e", + "signature": "ddc1bd980982953dae41f6ab3f7d329a15077b4fa6e43e50cbfb1c1b71a6866092ead5310927415d87061d2113dd045f436d51f5b0ab0810cb5eb7cc90faee09" + }, + { + "msg": "0f5e53a2b993e88086c2cf2483ba61f3f700445b5e072e3aafdb19e6df792cddfe", + "secret": "fa09c12ad283dd16c3718842e4e6cb66a1826881465840de75c81c94c7d8a405", + "signature": "b6eb3f94efe53c85094b28c1137dcfc2bf3d4048f167ea1866158e2abbfa2951eba663d5c40e5b307ce33f827778bb4d5f77c1c29b229fd09788ca184e92a305" + }, + { + "msg": "466e01d60ed15164a4496b66b71ebb12b1afc60d884bd593d61a33775284f9cd1f04", + "secret": "0f23759ea11f1d3c57d3f7f907dad7df618efdce665831b222f2032cfe24c6b4", + "signature": "c10af8c93621c0649d8813120a5b6647c4d489e9b61fe7956ca852baef4be3d44fe2e8f1e3e0d33c33bd972d388ae1a6752e578ec861cce8e98ac0c3b16b2601" + }, + { + "msg": "06db42089304d50bc332c99a731c26b033a524ee38da75cba6f470bf495cb0a0304ad4", + "secret": "bf421b700b08935b93e3ad1e4220059dfaad973557cddc9d402404fa6e53b702", + "signature": "326f8fee62ac5997a173d8c2e5cb656c60324ed131998f8fb94e2488c2d3dc67af1eb85e617025074ac802240783443e66cdf1a6b808c497e99f6ab301cc1b02" + }, + { + "msg": "2ab5bfbd471e5e75da553139481beb87f61a3b2ee06f481abf58f0468dc7eb1772606c2f", + "secret": "4dd19bd052722a23cd574e4ef20c39ec566376412e2c3ac7aae9233a7c0f81ea", + "signature": "ed66759652e3ecbcd57f5f51c91af39eb5c7ced908914cd41a9a56c7fba056752c9c7c3e1493ac49b3a6f93b4f22967c66ec585062ec6e197b2f69d8791d650d" + }, + { + "msg": "491f9ebdfdc658f60eb2b4aecc2f6df16aea28e7d2faa2a8b4be4d51fb691560d46cfe1cbd", + "secret": "f9d806909f5073423bf3c99ac009a6da8377fcc98259ea1874b07729f8108d92", + "signature": "ee3b850319e01b1653e7ea8a27eca322d6bbc7205b77e0d3e68cb256d8ee655636bafb905e1267f6d893cf17d256438b843488de94663a4cdbc837dff5354e0a" + }, + { + "msg": "661cb7fe373b0f4e406aa834f93f9cf65c97ecedfd6d5939654588439598ea0d3727aa48d2cb", + "secret": "23d4728dd86637edd7bc9c982ecdcb984ed49f6f3989bdd22b9bb5af418a89a2", + "signature": "9fb9cbaf8212d60cd72e114c269e1eb3fc82b2b44bc5fa8e06e99b10c52a34485eca7231b20955da442930108055791867dad4b9de4bbadb252fd3169c827c03" + }, + { + "msg": "73c0f459d7e39a1d46be2be197e9ba966548ca96420505dfec30f17b62826bbc301853cc1d6fe8", + "secret": "d64389d6e08407220fe5c645950f9766ca4fb52268f0b1b351e77c55a42dd01a", + "signature": "200722ff51d30ba48be9b4bfc4a3f0aabd23de60ec02956ec8eedca838ce40452740e6e618d86cb4a18ce3e837e19fa005ed3b5efc5db5294eddb54fea7a6202" + }, + { + "msg": "e88023331860527fd016ea9f506c965edce768176ef6a88cef284cd1716384b90da56012885e5796", + "secret": "5e664ae962b824fb28b44b4ee39add25555a65bb72da628cae3403c4727c4eb3", + "signature": "bc17cd458503d85e541521af3e4e41a2e94d2187259a538421714b62f165baec951bdb4e2f1a1d7b1474db031a36e0e7882decb5fef23069bb17eb486c8b560a" + }, + { + "msg": "bd506994d55e6806331ce320c67116818c19f4166df82c6d81295a7ac8764a2523f8ed9eb52f6236e6", + "secret": "964afd25ff6ae08cd16dcc0f6c1d6fa2331fa5eb8e5aed2b531ead983cc4ba22", + "signature": "aa68839de2669dd52137d2e4fb2f27993815338fa97e82a828e21df9ecb0b7aa6487c6371b2b2017fd8399f3addd54435a4c207df89b219488cbc1ac32a10507" + }, + { + "msg": "6165baf9695027057ddd5fc156e6a488ff50030485362d22c350d28de14dd70102d61ae79532cfe7e7ef", + "secret": "16b72253cb918177b3b4dbaaa42e5d6b9bebf8668b9b9cf923905e25ce15043a", + "signature": "2fe7563c0f7e8a942e265884b559803130eabb99d011ed41acd06690ee0ed22bb84cb8abe0c65e188b455e74656bd60128d73e41139de07288c5b11e74e04f07" + }, + { + "msg": "1fd286cd7b8041eb8e95339df03817f1020ba73cc8399ce75b445d5e35542ba4c93cf7926e9844ef16327d", + "secret": "23b6be308e4b7254f41ea8990c27b4b2343361a6b724981e33eb9ad154a8d99a", + "signature": "c07c1c56c38a29b65810b32e9334dfccfe546ba231e1e261882ed96d9a2580f4d5e5aaa1f7cf579d4c497ebb4445b9e08096502414eb2e7088e031025eac6002" + }, + { + "msg": "6cbbb7324fffab6d1ca6b519609ba79a28d25efbcfbfdb4fcd4bc8bca8845117349453002d0ffea0c7945e32", + "secret": "27295f928229ab3598bf59d90998d8c2fe267d546b587cfbf497951c8682342c", + "signature": "22e59619225c7c47d2588c986edb4a8a361987d217b71276e14acc88e8de5e2bc72b118750be6df94c8c394e4daee28a5ffc4710378789289d8f713b877a5000" + }, + { + "msg": "d351b6b57e0b01444413ac3d9cf7ac1ebd42666fd66e8f08cbe06751143eb398892320057941dbc50d746d13d7", + "secret": "f69e27942f96b98d8b5c2d4ceb929806a07a9998e59f641157c24b5b20011c66", + "signature": "36ef3128044457272b67ba94bd3b2d9abe6fb40b54cec2948c7fff2830998aa16a0322c80f328d51ce64bf69f360d5543cdb731fa0b03c048ab7dc49de755408" + }, + { + "msg": "0c02a3962a8f58057d938c66fb11d6ff7817feaeca84997a7b4dcdaeaf3929dae7f91de39584fdc9716ff526e3f5", + "secret": "03977d96781d00e529a68a3a701e00a41d72c7bac940a9a8f091e392da2da33c", + "signature": "8debb6e8f77121b9f020a6502e9cbe87987bca131dd8ca7b0da0b750653eb7e1e9c343ef4d0037d12a04155fe1776a9d1dcc3b25c82dc19d55f0edc4176e3f0b" + }, + { + "msg": "6699d86e4118d0dc72eea758e89d7aaf123fd175e961b301f2045caf3e8702687ae1f4accd8be6189ee57336e6c47b", + "secret": "7449239585dcaad79afd0c0bdb31c50f6d06433b9f986916541a6d7951b4d718", + "signature": "203c2985541b5b4c4aa329c1ad6f3478a7fe8b339c439ed61fa4183c50d82d2405dbb2ed5574ae9e7120f6b7b6ee249a0fd950a2ad33f0a52506da935966180a" + }, + { + "msg": "3f8ab7c71215ffafa2ecaa90909ec276be8214059f1f59d94dafd47aedca0ae27a744f4021f8d921e83fdf95b68d2d10", + "secret": "e14e9b9b05a74d78d3587c166c6bb42506b133b054c8fadf8be54fc10bfcc633", + "signature": "b02e85d9c61eb89feecff7012ec68b3367c701a07845e91c251f8c75fb06fd7e9f2ac5c40e5e47795c93b4e184726e51be13c28f971441bc0cd9cfae0069860b" + }, + { + "msg": "3e1dae215aff8efac511316ed4363b2d4ddac813471bc7d43c6b8ebb58322d17ccf0db08fcecf4b1096e73bb46daf0687f", + "secret": "379bcf26e38f4d33e7d2218ad843a404710f8367269197626aab2727e03ff60b", + "signature": "487ab592c44a5dad6568802e820e6d6826bf72389516d6b6750cdbd39f1b98366b449375ac2ab94c8ca814ddb70b5e382fd74cff41e76786735316b72b9aed00" + }, + { + "msg": "", + "secret": "3649ece8427b101cba77b060c3fe59a1976b24cf78e371dd759eafad7fa534d6", + "signature": "cd86fd686986233539ecd31f8cc7c929d4e048e1bb178be099ade8f582e83ac6801b3a4f0a55dc8ffb76d33f01702d507e05210ad8487bf07e91fe25583a8108" + }, + { + "msg": "95", + "secret": "ca397e3535ef63bbf9de34bafc10bf39638eb7e33fef42d508747a8960574c18", + "signature": "51395032ed5558bb20d81f32c07eb890116013281192957802f8081c4373bdc70ba55af063e817e00c6891ad5287d232be474d385372663cc7b5e1a7790d420d" + }, + { + "msg": "828a", + "secret": "7362747987cd6fc2782f5ffc37467b36a949bcf7c915211d32a58a8105e1b7dd", + "signature": "15d9a5ab247ac93e0ef69bdaf6e94a12054f1d80abd919afb2e87499f1598e853c6e40707cc50546f3ccb4b3345f66ca8d5077adb13ee48f7eb7fd53592fe307" + }, + { + "msg": "715b1a", + "secret": "c04fbeab76614eae5c770886b3f8c93a62f7f1a1400ae8b25cffd74ec1c8ce48", + "signature": "d11c34ddef402522823b2e3abcba2b689ca52ea69d075747453c68524915df3acee345a38700395ec2d77d59c0d1b5d594af09acc30341ebebecf63263806602" + }, + { + "msg": "2e3eb5ba", + "secret": "906ce921888483e5717f3ce44fb90a17333cdd474345ef39b8e5b59157862999", + "signature": "98c002e1fa2e2473f6ab2660e1a4db4e641b5a910bafc67f57a7ab7808a6ffc00eb215ed31227d7f82454809fc65551f1ec714075f9aadd449b8d5fdf163ed09" + }, + { + "msg": "ef55e39330", + "secret": "7b7900fddb6b1559a8f1bc1b6a1e68af562300201709a95bccdab3a1d19a2cea", + "signature": "556cf6d9e15191741b2a6a213414180fb5355d752508052020aaf091db8839bc7e7f64aede395f7740857c31387c328e64a0f9977de28e70c5d2b01c3d5cd80e" + }, + { + "msg": "c94f96d9c36d", + "secret": "1f3be56578589aee2805d73b66a89cb05b557b4a219a306a4c251ebe8cfe98f0", + "signature": "39258bc55e2847f683490e43a8a815aaed8ccfaa2689a6976df257d0b90442ab8e3bf08835046a57cd88278a8739488c3f2dcc0f00c5924b8514e5c128912f0e" + }, + { + "msg": "2b5db387ca22e4", + "secret": "4b1b8a820c051473c82d609fbebcc62ef07492d9bab7c3e1af2911287cf373d4", + "signature": "64e4359507784f1fab477a16103d94cc0eeed69a326bc5d39f999a7b05e5be5d723da6bc737be1ec96410e1f4af2bf8390b9a64abf4bbdef9b2ecda12bc7760f" + }, + { + "msg": "86b340ee9ff1062e", + "secret": "4c99aa0bddae4720d9b9d3f609c6c96b29b8f130f55349e76202e83713c6ea5c", + "signature": "51797279f8208fa0a58b8efd1249f364aa94f96b1f9abee2312e7564eff0b142a10c62fd9b1683307598bad3b6816e799dd41db87eb06f630e9e0a07e2ef8b0e" + }, + { + "msg": "0e6fa0c7fb33910b43", + "secret": "412658bb90bff626f970210f002b85da4d5a57d21ccf3424d01ee7aaa5ef1f40", + "signature": "99bccd2c54e099f1ca64fc6b2cccf770ca8e1bd36e463b09939134450590751dae761d626ac0f0be9eb6688cbc938cb7c3bdfc88648a2b8d680be4854e55cf09" + }, + { + "msg": "0eca84252317c176842f", + "secret": "c8465766268fa7075924d32a784e9c5401049290c0b792f283c89b73f7812c58", + "signature": "342c73e5894b9970d36a54f1aa20c2d9d7e35533b9c9a9e526b8ae1a3004f724b68cb8bddafa01644e00eb44b55e8cb0a5f519698854fc114804838f47bc0f08" + }, + { + "msg": "3dc672063165fee87b210f", + "secret": "fc0c8e61d89ea2ec1b3484f34377d1fab75099325d16bd99595aebe2f3119e73", + "signature": "8c3630e8c0a0cb71474f940320a9785fa1d6bef5c57f0955d000aa6da97ddde7ddd414670bb74af2c20d6dd79153743315d830ef52f3e54b3a461f953b506c0d" + }, + { + "msg": "ca6cf782a1d1829b891fb08c", + "secret": "378bf5829684cf4ab75e901068a36ec42140f469158c19c76abedf07f1105743", + "signature": "d0f0fcb2da1d9c719e5220a020c2f1cb679225fbe6a3a2de94514112ad05deb52b2b2fb8e6788780356b3b0633253eee7addd8bd9aa448679bf5d73eaecc5209" + }, + { + "msg": "20214043a9a0c96613b916f76a", + "secret": "cacf0196a4a13245be33ccc846ac5837fd15a16d0f37d1333c55e79dbb3321d1", + "signature": "527c968a607106fb6eee7c2f9ad8372cc0824eed4e98ffac0b1a5c12fda58f9892d00adb920033a5b24f19102cdd07c93f2dae1aa65766b5ee8f52407f62c90d" + }, + { + "msg": "5cad140587d642d883c6f016177b", + "secret": "6014714cde61671984639187a856c4e3d1af9c3160b8cee9a1bb0ee8179264b9", + "signature": "1582d6c3eb0f8bbfbac443e3497f3b830be63b7269e07c5a12e24db9d047041eb84f723191ea5b207d1bff6fe24971ada4a739d387bca29d4e1ee67df086b302" + }, + { + "msg": "46fd983aa0e68c3c7055444a44e1b3", + "secret": "d27d0d674a7b89af80a1732c350991f5f6df709bc4f3d8b8d45ad31ac821e64e", + "signature": "22658b246b4032fae8411f0faf40006391193284ffee7229946965465e3c13c63ff939d8e631aaf81580d164e14014d9f55656975933526e301c5a0fb5fca10f" + }, + { + "msg": "f057e7a602e42806c3ae99a1e84e7bbb", + "secret": "06d24f20c4799b8bfb14740f58faf6ee2ec6c62bf4ec2c704f2e033cb698d7b8", + "signature": "c9514c37616a12a06ca1b0197183dbae62ac84bcdf3e28e1865b66f1bc71e35153edd8fe22498b2202bd98b857c3ab3b6cf7abfb41199a90ad66e01535320202" + }, + { + "msg": "75fb64579ad73047d611f8d75636b34a94", + "secret": "d6ea7f197b3ab202b2b5ec20c6dbe164cc074e107564e74035febf9b0e781451", + "signature": "ec3f26e245fe81ca8f264c7d28727e0280c39e9234cee5cd8ebe1dfec9c105eaccbfd09aa705a1133bab46c3bf97448d639e39d43caeb90d12c11c94c3733108" + }, + { + "msg": "50dd064a197fa5ec6faf88c9e0853ff26c6e", + "secret": "36e3ab98d9707b36df670a24d036d719c4fc0879188f4e12e0377956e10f9366", + "signature": "f12a74e957d02e077639187abb87aa6dea410d332478e8293c107eb370ada6dc30cb2b22e21be7b272b08f73bee727f86f825cc55eeba96b508ac42628a2db00" + }, + { + "msg": "655ae28d05b42fa1bffa4b49dfe2667898a003", + "secret": "9f7f9f81ffba51b119b7daa1edb65f35fe6030b37b59325af8e192febfbda15d", + "signature": "a618ee0708aa88380d7a3ffc0b1c897dcc58fbc7321f436740257242f71fd0370e37b0ac2d4869f775483616ae9f0becd12136f94b22ea3375d5d466f4083e05" + }, + { + "msg": "2e81045378482d5cde1331839c7a96cc2f7d178a", + "secret": "7e8d31e42229138bd9cd42498c249333431c3cdae1da2b836c16bd168845e3eb", + "signature": "808b3625afc01aa5efa24ae12b416ecd671c946e666f01fcdad9c499f4a307266e0622ae659632cd6c60fd5a8ef3ef9bb415a8f0ed1f1e57e5d5db5de9231803" + }, + { + "msg": "5032a7fd8965c879b80ef63afe838b9345d42085e6", + "secret": "485d73344925efc16ee233cbbcf01494472ff9735e3dae60d31fb79c01d0bfcd", + "signature": "9a93db7126b7df17d26677560ec4425a4b92e20ff436449879b3fa0f03dbcd6443cde8c88f89dc58dc22f9690d1544a87b7fc3e5384d97639c89bfba46f18008" + }, + { + "msg": "8e9675bf7fd7d0b006463ba284413f71401be341ecc7", + "secret": "c04163e216b561dfeb6c0b595be01d9bcf02f82c9149c8d47922dfdc05571abf", + "signature": "7e09f1329141f6c8f808954e63ab13e60cd8b4cfded761cb3e418e854bd4df07f6900385efb79d701c0e0c108c6fd620f13b9acf228fc1dc489ccf10e47be70e" + }, + { + "msg": "797602dbd67269f1299f5bcba576d336b6aec85bdda1ef", + "secret": "031789c76b54d41ce9708d92c390f103ddbed48ed303d79b11e8cbd6c3fd51d5", + "signature": "c1a620832f610664ddbcb02bec8ccb529afdb85f4d6a218cd8f5bba031008a5584d395d7051060b32f6cf2abe534f147048f05e3adeb62b175e7baf92cad0008" + }, + { + "msg": "7f2cee6e1a865d64b74606784b1d4946773b8b8730d57816", + "secret": "6ab579119045a31c7a6251433a08209342d90bf68ce208a978b92607928cc5c8", + "signature": "2ba2c38288bab092a68a9bcd52363f1292e08a78fa02b8f6ee67900849ae342ef250f6e7b669fcb16484c5b277a48acaaa9fcc78b7e5e7c34af7665600779509" + }, + { + "msg": "0e47bccd76bccb58de57960234fbfae391089bb949f36d82d0", + "secret": "692724dd8bccbe27fb007ffa85baba8e1c1641a1dced88c222e1d22d02179304", + "signature": "edc5d226107c1a6b6570c4be462395557d3ef65a1a761544b8bcfcb851eb74b6c9e787ee9b13bc723d297f445fdd02d7a640e079dee9fd05529c5874d4a88a05" + }, + { + "msg": "55da20a51582e10981042d2c35cd1321a08b3eed09f0a19401fd", + "secret": "df2e78ddba0fb729ebc4fc2044fc35371dcffa20ac135021d4ec2c1f2db1e6c1", + "signature": "98316215a056c999c0775ebc215f7cb2c6ed75bcd6b2829c82cd752853beead57f35a7fe38800e84dde148459b5e3b6d280f75f258b494ff01fcf7fa2371c800" + }, + { + "msg": "6a19b5041ce0cdbbaf2ccc252b202deba7f6a71a1cecdc51ef2aee", + "secret": "9864f612767dff80a0241b4fb56c7c625baf94c28c8026637dd2d3f7d3fc5573", + "signature": "9d9b5326283b0532ebdb5749acabc8948e29af7a2e80dca8b49d3bc26f5343e878eaf12babba5adf368db4f35677eb43794292d4037335455674d277d717fd0d" + }, + { + "msg": "bc745a47275a853b281d7fdc1c059eca512549af2c18a228437bcc04", + "secret": "78afe3ef521a2088d7a56adb5ba43eca4fe651965fe61fe7d3a6e5970fccaf61", + "signature": "d9e324b313521798fc7dd3a14c891c8020d193fbb1333bf49c79fcbe57646a48fb4b16b3095cfdf7c5ad8ecc579dcc9fd7adb1d05cad254b6d17a83072a15c08" + }, + { + "msg": "ec60035f58f56782d449f91200bf609823253e21975ae9d00cc10881a7", + "secret": "e783b58713de2b12a5ef4dea6b61fcac29012be1971e750bd283df935a7f7069", + "signature": "34c12faf8f6a56f6ad012d483e097da6a51a593b3938556f4636678e46e2d146d292537b8e775dbbedee366b8d1e2719dfeac6ea07082df57d65c3e7e2512a0d" + }, + { + "msg": "2147fe43f82ddc3cf962b88b2de52dc6b8ea7c29160ccb06e14d42fdb298", + "secret": "7ac8a03385998286d377f75597dc52e26345a2baeebccb47d18c3e57a5d30f6c", + "signature": "1fdd00ee6eebed1a929d3a7243bb57938be5805880fbaab12aa369f96f3ab74a3250ccd553aeca3264cb91258e81c1efe492b367c4d151301242777a96f3cb05" + }, + { + "msg": "5044b534715b70f6666fce3badabf7c6780d1862d2166b3d31b8adf1b9d7c9", + "secret": "943cda4bcc42b59fd8bbdca35fd6d193491d53f8ef90b9586aa45acbf512aed8", + "signature": "450c1de39da3ad136e96a0c68282477ae8ff2758d4cd906c939071748b0114791d3c2fbfa15f191a3669a35953a193340adcd11d86d47f32ebe569fb35a16008" + }, + { + "msg": "d3dc1f82be80467cc3e0c8c511335d16e82271835f7d4977fa2669fce8d9aec0", + "secret": "ac729ec4905ba157d23e6610eed6aa2f0446cb270258d3210ec9f154c37b374e", + "signature": "d2a31b63fdcbfc3b57aa5211e632ae8f39d728ec32a063bccdf89a32683d43bc5b8980bb9c23fc9674486de9bce337ce3efba49b0b25d8df3d1727d28c063308" + }, + { + "msg": "0d4ad4474e1adb9918c38bc10bcbf0887e5a660ab250597aa063ef1ac2dd50ba3f", + "secret": "9cef9c04420ba45c1a309326fb833173d254261ce96edcbfa17a20a4ec42a5ec", + "signature": "5f0004111ae819bd1c5c4d88cf223b61a1a62d97c88af9749c574485f1bd794800a99bf1ba715c2674cb459ba68e31092b850fa3e0193418fb5fc973b6f1da0f" + }, + { + "msg": "ff03abd4ea9e74abf17dc5603f6f1eb98562f1a49fbe4f7ecec385e2529f23e02ced", + "secret": "f10016c5c4a4d3643b3093e1778dfbb13338ad66dcdec310c37d0f842f28f668", + "signature": "2c2bf167e05db1372bd1f7689ab2f482d035160f6e7784dbff53a4eb017531e56ac9a7278d8109e7dac5d60650a58d4cac15ac6f2c405e1801a2514627da070a" + }, + { + "msg": "4a241aae0a7568b8e26256e38cbe15f700fa9ab7461550230649cfc6630c495febcb63", + "secret": "9d6c97933f01a446dd63ab7a3262c5773d394d45e90d77d25b59d15fa963564c", + "signature": "68f7bd28dfa0ba38bff4e9783b3293af1cbf47154897a1a8d5e576b1ee547d8fc9bf709a5c68145e7c7db419007292773523079a4a4d1c312cc4a7ae30903307" + }, + { + "msg": "513a6af466a81178f592ae0ebccda8b6acabfbae735c63b20faa57b7f2995baca34d5841", + "secret": "169966f59f4c7c0e27883e24ef79ad20fb6452bd4a2ad2ba0b4b19643629fc35", + "signature": "8a4aed7c95f1a6ca9167873878074bb6525eaae04b1cf98053efa2fd76e070f58c9a44517ec40d6c888919a74ada8743fad674d5cc0471900b5ce36a282ca10f" + }, + { + "msg": "b00a6c1eec3c750d1645a57bf9eddf494aa43ed666b69d4b8b8f3828bc972281b446171ff2", + "secret": "385b4f65e9e428233969c76134697f4c255993376254bc6ea1289f7ca1357e6d", + "signature": "37fb8005634cdfcadce7275a572013331f16ef39cb5c0f81236014dfa056d6d850d3213768988195f87319ba43612e2812c7fb8077b425498c784fe8aadce50f" + }, + { + "msg": "f4fdca909fbe5500494e34ad8e018fd07b1fe5b0ea09248e45ee0b347460ee845b0df8bf7ae9", + "secret": "e3317d186766b0b917ef38af522b01fa0a5be0970cd67c4514270e9243520d54", + "signature": "41a2cb393536b5c0b31cce44312f64824f5ad63e8006a733790127ededcddb993910b9178b2a048e524cdcc6adcae8ea390746ab8be4941309ab95253de2040e" + }, + { + "msg": "703176aecb5dfc64f2cc51086f20059bb2b1a3858aedc8406f34e41f6e0430853eef16c88566b8", + "secret": "22c60bd19ad06131fb3e1276da28e1d2e48284f91c84081d5ac889b43d8d337f", + "signature": "cbfa317cc757c98a09f6e0e1c38c389b59fb5861895d14208e6e35f2537aba5e118504f690f8e033241de3880ffcaf6bec76bf46eaef14bc7c97bd0ec9ed120b" + }, + { + "msg": "f3bef8440e52fd71ab00c74eafad5c8db5e85de1ad253bf8e91983e560727df92ec825155721d839", + "secret": "5cfff124190b1139b2725d457f10b76a902a5faa77aff97b34afd0e1019207f2", + "signature": "a0569112807894b4ffd87b6eab44e30eef847282b8858817bcd33ef3aa64d4e0aace33200b766b14fea47d4e0ac68531e8e4b907a37a1e4752181d417a3a8c06" + }, + { + "msg": "d2aab2d61aa063f1da2b81df1334457a75653463e70459a8a5928e3894055ece0af67aca436fdf96d4", + "secret": "3e41db85b3f75a718eca247abb0b8e49192a0fbd5da1fad6f05ce9a187905ea8", + "signature": "ec414a78385c1dcf6713e654c7f1ce21ba4e86f3d36fa1db4434379fd6f1e0e7d04cfe546308bc4e2aa548da8f33ce5d7b57006a176370c9574f8d5e8a7d670a" + }, + { + "msg": "8003fcccae8cea89a2d1743cddddbfc195d771e94e3a489adc652ef3628e7e59ab9ff6b1401ae6809fdf", + "secret": "a4c9afede9a1cc2c14176e023bdbab6c02bc8c42042ab3b5752c14b5ac2d97ac", + "signature": "a7ea34bc04aba74c7ba2c53f6321483d892f635e5968321f04a5f581004109bf8e77f02ad33aaa51b48d3e2f68eb71d80d09ec16b3c5f3f7dbdc90430e953f02" + }, + { + "msg": "79a06ef07dfa8f62a30d4c0d2f43681c3d45a5fde20ac78b24060ef855c760039fdad8fe6b06b16a205d05", + "secret": "3fc9ace4887e33425bcca94c80694beaa9d439e742c20ffaec8c811981c7db2f", + "signature": "bc5f6fa1a226a579184d006fd5ecb38834e57d68ab48f96f4a85ad03d9c1c7615cb1d56e75e7b110415ae136ee039471207395136b69b166659322658f55af01" + }, + { + "msg": "c6813445e3a75857eb3ce49302f2f96fdedbd0987f1ecff702adafd1782180b165d990dbb2284d1b91cc9b1b", + "secret": "d3b72f6ccac077b4c64204903908e34a5378a51754a9d879c960e3bf4e7ce24a", + "signature": "a3f31207bf5fe1a473362ae0b590f87a8ba69f1654cd6c008ee6e39a1e7bddff4a58b11a8fb52291ce6a0ec60246360e858908790b914c4ef38d57aab99bbe00" + }, + { + "msg": "022e8ceea7812f1c4981dd3adaf9bb5f48c94f9b1bae0b67a72c686656f41e0cc51d5e348154ee0605346c125b", + "secret": "c5e63ddd9e37830ae0a7a538dd631b34f3a11a7599d53ae0eb6ff540132a1380", + "signature": "00a94344a8c4fbc856cb1588454a2f69ab35708864885e49f21b4fd1d2df2ea84c2b37ae1f8f638c1cf6169c84943f48a23101d87389e63a780e299ff9c20e00" + }, + { + "msg": "2457ca4eac6c3baae944ef3ac6415bdf60872cc704e526ae83be0167211818400a7a79d739e8d5bf732a5f9b11b5", + "secret": "d2e54e32c4428ec2e58c8abc6b3b396991256c63d9207b43e5eb0c2857c80590", + "signature": "708343246b46c238886dff519f04376fd979e76b2e87ba64b6634924413e5660eb73fb95d2954657b1695da2565879bfe1c53b141fbb34268c1684cf267f9701" + }, + { + "msg": "20b8e01eec426b2337b8f2a89441b7f1fc9c38ee98bdacf24fa7c6f6dc6f8d0ea60ac6782f4dbf28bc085ae1e4267d", + "secret": "202a8a94ba2c24f2d48171bf86367ea5a7fa4a43faf70ea0f6f55f970a125595", + "signature": "5158ac5f594f2d84bda84afeef1d82c1fca10ce38618ce96eb66a5fc0729d5346cb08870636719c6d694944769cf4f1bcfb61a3562cef96a34ebf0f0d0e1c500" + }, + { + "msg": "f72dd28acc422443eae91065dc54e9215c13cc30831878f07110a10231e0272fbe377ca390446965fff315c86d8efc4e", + "secret": "79def281b7781ddbf85cf3a22e2b6f9a2b70799287b54e823516bb0f62c737a7", + "signature": "470456951a4051943f81b7491ee1509cc30da74f0e33e493fccc4e9206198e3f224ef27d82185a99215bfa461f52fd259b1817937148541c33f49a0d975dd503" + }, + { + "msg": "4f9bfc6d0726b96d4eb265d1a535eaa67ff43ff8b6f12e5e98424ca63a50d50e962b4b6a2e12954b49d89a77f30f1ab4d0", + "secret": "bc7c8e62731a566f79f3569dc1a245268fd2b97376b3ffda8f425de911f8fbaf", + "signature": "116a7a24e4c090de93318cd6773ed0f065e3ad4bc7974c96a91ceb51162d4b0122210be41d1996e071af83a1b755f5cb76791d83891313a38b47a41eeecf8604" + } +] diff --git a/rust/tw_keypair/tests/ed25519_blake2b_tests.rs b/rust/tw_keypair/tests/ed25519_blake2b_tests.rs new file mode 100644 index 00000000000..d6f1b29a821 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_blake2b_tests.rs @@ -0,0 +1,35 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; +use tw_encoding::hex; +use tw_hash::{H256, H512}; +use tw_keypair::ed25519::blake2b::KeyPair; +use tw_keypair::traits::{SigningKeyTrait, VerifyingKeyTrait}; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const ED25519_BLAKE2B_SIGN: &str = include_str!("ed25519_blake2b_sign.json"); + +#[derive(Deserialize)] +struct Ed255191SignTest { + secret: H256, + msg: String, + signature: H512, +} + +#[test] +fn test_ed25519_blake2b_sign_verify() { + let tests: Vec = serde_json::from_str(ED25519_BLAKE2B_SIGN).unwrap(); + for test in tests { + let msg = hex::decode(&test.msg).unwrap(); + + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + let actual = keypair.sign(msg.clone()).unwrap(); + assert_eq!(actual.to_bytes(), test.signature); + + assert!(keypair.verify(actual, msg)); + } +} diff --git a/rust/tw_keypair/tests/ed25519_extended_cardano_priv_to_pub.json b/rust/tw_keypair/tests/ed25519_extended_cardano_priv_to_pub.json new file mode 100644 index 00000000000..72dfa59ce6e --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_extended_cardano_priv_to_pub.json @@ -0,0 +1,2002 @@ +[ + { + "public": "a4db179c005d3d0906c67fba2af0b1b74af9d8c3856d4f47e7ef856e95ab906923b2cf101d0419123231f604cc80155614e5595257ef598fc94fd85637973c64b540f854650e5e32d0961a5bdb7c7ddf673006644514dea011ccee16e49f96a18d9decee025aab6deac1d9a227f28ada4f6bda9fc67c533e417388139d663f40", + "secret": "7608214e79fb386d3c48d96084f3af17684bda520feaee0c499961b28d7958cee5e1bd6cdfcce99e95a58a7234985e6c635974e757d45f04bda51c29de7e961623b2cf101d0419123231f604cc80155614e5595257ef598fc94fd85637973c64b1fc032a5d306bf973513f617868fefd019e089ec592a0b34ecf17c7a6f220cab697bc4963f5e6921d92ada9e67b9f1c4c0049995d38fbdc48b889ddc91df5e58d9decee025aab6deac1d9a227f28ada4f6bda9fc67c533e417388139d663f40" + }, + { + "public": "3c5d1dcb1aad445aa88982d5a3fef525000e8ccc30b977828a55bd957ada9935ba2bbd8cfd270d193cfca4372febb15fd19c4cee26f94e0fb1b3d4f7287f044d97adf0f1b8ca9e67e93cfa86e47b53c653f3ff85506caf0a3851dbf3e43f90a30fd81b0c1148436956d5a546d45628a44c3dd6121499fb58478ed5a3038d4611", + "secret": "079a15370bda7ffca3962280b883ff55e9fafdfab3e7dcd449cab8baed4af498a60ec734f0bf50952d5b44d4edec0b91d53ac0e4b40768ce0d3b5f1f3227619dba2bbd8cfd270d193cfca4372febb15fd19c4cee26f94e0fb1b3d4f7287f044d4f53b7f9fbaa3ff066eacddfe19b1b81eb1d0dd65fde1fbe54a4b3c71d21951110da06966fbf2805dcc41d48e2265dd60bf45bdf1ed43a7c8f10d64bb83840680fd81b0c1148436956d5a546d45628a44c3dd6121499fb58478ed5a3038d4611" + }, + { + "public": "76bcd96d0f2f0252046da32595883b99d66355c26e187f27c9954091123fa86747121ff8e1f7776d14e33efc86ca809236b1b74e9ae7d43d5ab94151a71f7e50606060f81b13c893510f56b6a9f9f8930b2fa99a7ad212939b0f7482ceaad48872fd21da6fda1faeb5b58f290de3806fc0b0a78f080f4703747d5678d713f587", + "secret": "253b79c87603aa98f84f1e3971e7343d24279e4a3ae2f1dfc16792d6bfad1db1960fcbac421d3b0e40abd4411aaaeb8dafd63e42cd7425d8ede60bda0685870347121ff8e1f7776d14e33efc86ca809236b1b74e9ae7d43d5ab94151a71f7e508cf878169ce4ba8e1b5d528b2b0ef7e102a11414d69913c2a71f2b68ea708a19a61283b962d6fdd1920374cd8e6799b8be732dfc4c0c692b956953fdd8389bb972fd21da6fda1faeb5b58f290de3806fc0b0a78f080f4703747d5678d713f587" + }, + { + "public": "2f9ba2baadf9a8d71b5501d2d0863dded91d4dd603e2c52f82ba7f431ca07fd34728b8ce2bb8cc1dd7ac8b65b9df480e389fb4f9add088a54f4f973e373cc179eec81d7c8bec4070d3d32f59c597cafe37016782f74a4cf16227ab1db1a0ea90374d9c0bbcd257b6862c4cf7d45f4eb4268f480912a516d5e0182175bac9d7c5", + "secret": "ec8fedbbc03717de45108abdf0692ccd1ded071bc665c1412225afeb893447a9bbe0bcb48809634bbe7f46a992d008f01d14ad9e7cf2fa1b46760b82983a28c54728b8ce2bb8cc1dd7ac8b65b9df480e389fb4f9add088a54f4f973e373cc17992fd82917030497d9946de8cbe340989650bb93a3894c7cd44b933c40005f4f2ef5e3485be2e0955b17bd1ec40dd1240fd643fe9051432c0ee19615e9df5bf65374d9c0bbcd257b6862c4cf7d45f4eb4268f480912a516d5e0182175bac9d7c5" + }, + { + "public": "d1a159d151aec2ff1228a1be62164bbd2f471d53051baacde86f44807a119d727bb9a0c01ba4e6bbb8fa78385507457e76067648f6d84b7c971fb03d388088923c773d97de236741129a464f72192461df8badada24bff1373233cd072003297c849f04f0ad8cddf49873291935f7c3180bbbf6d0366e4c1d2ef7c89db18b209", + "secret": "8b1010f9e926ffe7ab4f88a40b40c02e940df96d7990ae8776fa223202a3abe5966de461596b65fa23f80dbe1294721d340b319e3339095908140c981be0ccef7bb9a0c01ba4e6bbb8fa78385507457e76067648f6d84b7c971fb03d388088928765d2aeba3b2da4f2d21d739a01fcb67212c0e039248afc8e4d1374c976f064dd5bdde7eda17b7b26e55a7affc3cd085612c87d111f61cbc6e401ab109990a3c849f04f0ad8cddf49873291935f7c3180bbbf6d0366e4c1d2ef7c89db18b209" + }, + { + "public": "9cb0af351ffbe327aedfddc00cb6b01ba289e88fe69d8834e1be93b2991375acedc9e7be7a8388c83384b6ae25b2acea01a85909f40b5917e901254903b6ba056f159d3ae4081ff7faa3943d991d616b1daa028b9f0a048a553cbb32b2683495867a60af436d78294f9484b5850cce58c1e277a42f23203a325ed5665947d1c8", + "secret": "fe2886d526c3c2bd0bb5d64d3afe4a38529fe02b954858540738fbdc36d14fd3ad8151e971a3476f609d2de2dfb8b9ca0d53bd4f56c88f361005a830e56fafecedc9e7be7a8388c83384b6ae25b2acea01a85909f40b5917e901254903b6ba059512b7fe3cf0ee4a2d5dd5b397bf117f714abc2ff7d67a5e101accd477cab07c13855fb4eb9bcd8dd517aea3fce10cbda2dc6405ea508e86eb985fe8cbbfeaae867a60af436d78294f9484b5850cce58c1e277a42f23203a325ed5665947d1c8" + }, + { + "public": "ffeedf9ee09e6a8b6ba4c512f3ccc2b37146226953fb7a96fb4cc563331856cc1c46787c9f71a9b58af5e7175cdbd7c32e3bcc460d6d6ac5a84dfef5c4ffa116485f8ccf83fe0b02197b87db8758c113258c07772b52b6fde789fc2f46431e51b7203ca9aea19f573d4352d4717d33072d28ef5816333a48882d9da2ca9b52ac", + "secret": "999307b88fd8877a5cc242fb92ca6043e795fffac0e5bdc03b0f1a8ffbd0813f79ef180d35589a084a11a53cf9ee45b213c4fd185dcd5b3d64d06be6663d0a7b1c46787c9f71a9b58af5e7175cdbd7c32e3bcc460d6d6ac5a84dfef5c4ffa1163dcb9a92fe73fb3f11c773bff7f7c9b79ed15ee19a9b2e947dc63a0b49e5e44e1e9be37355ac800340b44dfe8d47583c1c193d8fe62520c123e01e30a3ba6204b7203ca9aea19f573d4352d4717d33072d28ef5816333a48882d9da2ca9b52ac" + }, + { + "public": "5133b7c2815fca37ee132fb2294e2c61629bcfaac3bbf73af8a19106eef9d2edfe80c56e01c72325614bcf2c99a0124181669b8e0f0ebce8cb7716fa70efa67bea768ad0e46b5ec25692ee22669f7c974258ce2bf1a455669cf0365cdad5be3be9abcd6bcda70eeb633d8ef8c0ee588fea450869aa96de917ca636ca4d8d9f2d", + "secret": "86c27964f7ef60c847624b2e0ba25a22b5683e2ff2a4ea414f3c995888aff8b5d430a6a7d14826506dc8d63797677b9dd42d154b19ee311c2f429c0dd6ce2ffffe80c56e01c72325614bcf2c99a0124181669b8e0f0ebce8cb7716fa70efa67bc69740a9d95246a565b2e10ee7dc08faaac49556dc427a675743d087f3f5a0923c9ba780e1728f138c645069c2a907e73155986f04e9b5d239a1e8e335728dc4e9abcd6bcda70eeb633d8ef8c0ee588fea450869aa96de917ca636ca4d8d9f2d" + }, + { + "public": "2d4ac77fa910fc3ccb69bb9f02a17e431ad3ff36f388a6b43714b3c7e6ca7aea57ab1f6ad1db8faf44cdeabaf9e1dec51ecdf32c1d22bfe926765fb410e467d5bedbbbacc12dde7eb384792ab08e22d6d665b1d2f06be7e15817cdb3dc04f45c717c3fb588db754f39ec9ddf2dd6e12c753a78b16020188c9bdabec1aef54975", + "secret": "609094888b49a3c54f2dd390e6f47e2ec7bf43dc85a0598f054bd4396eaf5d13c82f9cd1d03bf0ec10fda23639338283f6b7d5c179bb8fe0a20f7b9f4b10a43657ab1f6ad1db8faf44cdeabaf9e1dec51ecdf32c1d22bfe926765fb410e467d555f1a4303be749cdf69d5bd7b52cabbacfb2ad8ec2bc0b795e0f6ed4055d3b7eebe03cfe4f6ab0aa486b4da592b833d57d75395ddbc016972a714507a29894dc717c3fb588db754f39ec9ddf2dd6e12c753a78b16020188c9bdabec1aef54975" + }, + { + "public": "4141541284b02fc2996fb4811b79e4a4b6ea1e969fdaf21fc27de4bec0847f1f1bfd6d7ab71faf9047dfd0e63ca5a3f10b4a1e5365a83726266d24223997b1dfb6e0269eaf8d39fd593926f6f9e2d74af017985fc34b060f91eb61f48c6d3820255dbaddf3c6cac8c2dc4d566cb78f9a0fb0d9997df930cc8cb8bdb9c51c1e6a", + "secret": "2a67660f17b088515cc8816d80250b727523dead57b548d053ddd76430b438274b123e7999866c18870221892f7f05a5446c23db0b2e8484755e92b4caffe54b1bfd6d7ab71faf9047dfd0e63ca5a3f10b4a1e5365a83726266d24223997b1dfad2760eb7ce9a6efd9decbe2fa1cdceef70b54bce2dce6762aa7e13555984ed5694004ba203ef5ebd168c3325133ce61a9e3c4af1cf28d941d075c79508fdf0e255dbaddf3c6cac8c2dc4d566cb78f9a0fb0d9997df930cc8cb8bdb9c51c1e6a" + }, + { + "public": "b2ab866497d7307b5f466ace43b235f2117ab208dee2c8108742f51e874bf56723a267599e520726d2ce9482c8da43e5fc971d16e3e420c9b66be59e307296d798df2ae0af88b0ba957c49fc8cc35b5d7571d41586b55b9aa60b44167baaf069563d4f95bca71b6412518a0009420a1fea3a843d19ec01af4095c84cc79bd40f", + "secret": "a1ab8deb033197623bd15e29abbe454da1cc62ba05060d17197e4e3f5c5513c98068ba6237ff64093f4bd3f9444cc4439b22a3fcc9f19653b1d6e5a145cc773e23a267599e520726d2ce9482c8da43e5fc971d16e3e420c9b66be59e307296d719deca3c6f07f094acc037e345d5628476ef6802ca66d4ff4a74c00fd847a87ac687af64dc63a48bd1b3e5fe74ab6630637db2a1e9a531293a3143a8ba8d6b91563d4f95bca71b6412518a0009420a1fea3a843d19ec01af4095c84cc79bd40f" + }, + { + "public": "9be81c1e9125d942a6de2be35ab74425fa5402e92610cc66562215db0acbc42df7d22c4ab153074a21be9ac4d2a588927139135d781401125323cc3334c4fe8074873677ce2d546d1050696fb301020ee12f8637b1d2ffa60c2d83f486ddd35732fcfc7bfec0f7b40f19d955dafc73e6b11da982c4d1b0e86ccd71bde3d7934f", + "secret": "45b6df9e20e35ba729ca7ab132da9f3d869ba835e3538b741f6ff4fcbb95313c31dc718aadd379b65d14b1e33f7bf4532f00dc1299c3c739d5743aa832218ef5f7d22c4ab153074a21be9ac4d2a588927139135d781401125323cc3334c4fe800b1be8e3d1e4cb49cd5210fb074b0015d4c201e6b8857b679bcd6f3b4030d2b7a3d0f2d8bc284b5af8f3968b00ec3b6810de49df60b220659d24e6c1c3d3481732fcfc7bfec0f7b40f19d955dafc73e6b11da982c4d1b0e86ccd71bde3d7934f" + }, + { + "public": "02b5b6b07a4c1fc63299e2d9fb4cecd7447b652fa4dd4bd0c5513a731dad841b4ce355e694a25f83c84074c7fd7904876abc860e75716d88d86bed471a8df5851a252c893679eceac2c91c98ec476d921d239b56377348e86cf3f47b2dc320197ac7326b2c2529928de702cc8326b67fcda4f997d68a904a248a67199d6d2663", + "secret": "11ea783d020705cbdbb4b5c79657057ac46fea97bf53f706e75572e6e875be4ef2812830023ccd95e881ee84226e128e2f49c94661e62dadede3e41114236f714ce355e694a25f83c84074c7fd7904876abc860e75716d88d86bed471a8df585c30b8bec8b8939e5addb191d2018ed0be07785e36b78eb29c0a2207b76b28a93f5db7f73936ad2eb480674fb48f1deedbf56ec079c368679dc1b525b79886c5d7ac7326b2c2529928de702cc8326b67fcda4f997d68a904a248a67199d6d2663" + }, + { + "public": "97b038f242449e321a338f689b8a31ea21f38a4e020132b61632e8124d92a6f4c2d3603cd357392d309b0751a9b4474ec80de38d41255b0fc93e47c9ca798b4b3d9f0720e903c825022cc03e862208302d0cb2aa79481b0b71572bd5975e7e07072525b013690baaf1c92197be78bf831fd8a9ebbb28302bc0f334223dcd04a0", + "secret": "c06ee098bd7476d9703cbfe9a7aa010efd502e5f9a5d8e3f057ccf5035d485ffa8a38a92d91d002ca5925af9f77144579e7d5768c6b5dad27601e19c5db7f35ec2d3603cd357392d309b0751a9b4474ec80de38d41255b0fc93e47c9ca798b4b2a4c3b83f3f1af450b0bcc70634e44a1324f799fcdf705b5a52524bad812673d4da293ad5b7d46c9a0538490ca1661ebfc9c6fd1a36ce740c62b3bc51be2d5ae072525b013690baaf1c92197be78bf831fd8a9ebbb28302bc0f334223dcd04a0" + }, + { + "public": "990337d9d1048db27e4223a711b6cf76087b28d5ba01b446c1053eeda25cbf0c5ed5d37addf8f604b1063936a7a87d1cfb18c43fcce0eacdcf11bb0650fcbb013886b2bf35b1b74a9048bd4f4f7a9c5481783bb65b7c6c5407bddc6b6d6e3c0ba05f2082070d1b8d14411c14f1453088dc2720335cc87786fcdb6135eb978c5e", + "secret": "b8cfe20c037dd8301fd5762edd1a07a6cfb7ee0bf143bb3a123c6172274450563101bbc1bc36d63d3da6a4a8bc213540cdbcb6607286186e1b8a7b09bbb70c035ed5d37addf8f604b1063936a7a87d1cfb18c43fcce0eacdcf11bb0650fcbb0157efc45dd0961e9cf4260e4daeb748fe2a8642f355c1ff42e8674422dcf4a92f452ee93a9f0474b2a40ea8d351c9c5db52b40a3f314ab77ba407e10d12175d6aa05f2082070d1b8d14411c14f1453088dc2720335cc87786fcdb6135eb978c5e" + }, + { + "public": "2e131d6c4a1d372a5408c8becc850a1a2e40c3a08fe543ab44ff6ae0be9177551e573204639e419891b4b2c8d50eefb41b31c983a66cecde960b158975a859464c29b8f8963ee4f154e82bf45aec0fc4b0f9faf197ab24e5120785434852553c0bc91f94c512d6b5c754e3603b57ea10e17e5423a40950955bf6d2bccc48314a", + "secret": "40afd4e82a58bc8ee7742e9d5cd91f49268f63905d50d5a8f77f178b21b366559a2a48f565bd66f96fb8121daf2a5854c53a96d89b9311eb8c18ca71bbde7ad21e573204639e419891b4b2c8d50eefb41b31c983a66cecde960b158975a859463355f8cd196b2343e692eefe8a9299518ebabe4f2d9ea39db40b559d6097fdd8593c7d0d0334a2aa7db8515d6681ff8bad1345b2b24b2028fb4e3c0a22a84ee20bc91f94c512d6b5c754e3603b57ea10e17e5423a40950955bf6d2bccc48314a" + }, + { + "public": "0d27537d7fdb697503c65097b82366ccc1f58d09b8ceb9800187d42d60e118ea120fccad0fae858f9363d17841fc6b26a81bb874a4f150ed107ec8ec65787b5bbf229fa51d4710d87c8454218e91f305e1d52b707c4953039ff78c2768132f86cdbff438d4680cf77b3ed9bdde0d08b854bfe9b21a2fbf78e0b32cd617796a0d", + "secret": "abc2d745f632b2ea59546d439e5b3626d2247a4cfd561cfebce89e31cda36f7834697465ca16b80d077ba9bd1af908966eda2ad1d67bfb44ac64d10f935be7d6120fccad0fae858f9363d17841fc6b26a81bb874a4f150ed107ec8ec65787b5b765d99e46bb811edd0e54875a4b26de9c6b6aff63c1587a4dcaaa962ff41f858e20530895201d8e56e49e8403285706b122e0a6958b1f90198abc365d6b07f20cdbff438d4680cf77b3ed9bdde0d08b854bfe9b21a2fbf78e0b32cd617796a0d" + }, + { + "public": "0bcb4c0034088a7ce210a9c8d3ac4ec8ca69bd1544c6e98c8d87d8ef3e0aff86b2c7128b5578acf5a1465a2eb3e00f3b50c7309ddde65b7d8f0cca903a6042e2c57b7a0553c9b04788e28f70c9c8a493a830c8ed4079666bc130f2bddf9f94f8a2f7cc426cb4c9ed786b875bd9f216245d408fabcb74bab4e9b69a5d3fef50db", + "secret": "f73815dd273a341fcf5d57de8c17b71978f55a397b89192b1e84f0bf55d25761fe343a25ec7ed424ccf13d68ff2e81f083df6cbf7031b61db3c5017e1f2fd384b2c7128b5578acf5a1465a2eb3e00f3b50c7309ddde65b7d8f0cca903a6042e2eb9c6e40628839ca499ae54585fb1dba308b3570da69690a06b9af2bcb7865c5584a23560c720e60489d2b5756617044463f99a050bf83ae2267bd3710f1ee05a2f7cc426cb4c9ed786b875bd9f216245d408fabcb74bab4e9b69a5d3fef50db" + }, + { + "public": "344a831d6df5ca85abe3fe790fc4d093473bc49ccff0342ca46a95b4fada96950f4ce53d7530c5865faab0ce900579beb5952194c7f4de061734add0edefe4591e5ede2809b45d478c93f513abc1e3ce6a9b7ff0892b168c905252ce8dc0f945c7a642bf10b2bc63e7d9f988da01aebcfb8d239fcdf0ebde62bef9e7a487af28", + "secret": "18695ce7d39a71063a15c90c4f80373da9b4fd0f153613a31216b02bd28b9ca06ae4108523aa63938ed811c5e4e7b2001490d9879bbfe1bfe9df27cb027103740f4ce53d7530c5865faab0ce900579beb5952194c7f4de061734add0edefe4590f95a7fb01a2df7ba52850c1a7e3ddd4cbc0fb6ff91d2332c51ae2037f14f2e421313d160ca5cb2eb4fce1be1b9cceb5ff20f7372d898578b35352abc096ba8ec7a642bf10b2bc63e7d9f988da01aebcfb8d239fcdf0ebde62bef9e7a487af28" + }, + { + "public": "720afd999e9f16c983c1aeaf2f6a5e1fda4eab529337ef5c24e92819f4a3a70a38a0f6a15ccffbee8a53cea5d2cc6d08a41d2412e14b7d09e2395f6e1ade8d7d4921616aae0fbe7952f8f1ac09ee22dc89cd358e6d1e02b60509e03571da1811edc3551909b88b46bee950f461d90412b1500fabdacb5e286428386e9da995a1", + "secret": "3314c0749f7ef58b1e283711b870b5ea8a56523a5fdc268bade23ef4e201d95f52e46e4bf0e64e9c91ab661a3c142453d3a66788ff392068cffdf84a9ddc2d5738a0f6a15ccffbee8a53cea5d2cc6d08a41d2412e14b7d09e2395f6e1ade8d7df53955b47285a9d164cafa1548ca6537892c767a29be8e8b9e70468c976fdcc20de05a999b1531d2292c28de6d722918fc1d1737f9279063d14ceaa9f420421eedc3551909b88b46bee950f461d90412b1500fabdacb5e286428386e9da995a1" + }, + { + "public": "31e8d01fc6ce7b74e622a456d64821c8fccf7ac9f943aaa2a9203c89bbb9446a0f3c706e57ef54602c16ffef63ac872e67af43796020a2fbd701838b0af7521f089552b19758c6ab7fff56d7ebc0ddfdba79708fa2dcf406d64977f556433b8e4ca384ade31c9507ba64ab9c4fdd7f8f5fcfa74a1deed78bcd8821f31ed60e8b", + "secret": "c9f9b3bb3b7f3072051cd1b33aea7ef72dcb105ce5e393295a70bc376375812c9735832f61606918a9de7dbafcb76862f737e0bb00f66b119902d06fbe06b8d80f3c706e57ef54602c16ffef63ac872e67af43796020a2fbd701838b0af7521fa3223432e2cb9e514d70a4f7ae20c43d1bde03aa1c32f0bcc5dfd15b092b8eda2a074e005b21e6b1043c9b648a3674f4f76d1bb542c9648a3e1502a8bc56b7de4ca384ade31c9507ba64ab9c4fdd7f8f5fcfa74a1deed78bcd8821f31ed60e8b" + }, + { + "public": "ed95592bd61d79635a4daca366d8dbb0fbae04447c0ad686cff205867c1ada2a6fbea3f11d74528ebbabdea1c4792ba400e810c33d8447476d319c0c44133ec01108386adee6a0f986aa10fab96e5d6d8900423474d7ee726d578a5349ca15574fe2eda1766de28c7942f79278e2666348c14aa51b976ad1582b611377d11ee5", + "secret": "67dd95a9c409e9c9c32a88a74bdae36faa7bece273ec19ef1e23874799cebb43f8cb528777e90628da5e6f3c4208829acc22fe43ee716c6f6b43585ab8b96c446fbea3f11d74528ebbabdea1c4792ba400e810c33d8447476d319c0c44133ec0dd790d46c94943f04203b9755fb9b850cb715d265d369cefb30b9276b849b5a1d6859ef966cb82623cab552f483917629903c8d9788dd63c90998a079f79ae2e4fe2eda1766de28c7942f79278e2666348c14aa51b976ad1582b611377d11ee5" + }, + { + "public": "807e02d6fd3929a0bcbc09518a7c50a33138d8d12a677e26c610523c12483b6669284ef09bce3555df4d935913ac12d49d7074264eb922e7491cb2ad06ee66558ae4abda01a73d3dc68b59863e55d47b658cc80e3b6413d9240690d1e2f3bb308074284680b4b8b4cfb11d5b6e0ee5ab849fb51b492edc8ff5bccde4ef68e5da", + "secret": "5e12b1f2a1ae0bd257a2725dcb85645222469361cad14763128cf7beab71e9d95d58db52929d571b45e4b8b616a9d96bc30b626b7e417b5c9273aafa787929b769284ef09bce3555df4d935913ac12d49d7074264eb922e7491cb2ad06ee66554e38800b6f5682ae2f5ddb2330dda620c371461278891116caa1aba5d69044390fe1e4ff4da096a6e2a6ef137d271ddc40fbde753ae6deb3b55a89c5023c79a18074284680b4b8b4cfb11d5b6e0ee5ab849fb51b492edc8ff5bccde4ef68e5da" + }, + { + "public": "a6e1882a92572bb33c00d0fcb0ae586a1dca09368507450be37814ef0bc9b6b1430b8948700785a383efd728a4bcf32441bd97a2ba6286dd3c3347a98e4bf695f115786883706a6be5998f39a11a67ed32d1d3b2a5757b9ce24c2affc71e650073a1124df5dbc6c895afdea52054924f70a6cc70093d9759066aeba6483e2b53", + "secret": "d5e3f79cbb05759ed6b64dd6309d792cfadfdf3d42dc39aaade6c048d93a362d756459a973c4ae7a45c16b6ddfa7006902dcfd1af8204e4652772b6706769896430b8948700785a383efd728a4bcf32441bd97a2ba6286dd3c3347a98e4bf6958bcf69ba9a3a0a96ffa76956a6510a31367073ca2d426074997d4920fd3ce612e36adf5e76b8fdba5573bbcd236e23db013336ffb3bc2480f1a06cba78e4132773a1124df5dbc6c895afdea52054924f70a6cc70093d9759066aeba6483e2b53" + }, + { + "public": "734c62c6d2de5430d58ca1dcb31da7e8cbd7c6aa3fdfdbe8ee3056638080182b85f1c687e18ffc6f39865621debbed66d33bb5ac226d2d10f917614a9cc299c56b7de95eab38e49c1a666705a86015e578d89c7d65f5b8cd0761394e6cd18d4c8964e4ee7377268ea670371d0111fa6f1e289c9db096ba1a8a6093ae4694573f", + "secret": "ca9a817d47de153026782d4d806d4e3d60afeba01b1997efc99c91f63da725503e965ceb83d48b84afe9368d2cefadcb8a1d6bac766566c2a88665cc88fa249f85f1c687e18ffc6f39865621debbed66d33bb5ac226d2d10f917614a9cc299c5b6a43c8f55fe3c8fa2b938d9e7241796bb6ca63bf2afa941a8a6177da52c0a70118376267880dad955d8a79ef81d2841b810f5d263bbdbb22f1ec91ac9b491b28964e4ee7377268ea670371d0111fa6f1e289c9db096ba1a8a6093ae4694573f" + }, + { + "public": "b2d16a70e1170704e24b8fd80826e73a1812a4c66b2db1557486541a6952aacba357a360fab2d57895eb262e31c1632008376e8a7bd3c657e3912023c48370f8895f2b12468414f76993b1d8e3d0ca23d6823967843b12540e2f5b8ed36447839fc8b75c50e82fb04234b05b9ee19147bb45ebc823ca96cabe76b81fc31ea15a", + "secret": "01118ef8e6b5d19a63e3a9364cfdd225fde83d389bc9bd75466fdfe843c362d5a496591b7de884914fb78c8760c1b25380b8e7e8345458400f5657a0bd0235d9a357a360fab2d57895eb262e31c1632008376e8a7bd3c657e3912023c48370f886b6b6c821bae1839eaf13fdad1e55703ea437a2e2a0e76e530066fe01a39568d2f1d59f55a08c3e98fbd22ceb27856d85e9ae5d2c743574ff57fad6877e6e259fc8b75c50e82fb04234b05b9ee19147bb45ebc823ca96cabe76b81fc31ea15a" + }, + { + "public": "4f89c2d2b5bab4b26853f720151bc00e827d7102ff9c4cb5a357c5d36ae8e3c9447aa5bd0f722f366acce40c299f50e6b353a39bf3d730b36091043e5e76ef431120d0b0fe8a6262959fffd3ef81dc5d16002766c150eb23bc3fb314a94fbbf2bfddb89dfb2bc479252c40fc759798e66c135bd8299cf324e6802498e62d4cdd", + "secret": "ae047f4614a1fef5ffbb9482f6bb20d195157c53c278d4ecb78ca577cf4fb2b94034689437b68498482d006cfb33a943b1dd9e5397255aacfc4fae62dc1d19db447aa5bd0f722f366acce40c299f50e6b353a39bf3d730b36091043e5e76ef436346cd43879e8d2c4783d913848073c791e5512cc1da9ddf38461fc1aa8505ca6af1c51e5449b7d256c16815b4e36ae64ca637708c549418e245a644ebdcb9e8bfddb89dfb2bc479252c40fc759798e66c135bd8299cf324e6802498e62d4cdd" + }, + { + "public": "6a5bbb3799f9e79abd6c290683b7e1962bb2bee6311ff4b0940137932d7d8ba9ccfc26d939380856f9c0b9aad904e7108f39286489da38d904196354ef6a28e8f6a6197ac2949f44c3654061f6dd54ac8da216ec0b178fc27e8a05bcc43aadfce0719fd94c5ca73b538bccaf6773ad6ff3344e22f1f962853b970d46ee5f78e3", + "secret": "75046d7dde06b6f58cb0b6d10259f91f753d03230af3930f0ffee0ce0d9cb7607bad4f19300dd29515c718d9197a808b8c94cad5258472d63bf6f167e4a457a0ccfc26d939380856f9c0b9aad904e7108f39286489da38d904196354ef6a28e856d40b25a519f1fdca25ea8480a1dcce18d9608483422a4260f183d470a765d4d6426ebdf569560040843569133310dd355502b035ecb6c0f4def984ef0b42dde0719fd94c5ca73b538bccaf6773ad6ff3344e22f1f962853b970d46ee5f78e3" + }, + { + "public": "3ed7d7a55b2e7b7801aba7fe333c31fd522811f5adac3ccd674c1b532d37784e01f963ff16710d68543fd243efaee4e8d269a6957f8aac112f328b3a48f81c4dbf94cea5779ee5bcbaf595a30519a3649c3bbafeb36ea906290575166c45da9da0e7fd0063abfd6e662f0d6de79c3b7acba865b4648bf32bd831dd5d346c5324", + "secret": "650fc35f234b22bb3f711cfa8165306130831526e7232a27691071ab3b81c093e094dd17df93247527cf3b47a8d24779114f2a049ef87e1f8643a782200bad9d01f963ff16710d68543fd243efaee4e8d269a6957f8aac112f328b3a48f81c4dbf44922498b16f8e5497b74fdde67a5d2c2fb969056ef1842e810b7d70f3a11ccfb9659437f141813f4df9f146251540f71f06778de98463500751bbd25858c1a0e7fd0063abfd6e662f0d6de79c3b7acba865b4648bf32bd831dd5d346c5324" + }, + { + "public": "858c3834be411da5907e2601666b47060cd5c4c7b72e628d14ff2cf7ec1a80a5efc97df8fd7badcc80e7672554ec8dd90cee1cd33be87ec5fff574f5da060e4b9dd9e10687ce6dca78c311b015a639cdd1cce8f85a9f141612f1f5d6b5a8fe6e83243b093c089442620387803808a4964bcfb2d17130fcf6bf4c02183fdd677d", + "secret": "f14a905bb7924823fd8cf867a3b84cb5603e26aa64266371bda3194aab99e9a199d82332fa773000c3b8aa886c279ef0b296f09d6bb563949acc62025eaa5e21efc97df8fd7badcc80e7672554ec8dd90cee1cd33be87ec5fff574f5da060e4b4038022484f2b96163187bd1a463a743185e6182bd31695f1bc28d3878f715b289d5ae4428ba023cae8483d0901b136ea5bd5e68653955b7f06712647aa79f9283243b093c089442620387803808a4964bcfb2d17130fcf6bf4c02183fdd677d" + }, + { + "public": "02cc5f260b604e78243bcee01cbff8124249060b1a5d0954fb7e2c7fe1011b8d5ca8d8c28c4beae00aea6a150acda7e02d7024b5fcaf6300b744577c36b9739f4362729ed9f614890b02c1760f82b431411dbf13ec82c2189788d9b085ea8c71dc2739f02c4355d255865be9c516ad035ea0bdea1094489c5da0c39de493c4ea", + "secret": "065bae455d2ff5250a5066572713818a5bd80e314d428c8923e3ca6ee8e7ce4b85ae4b39ec8ce2dda2e65160ae151eecb6e20241c4ccd5c573e7331a3106c3e65ca8d8c28c4beae00aea6a150acda7e02d7024b5fcaf6300b744577c36b9739fa1886170504172850b1e00290af7cbc804c2a1862287772cf4c30148cd8bd54048a7aef8fffa0d0b5af58faf384c7ade4c2ff4fd11c9e36868fe2831cad2c35cdc2739f02c4355d255865be9c516ad035ea0bdea1094489c5da0c39de493c4ea" + }, + { + "public": "f725bfdf52eef39d77c5d6023c5dad79dd859203430b5c22e110de7b4e7e7f4b31d9f056c4d77d9daf77967f8eb6faee870db4fd9f6c69a12bd307d97571d9123302ac8f8725f6e0741cfab41c9eb61edbe93b95462e7212e6009d08bcc184e04a0857b8a425c13572defd885a7dcf8b6540e903e40dc8e6ec7672110035feb9", + "secret": "0973adb948e73a7371e12c8af6589977c22dd7f777d6b2433b3626e9b3a88c263dabcda6ee43421738b7ddd0ea5905b870c197e0008f1eba148a6d03f9f4ef1d31d9f056c4d77d9daf77967f8eb6faee870db4fd9f6c69a12bd307d97571d912207b1ec2195ce7e116b0f7cd54b12d8de3e71584a048c33533f5d137d7f1c8c0323ca48da8798570f05d641235b0df3aa0f301790d555e671657dfb3fa6eca3a4a0857b8a425c13572defd885a7dcf8b6540e903e40dc8e6ec7672110035feb9" + }, + { + "public": "401468112093b3824f9aba781246308a53c09e78cda248de622b6e285ef3ebf499a27002f29a85cc397ffff55d0ce80787763bda801e42ce92a2c1763c091ad6b7febcf35fce5eb37a8fd9677f9e1e6f127dbb3c8ff303ac6b949970750bda8877b2a67b9495d9a31a3d0dd7d93752edf9115ba6f6921625f111c329ca1a937a", + "secret": "fc34eef21f9e31a88abb50a643a4ff2d97946800074768a2a90fe7362065499ec9073f8388684e7feff5918972c5a390871af233a73c021d368eb5fa690e6fab99a27002f29a85cc397ffff55d0ce80787763bda801e42ce92a2c1763c091ad626ed47bc724af69966ed8e5fd57d646272f00b5f53d30f466c27157939f822a48fb0a1a27b4a9cf15592f59c5c58ebb27bfe5e02ae555d056bd794a62598bce877b2a67b9495d9a31a3d0dd7d93752edf9115ba6f6921625f111c329ca1a937a" + }, + { + "public": "19921577c1c7349c15dd6b88009a393b5835be549c672a6fce8937769cf279179cbcc80655b818a4346fade491923e6a3b284627c9c17bce7dce249be2021eb933babd41cda8556d1e18e930f0cf49c95bb8c77d5a4d24bb0528d9a2bc9e4f9943c96da25c54813bd6ae98820728f1dbe0e31da361ded3c2614528d6b9efc6ec", + "secret": "ff9611a2ad96f2cc64cae2bb8a6b9dc3c520298b4d244dee2b5030b85277880df7b4987f24e42f5f4b62a835ce8ceea1ed2de6d8c1e32c363fe95c1dbd18fe889cbcc80655b818a4346fade491923e6a3b284627c9c17bce7dce249be2021eb9fea0fb1f02d747cea7f3cf41cde805ad8f6649f72c7a10c803e34277c38747778ef40aab3854c0254fb4ec28c5e6af3106eb77894f1d0dd00c62bb5220605dde43c96da25c54813bd6ae98820728f1dbe0e31da361ded3c2614528d6b9efc6ec" + }, + { + "public": "7fe3aa867ac7af8ad6defcf7e52d0c1a925fc35eaa394f43178209510c045f202f9fd5008c272b2bc956e18b133cdab68dd615469971656f69a94b5bda5db14a4da7cb60b291236c81c1616bfc4c4feacb651c6cbe00b7ac86dd802200466a106e7160f6e421e05ab2ca854a5b31ee6e54e4f2b61bb6f432866608727f09ed63", + "secret": "4021946cd10820e21183ae9b3b6824a812b302a03d05ea8bcd1dca35cac163d1a97142c9c0349d4019bbb365a9a46d054cb0e2ce86c717a8367d76b44f5ca97c2f9fd5008c272b2bc956e18b133cdab68dd615469971656f69a94b5bda5db14a7ef747fb99e255e136433221006eb0429ea5f070a0f9131e0e2bbf4b5c24687987b87a4d70a87c48d199e3be1a005fc67f042921430972fb5f7a61a1c97ede616e7160f6e421e05ab2ca854a5b31ee6e54e4f2b61bb6f432866608727f09ed63" + }, + { + "public": "29fff684e855644ea905c55038704a0ae2c347a5de2f42a6ed0f3b7e254579db71d60dd1f7affd09b11dbf4eebb4231e442769c8bff56b7f2f38d5d82c4446e0a53c5f24806ffcd72712594a2a505ea82d02c5ebf55ba3e50abc8d480049f199c54e682004ae2293046c7774fb987793d54d75a9a83c84e4b56b7fb8c9a1adc7", + "secret": "dcdc5d680216acfb7fe22d0d3b717fe775d7b1303d8eefb6d4430a8335dc3bd7320bfe6dbac9458f55e0429f0384f3be26dc58c2692b8042b4e9a159464a07e271d60dd1f7affd09b11dbf4eebb4231e442769c8bff56b7f2f38d5d82c4446e03f3177d6c475501a15ae9fb6f1808a088f502dcd807a23c679c289de364d400819e351fc1d1309f1d347e1ce316f5f5ff34009ea3a19f5da7f19395371277282c54e682004ae2293046c7774fb987793d54d75a9a83c84e4b56b7fb8c9a1adc7" + }, + { + "public": "a02d48cf74b417fa155646b29ecac6af9bb4b2f942e06fb346d1f54774bb132f009a43536288081e94d1cb458065fca02ead0e24989fa9d275e03affe4615a44e36ed31fef9407ee819e48b5eb030f7f84f1d8e1b32252d826e92ec4578c033db3fda80eb7a0dc2fe6e8699c8cd35f0df19f0684e16df8c448f5ba0f359d046b", + "secret": "b27efd2dc998da5c8b12a9a92f68204e8647aa5c6c95f6abb8e0d3f930056c668f8921c3871311b2da97cbd7b28721de59f8a4ddbe50243cde7eb08163c04c5a009a43536288081e94d1cb458065fca02ead0e24989fa9d275e03affe4615a4433cb5967a83f4e219e9675331f6a435fce900f4c3b45b7b1277f545972a774bb95bf647281bbec96b73c9d51a9d43282da4c3c86069d5c92b708ababa30784efb3fda80eb7a0dc2fe6e8699c8cd35f0df19f0684e16df8c448f5ba0f359d046b" + }, + { + "public": "9f0d592b3e4a0cab6ae68a481ea6d62bf2bfe20aecf7aaa491cff860b041cc0a6690410da62e97b50ef0258b77e50d94bd334e4f7b9a70e16bdd894ac65a3f6f0a6bfde25e8cbf830bd98ef022a4c5aea17630023cb57067a777e25eeb8a7d4970c7564eba4d86797cc135e08ce89d50f5d444e6a86f2d65dffc22a0ad276e96", + "secret": "712b24a05a4fce8d07b123a4647f6a5d9dfc111e79cc4c51522e64e796ee7082bb6f6bcb2d689dd597bdfc4f15c865e3e177e09ece518a23f55aeed59bf6c1eb6690410da62e97b50ef0258b77e50d94bd334e4f7b9a70e16bdd894ac65a3f6fecaef29343ebbdd3a073f7da60c016aeb71c18d3c7d54dee8056f8e08e9af11e901da3eb66f6264901b9b0591383508f67848b6c5fee018087a0e98630bc23c270c7564eba4d86797cc135e08ce89d50f5d444e6a86f2d65dffc22a0ad276e96" + }, + { + "public": "b80c1bc693fb7aeb6495e19e0f2ae7d417b5bd368d16a20bbabec37a550c418943b075b1770cbf84f81d3f0289d620f9b78ee664f2906329a14ec6285395358020f3a43a21d63d77d45e3fcdfe0e8f24d1d13dcd002917550fcb1373658bcd6222ac90e1db7eebd936f07aad274b279a40f11d9c92675231935eeaca567f6b40", + "secret": "a1807a0ad24823e24dcba53872d3a190e125bb8856af1f7099651717704cba47ef0ba3dd5742a43828260ee200c672167f709b90d188644b2681e19d38f8fe3943b075b1770cbf84f81d3f0289d620f9b78ee664f2906329a14ec62853953580176733f06786c74d940826edf21d63b6b30f5f150d2edc95c2cd42a9181bfb19236b47d9c2da8668005109a7cf7caab91d28d6032af4fa5cc0e8a08e595b906c22ac90e1db7eebd936f07aad274b279a40f11d9c92675231935eeaca567f6b40" + }, + { + "public": "5bc75e3d76b3bae80f5be1b6cf7297d15c70c8534eae9c62f6fdd2500aa1c7fa9716c0bd9bfc62932580167f716fa01b4d5d07ee0948989d7be25360a60cf0ab6423f8464f7fcd0516ee3023b1448f5425a0667c2cea6d5e02147fca277b0faf412ca0de50f7b1756f53160932064ddcbd5d56f8205c98f9b7ee6cca6005dd37", + "secret": "27b4ae2930aaa6ce4605210ec2b08214eab4325b1933962d58d368947d0b776b9728c7483dab6128f15611bf237fd7d2395341471fd62262d3bb9992199f74669716c0bd9bfc62932580167f716fa01b4d5d07ee0948989d7be25360a60cf0ab7de54ed0071380b934de9a17fa19f6e45f6cfc0885af603c0c804d5367418828f59f1dbfc199b310ab2fa43b707e07af64390f1d1590231068d473e9c2f0fda9412ca0de50f7b1756f53160932064ddcbd5d56f8205c98f9b7ee6cca6005dd37" + }, + { + "public": "75ee884697c3fd78197fd9190d563b0f51652509fee8c8f86b320de08eb11960b725d47c9aa0587b17aedc30f1ba458345bb36e85e714978ae944a31402718524a39385bda61e4c02f8c68dd7baf4c0e6d6246fa86fea291aba4a7ecab9a8d21ca2db4a6723c321e4cfa5029e5be9fc1b06eef3d5f1b56763bd9f6a0395a741e", + "secret": "22a8b583a5f27f36c95939d5fae54dd9eb233fe5771398a899856fa997a913af1cbb8ce89fb5a95dbcc8500dfd8bc30450f670528070863e1533279f132a63b4b725d47c9aa0587b17aedc30f1ba458345bb36e85e714978ae944a3140271852f7f095a03eed14f2d82672d223b6e0a4c9cbe396daba886ecf2f92fb971db40965ac28028628f6e6b23f4dcd2d4d34d8ac2791f4a16b93eca6c9deacdd97a1e6ca2db4a6723c321e4cfa5029e5be9fc1b06eef3d5f1b56763bd9f6a0395a741e" + }, + { + "public": "6392c9afe9469bd025cf5839cd0824f9a164013ba68d2a6ce15237b2dff295f828c1779165009dee8a91f6c307bc3ecab19bacef376bd681fe1220a44442b471592ee2afd3d1c5a1f908d4b87f02ed59741ffc3670d576aa2d468e76a6be5a3b37c8e514a46fbb84a7a69c9ec549656c9bc46e27be06ec6b3fca9644978b35da", + "secret": "15f038cc121a89d458ad55cf75260810adf0001e797ea5d98426fc7ea25c495760dd0c371b186e9c6faeb45dfde59fe8445d0ce5cc6073e20028b182113d309628c1779165009dee8a91f6c307bc3ecab19bacef376bd681fe1220a44442b47135b8965edb7658067bb57edcd40b427385a63ec379a1457d7076d5a99c2505254642646384df0dfef61c8f5ef3c8fd987f21144bb10316073457fea61850867137c8e514a46fbb84a7a69c9ec549656c9bc46e27be06ec6b3fca9644978b35da" + }, + { + "public": "3342db472c6f1410d70e184b848b575d3c0bd9be7d429753b7eac80d72a7df60d8f5c92ed56964d80b6c628d250baa4fa9692fe1f5240262b3f4138c22204e99335d509364adbe595c40e0313aee504d93cc076d978b1c369c026328e9557b9f3abe9d54e33a7a409e715b53485d6b119d8125410fff5e1658a47996cff852df", + "secret": "cdc39f505a338f68802ae2e8ee56d64513112bfb58fa8c5177f4377bfede579f1c0caeff56ae86a8889d619b04731a5d58c370792a092e508118fa9c92292c3fd8f5c92ed56964d80b6c628d250baa4fa9692fe1f5240262b3f4138c22204e99a59d1ecd404bf6583ef91aa94f7a75e7a50d7c176d65e57c738844690d06b56d97582025675777ce58499d8c53bf62ab70676d10bacd4b5ab6adbb954d5ef80a3abe9d54e33a7a409e715b53485d6b119d8125410fff5e1658a47996cff852df" + }, + { + "public": "d8a288c73a40f3728974a80dad55720f0b90a2e15d8ba543bf9866a9a61440c7baa5cdc1e7ae769cfc483bee9c39fe991d6d1f412ebac4a783f39c265cc8631a41250e3c3d7d706fd3db7be1cbe61b261966f60f6be40a7f4648738c5bca3206debff556b524ba1a412061c0420a0ad7958a2db9b44b306413590ba14b8bbe6f", + "secret": "73d155cd04ac9bab7475fb6e8f8e061435c83eb9514cfa6fd30ab49b1b9eed36360876a6837bd4610e699242ff17754c38949c59b0ced2d0cd60b10f22c0734bbaa5cdc1e7ae769cfc483bee9c39fe991d6d1f412ebac4a783f39c265cc8631ac50ff5a502885fb95a052d86d095b05b551a3604563cb5b8c2c598951fd5b648438b028a2535aed37aa24b62c77cece578b77bbcd699b1ebbdd26b47d9223699debff556b524ba1a412061c0420a0ad7958a2db9b44b306413590ba14b8bbe6f" + }, + { + "public": "fbbc8d126b800e5853254b4dc1f18cbba6fe777cc13daf254f9a08f4b63a7a77edd7a4115745cf64c1751a361ee5adfff493696a7e631cfd64abba876e0d760a148eac6c805636594c1601e0a5ed5a3f86ece6a49fca4bf47884dd810ed0dc05bebdb4718a80b1920fad1dbce18c093757e60bd5a17d3c4f4c4b034be7acbe21", + "secret": "7e6ed95f37ad13409b1dcf1cb43083501f5937fd5687ff9d8604e29f5646df174dd3531913324bb69f9b4734a0b5086385b127b4fb90cae19b6616f018a7f8fbedd7a4115745cf64c1751a361ee5adfff493696a7e631cfd64abba876e0d760a68a05b97d73dcdf384434b95195ebb3aa14c3973f3bb0060c97cc451d21a55df3b7f38a1666ddb067ac857bdb4b07ce7eeebe576e7e7fd42469659b4666c3f0bbebdb4718a80b1920fad1dbce18c093757e60bd5a17d3c4f4c4b034be7acbe21" + }, + { + "public": "2279d7c22321118a912f79459fbef18a4344a29e9807b50dc888ff74d5d852965bc52a1ed07991eef562c61593bfbb54b54049e5a06967ee55adb6fe87d6080286ef55550e8325be28dffe5c856f489720b062358a634877ccd4dc038797d2fbe30e6b788699d11bc34c88b70e42149e7667f30ddf8048e15c3fdcd7f44cf1df", + "secret": "ef6abc6def37ab4baec45643e32189d1838cdac3a9ad784b2c04e7c90b1b405d244daaf6e165b989a3a59a37a1d86fc292f4e8141a95104e5b1fa44f2b21aae55bc52a1ed07991eef562c61593bfbb54b54049e5a06967ee55adb6fe87d6080279302045e611f51d93bcbddcc456ff64bc7d0cc9a3dfcad64ae57008644a3c3b39c7c26849ef6495d6b3141cb3709417727dae7240888a7f8712b67b822c0d81e30e6b788699d11bc34c88b70e42149e7667f30ddf8048e15c3fdcd7f44cf1df" + }, + { + "public": "f1b021f5f3f950712765fc809fdd2d1ba13eb3f29cf2e1f7741effdb66392dc6c10dae1edd5b6c4bb6b72d72d181e16ce17ab1c7b096417cc0fcf84ebd3a6719a36a109745651c1d607e845f0aa1487430f3b2286c2b54a7428fbf1fd2085d1b2a0b83bcf4aeedfb5a77317619d55f2c2d546791baa199f0713cb0716d22c290", + "secret": "675225521bec85c3e6ff5e117cb77fd06cadd419fc650c503909051fa2b6b9dc93777787cf0d01e7e04e0c86749eafd54c1cf149e83551c4be4e2e879a81e126c10dae1edd5b6c4bb6b72d72d181e16ce17ab1c7b096417cc0fcf84ebd3a6719b3c00bf1a282b5acacb2208264bfbabe27debb129be48fa3c7dcc56b07ed58817fd3a819cb134e7d8000f9055ad06fd4d915df2c63f5677fbd81b6b6845cf7902a0b83bcf4aeedfb5a77317619d55f2c2d546791baa199f0713cb0716d22c290" + }, + { + "public": "ab6d25d6c9f942cdb20ca35306a65596b4c6f4f8f9b9bf8c660b7e41156a085a5d8da89bd37f09a6cf76b023f539acbfef70e115ea369172ace31d2da1c9d6f3e745e3671e8211dc51c5006e53798ae8345957f2caba22e8ecd0c65c5b8f769df05865bda9cf8cbd795b9dcf2eb2372bc2a361b9138735410f824e3b589a11f3", + "secret": "7dd1b3a777767da8983e8e9a895ff7d58d211e5889d2136a46efc6de7ea2aca23c7572e73ca51d3c1141fa473bbf88c74565f4129ea6b9188e0060a451c421935d8da89bd37f09a6cf76b023f539acbfef70e115ea369172ace31d2da1c9d6f3b3da5bc033e5d1ed69678567d92aee5c98e3f49f20b9dc48ef4171adcbb86b9098aeea9c8f289ffc69b069dfacbecf872c3d6faa3337b3c9ec94cffb13140869f05865bda9cf8cbd795b9dcf2eb2372bc2a361b9138735410f824e3b589a11f3" + }, + { + "public": "7b446de3cf9f8d1505db41fdd8dffb8d91b93cdd6a24d9b9edae8a1c826b7f34fde29ffd6dc63004cdcc16b430881a114bb5623e6960dd612b2049d038a9e7c42d555d8b4ff7d14890c96e4fe983cdb43e37061ed479b74d315d5f6aa38056dc813d7f27a1f9f3170166da22f98403e6fcf7a9c371a125d8ea7cd011bc378c14", + "secret": "3a1c051aff0ca65447fbed0450a6b1f493c07058cba2b624c84bd391176f8a4202a4b143f22c0b7c669f3ddc6b89f5bdee38c8f4de64c746fdb1d463b9ef57bffde29ffd6dc63004cdcc16b430881a114bb5623e6960dd612b2049d038a9e7c46326aa76890c56272f2154ae75e8e0b0386b92a12370a8fdef06ec5a46cf4b2db80e37f96fe5cfa3f5b0567514bb9d7a7c6859b5736ee6bfe1f94fb15a8be400813d7f27a1f9f3170166da22f98403e6fcf7a9c371a125d8ea7cd011bc378c14" + }, + { + "public": "58cdfaca51585dd64a63ab61c25792868e1c1ef2771548c1aef77dd7b07f2e5110ee701cefade4841c9761dde24171e2de52dfcbff42083596e271f9335b002526a2daa2a9c11924da2376cf123821727f79e8d7b361b8f784856a909778c9295d17a0f51692a47a222e2f6778698e8de146da4148b6bfe34e5487bad40f0ac9", + "secret": "86488ca527c5135d6e5974125257d2e418572238469774dd17edacc5c14121f92a25703f07e90f049c26bdb8b2f667408c136ff2dd56a4e8b7ee2ebc447a850810ee701cefade4841c9761dde24171e2de52dfcbff42083596e271f9335b00252ae04ebb5653dcffc116f5576e4c8fa98751d0366cb43930d80aca97c6519153fb909efc28e5472f5bf0fb603d3f61eec4a0a09e29809fb49c0a85d75893df465d17a0f51692a47a222e2f6778698e8de146da4148b6bfe34e5487bad40f0ac9" + }, + { + "public": "709c934f9794da22e53181bfa9c61ba3e35a156d30b70523e40e44ac00329f62f41ac76e5f663b91b1224b2a18652c38233f4f0065d6cb44006977ef08305a67b6078bea86439b8e1af64538f0716c29d159d34b0793e7db1767b634169f9777dd0aaf744c1e222ce0f0df2e0f9368feb8c694e804a2748a77d70f5ce454ff64", + "secret": "39c708019fe30b9368cb99fbb389edfe46ea4a346db8b44ca26788ea6fc87ad2526731a36d9dab32687b7cc5bdad49e38577c8fe7ac985d32fcefd4b77f85794f41ac76e5f663b91b1224b2a18652c38233f4f0065d6cb44006977ef08305a67dfe38c000c3c151b7355da09fd7c0be260668ed2e915cf30c71596525830ea9ab35430a494998c39395d9de632908b82e9c107243628195a874c703ec9180c67dd0aaf744c1e222ce0f0df2e0f9368feb8c694e804a2748a77d70f5ce454ff64" + }, + { + "public": "5b7433b98894fe1e975ec92445d0507e2fa1d7e8f7787ae51b7f65e5061dabb62d05db11a8601949de91b266cb8e942adeb58ece658ff5f6e38433eeac20b122788181888143dba953530c7642912a53ddf8368f123415793f511760b8d81b6411099c2aeeb19fae2018bcd5658e82cf15c7870714e860edd823d5f94e027c95", + "secret": "3470d0a641cb72532b32f22cc320862c0214b320602a794d56aeab74707a1ce97c1e0688f22f0e3996e2f7d78830a637738f882e67ce29d1cbdb4963da1c60fa2d05db11a8601949de91b266cb8e942adeb58ece658ff5f6e38433eeac20b12206206b1ec1bb9c2c04bd8e74d047668dfe095ba8629589d116e08d1b58827d0c2161b01c2120c32efcfa1d8af12659e92d18aea8e1dbd334a30ec34f57b8840311099c2aeeb19fae2018bcd5658e82cf15c7870714e860edd823d5f94e027c95" + }, + { + "public": "2bc3716bf9529875b8a39fe41f40c2fc7fa802b7c1d6639ccf33026e2ebab4daaa496e4a411e745151e4e95021b98124ab0d1c92c76b64bb32d6414b829edbec8922d37cf03c2c6931d76ea8f1364e5b9382f8ed93c89c011d61fb94667d5b28e9c2072b2b96b2ae2407cfe1b9a25cbb562453dd20055ef9d50cf52e0b46ef4e", + "secret": "466a6d80ae831ffd0b3fe336c6acb5fc4609da585ee338f421a5ec34d236744204e1ac6008a4a72ab2ef9765c7a7361c5522d3ce93662e2bcf4d261c884a5ba2aa496e4a411e745151e4e95021b98124ab0d1c92c76b64bb32d6414b829edbec9816c4c8d20aa21b1a88d39ae3b1d9a3f6327852dab905f1925dba63bb54ed51f38d90b7fbaa497bdee6d0f738f5ab5a1dab85a7826c92bd84f210e63e2cd301e9c2072b2b96b2ae2407cfe1b9a25cbb562453dd20055ef9d50cf52e0b46ef4e" + }, + { + "public": "6b09ed1612b9c156a5e55b8e990548bf7570fa1c719f6d0f9d18e9219bfc0f95daf97ce1c02c351274fc9397980e03ffcdc0945ff58ebce89e6a2cbdb107e58216a9ad62570d258d0a15783439f149d79980704493bc9edb8d1dfe7a872c3211d86c68aa4f4e1163c919083d16ce02467e8b4962df9dda77e854bca166f5c4d2", + "secret": "8ac9f332a30ee7e4b493047ec01217e3603422ec25cffa5c0dbcb21e74e15eb71a5cc39a65b76a0a904f37c6759a65229311f8422384b772f3adf3addf0f337fdaf97ce1c02c351274fc9397980e03ffcdc0945ff58ebce89e6a2cbdb107e582d79f6e805b374168d848089256efdb5bc6a09d25608da0548f97fca5f2af0ef38c7ea0c71d673c639a5db56e93090660ec679dc6f18c1186a8e6d348d1e1a243d86c68aa4f4e1163c919083d16ce02467e8b4962df9dda77e854bca166f5c4d2" + }, + { + "public": "1d3cc69557b1cd6101210b030610822a21d6a569bbabd51d4b5e5d686267020a603991cf33cd2b3361e5efee06ad8c10185aad07be028476121528133d8c61a4b955b75d9e6c7baa854623ca188399439c6eea47efc78448387d5e9842804ad7893763d9960cfbb6b0a19f8381d836e77dd0e9462645d41c7be22b38dc6556a3", + "secret": "d4972231677129e772c5f9a95669b41e24ac3a1550683fc9e34f44f6b574af244b2ebeadd8a73005e3d0b2a7fb10f90096df2b9244a912ca3af96f51926bdff3603991cf33cd2b3361e5efee06ad8c10185aad07be028476121528133d8c61a4b484c3b5eef7d9a843fd452ad18153d0014589a3828f8c0c1507fccf0a13b3d3fcedd6bacd477dfd12c072fe4d8089a4f2339045dd8408ca8ba0b2d63dc4629b893763d9960cfbb6b0a19f8381d836e77dd0e9462645d41c7be22b38dc6556a3" + }, + { + "public": "465bc7cdb5cda2f92d671c397f72b77cffc04288c965e2bbba5be4902cf80f574143c2bdc03975c3f90604b3207772356382229911f10f4bcce8fa488bb80659ce9c49b716913096fe880effdd670f2a1413be09932a914dd948a353752b7233bf6fc391daf49453f0c8f319c713ef5100428f70896954c45d4b6ffb294304a0", + "secret": "1205555922c86889672d0cf0a58eed5e5d70295cb8e4e83da20b66956c92d5c74815815ca34670c574acec817c9521537847b5466ca222935a465eb3e07e9e644143c2bdc03975c3f90604b3207772356382229911f10f4bcce8fa488bb80659e42fecc9351fd5d28b8b6531dd65aaf6a976525136bb5f0ad8a2e4c90644181b24bfe401ee17365bf6e10868141ca4bcf9b41585cc09d8bd5dc3518625058ea0bf6fc391daf49453f0c8f319c713ef5100428f70896954c45d4b6ffb294304a0" + }, + { + "public": "8e3076caf35f95fb0d462ea1a4bc1447294472fb51ec71436d6aae35b1c6765670906276b4d01b14c4cd797f5e9864025cf7c18674e5f3972d4e8ce3afb94c5953a37257ba621a4369f7a46ddbc0ecb2c2b23900bd082075660cd4791c5f279cf74e040df332be4039a4104e27c31d78965638404ab930928f5c7736ae05a0a1", + "secret": "2d7ad3ca04e40df080e8764ec5e2850c6dc4e2cc52e95740cab66652e62b1748b333890bac28914d0ae620112b545aadfb9e6dad5a82d0199c57a4e510764cd370906276b4d01b14c4cd797f5e9864025cf7c18674e5f3972d4e8ce3afb94c59976723a251196a4c29fa299b40075af4c464d66bb7c35b2a6758f3673329a8fa338658cfa8d519a580df9158d043652ba81dfd4169ac0639d4fd6573a0cfee0bf74e040df332be4039a4104e27c31d78965638404ab930928f5c7736ae05a0a1" + }, + { + "public": "9247ea5fe88d43e6296b336964c2bc43a868f353c96793f5cc68f2acdbdcf08dff89b1d3bf7eaefedf2df2c5f5346b4cb1eedc23a78939ce1134a140f60e91ea4d313da3e134b81bef8feb7e5c71bde6157d47c926eb04a769d7e17f019e38d9118c804b2da4e8eec16be02562472fdff21feb354f635fc2162ced3bef314eb4", + "secret": "701a02be629b1dbd9bfb87c62823d845b3a5095ee49e17de924696abf43724cc7685becf24990308dbacd7362a10320b6580793df8432a84270bf76eab45acb0ff89b1d3bf7eaefedf2df2c5f5346b4cb1eedc23a78939ce1134a140f60e91ea43151ba5b12f8f51ac8e6c4ac3a5840daddd14ed9e526f4c01a00ea6ba2fb37bdf9139006dcf05080845182ccaab78463ab511f6ca9ef0f079b78131b2a0f902118c804b2da4e8eec16be02562472fdff21feb354f635fc2162ced3bef314eb4" + }, + { + "public": "40cc9214b05debfd0f6ac85253a93b9795b3405128d67cd95cfeae27cc1a0ceec5469552ad18903c69dbc0abffebf3add4becb38e8ce437ea13097cf3183325759c30e7190c737ac1fa00345854c6643d7c75210d9885dbfe755c0e2790a0868b17e1a47673ffd27fb9cd906f13b5937a63061e7a75b76e6bf37da37f5c999c9", + "secret": "02e1b4505fdce97f82876a71d4e9e983f985ff57b5199ccbfeb49ba5a5bb653222425968cf3641de2a2e1566263c24dbf131a27a311a9fc46e7ab1ff51b292aec5469552ad18903c69dbc0abffebf3add4becb38e8ce437ea13097cf31833257bf64013bbb542d9254997e9b61c797ffb2dc201484e6621359ef0cda4180685b0c2f2d32fa71b7dc6dfb14974af7682fa120ccd301800729751a2466ed06529db17e1a47673ffd27fb9cd906f13b5937a63061e7a75b76e6bf37da37f5c999c9" + }, + { + "public": "94347f749ebc6d4a467e453932c5bb49e4c45b202a403a79732fce75cb9309a0838db6a8f8b743223209ad63ba2e4aff72cdf9da48eb73092e73b8fcd4cdb5ad3dd1070d8be62cd3830a7e84ca888d2571ce548ef4b09ec29c5039e6330671df422160aabb36d5c7521f54a58429086ca097f2db977ed084b03b86c7ec030ed2", + "secret": "acd7f428147d459af3ac01efc5006df3c1d0cf971cce85ac655fabff74c6dcc0ecb493fe192a572b227a2f341271c9c0b0dc8fe0c4a1032c5952a1fd8da4738c838db6a8f8b743223209ad63ba2e4aff72cdf9da48eb73092e73b8fcd4cdb5adffe09758b3c09da5e7d8fa09ac0de249c9b92e514ef3d9629fd23dbaca836117dd6094f805bfe8b8708db83a5c6df008b51b6adb0093bd039b3b7829291e60a4422160aabb36d5c7521f54a58429086ca097f2db977ed084b03b86c7ec030ed2" + }, + { + "public": "e67be3cd0ba541dc742e5bdd003a79f2b43e3a04050a4ee7ab08d54b0f84dfbfd253b179749114250586acde58edf34d8f5b438791ad12868a865e72ffba3ce91acb8f2bdd27720efb5c5c89eda690bccacd2c4d3ff3d83c124d1331495724fe27b74a819a33f6bdf718355c8aabedae334a14e3bc1ad75719ef404d8847d29a", + "secret": "7b1aca9f38a4c5eb9b0a061602477c017b9aa7e3971f97da8c3b79fd2b4efd2c7fdf33245d1b951f0ecbb6c659275bdaa20a18cdfa3efb5c60b47de6a9507bf0d253b179749114250586acde58edf34d8f5b438791ad12868a865e72ffba3ce9539e7b48d22370c76836478976ff2322acaf26c86fe071d978c80c634ef49e2f621bf428485eb9a827d5ad822a16532e8486aa2897575a01c5c701c535e165c127b74a819a33f6bdf718355c8aabedae334a14e3bc1ad75719ef404d8847d29a" + }, + { + "public": "33d85f3f67834bbc71c44524f646f586d4acb430454332e6b3f9c98db0d3326ced5f8daae648b232c5d8a9a5010baaa29e30a254ccf7bd2e5081cc6cdd2fabffe844b2aad81191ef43e584d62e4e01da31d9ddf36d8278e349b712e9d2680c712844a43e583399e6c82295f29680579f6077199a0df1c99f6f801e73052fc28e", + "secret": "5269bdf6a26f14cbc474f6e12e2052aa48a7508eff98967ce5a7b215ee521546f18e5d06de492d951d7cb8087cbfbb3dc032f832db70fa3154f9c35aee9cc76bed5f8daae648b232c5d8a9a5010baaa29e30a254ccf7bd2e5081cc6cdd2fabff221ebd18fca51a7f3c6d18e054c0da65748835a69200ffa9f9ab85a3cc2418526c6bca2dd0747759f171b6cff783e615f050d5eb5d7062b1f587b858f3137eb42844a43e583399e6c82295f29680579f6077199a0df1c99f6f801e73052fc28e" + }, + { + "public": "10a00a2677607b95ddf45ba5c4567f790e1611a8de1449ce086f568a7792a439de77133c971427147eb78f927bd493de2493fc9a3eec10ac2d1d4ce4581c5ce64c16053a8e3c898901c62f9e681ec08ecd6eea5df17db74f89a61222bbe1ea5fdc0bf5f2c6167cd9e0f5b2da87e7c7a8b6135d1207265d27803799a67aa19ab7", + "secret": "8c41e665393f88c0b376abdf32f0203d480a9c5f9edc0b98cfdc2d063faf8c4576a6ebaf43abede503565f024c4111771ea59c1c8e2e7d75a3f779ab3f82ac81de77133c971427147eb78f927bd493de2493fc9a3eec10ac2d1d4ce4581c5ce606250fbd1cadabca9bb73f66d925ae15251d2ab61358cc33c6ad3f55fce64eb0cbc880ae5e6badbcd80c5208ba0d3b8a9b776b553d56eda237c9bed728da8e65dc0bf5f2c6167cd9e0f5b2da87e7c7a8b6135d1207265d27803799a67aa19ab7" + }, + { + "public": "88bfccb4dd4215d5b881529e337f8513eeca7ed1268c4448094c5fbdc80dea5e2ffc3b80a034f9c579214856dec8fca4d0de7a213b928a00f7c5b5c9ad5dbf8f2079f1f192c9ada7d12b2a021fa51e24c4758def6fb4824394ad07c874becd4f6657edd55022a2111309cec0d50a8a12710d147c6224078c2137d732d1c7ca0c", + "secret": "b5c43bfcfa11d0affa07f126137ebbe1a3b33d65e39d9406b754ae53593c85d30c5b3de4c93e56bf8149892ed156b8675bb91875aedc745b09bca20efc3394612ffc3b80a034f9c579214856dec8fca4d0de7a213b928a00f7c5b5c9ad5dbf8fa0b85bd333ac47de79074bd44a5a9f039df6c528255a545ce7072df7fa42c8048865c7fe0912aae0c442b1a95da5d762091f550923189518fb1d005d6f5234776657edd55022a2111309cec0d50a8a12710d147c6224078c2137d732d1c7ca0c" + }, + { + "public": "60b2498e5b200c3af95b9f90a6cd6a155f75cda57ef3cd76e1dcae6f32c0dd205316255905d8cd7d517e769b5f7d3b64ed3d52802923db90e4e2f62b9f350e670dc02ba3084df1d9a04321b5d58b2cc9eec7d149f6675df47b8fbef14c1c50a64d3a44b5834890cd05eb92137240d68186ae21fc392a18a76394edda892be10d", + "secret": "29a1c8cdce3986b4f5c0ed22a6e6e1aaf3523c669362c76225fda2e2f30ba2f4b16494a840d0a45b3ae459cba69147004d89c4b7bb8537e3312a9929fb39a8615316255905d8cd7d517e769b5f7d3b64ed3d52802923db90e4e2f62b9f350e67dc870f0c897fd031d4c94711d0b18d503fb7fa0152f4055649258722ecaab09105c8d16871f4af8e1b205760a5c8d163cd04ef777f15c72a606340ec8cfcec244d3a44b5834890cd05eb92137240d68186ae21fc392a18a76394edda892be10d" + }, + { + "public": "9f596bb3a07e89d76bbc4af9d487676d25ac7af1aae1fe2e91e1589ed4b719d5b7a2e338549099d4cef34da4be68a35034e246fc6a4f1ccb46aa14eb029e46080aebaa962cccef4f8218ea526c2d572103aa62e55e7ab570e4b3c9ead2593f359e8d4fcf0b0178343f06d66c9e83076828564c4ddc48a2abb50932b91c40ef80", + "secret": "af3da16789014f93ebf6d9f2f2863afcdba7b16d7525b2f1c13c3b07e4738dd78b8af092408a5fc05e792a14a2cef762648f1233dd75b7e353d2ac833dcafb65b7a2e338549099d4cef34da4be68a35034e246fc6a4f1ccb46aa14eb029e46080c825933c32a337f743920ab41346d8fff0510a17a8a457a31734ab323d1deec6cf8d4f110b580ecae67139560cc227a1299c1a06f61223ab6cdd0a404b2df389e8d4fcf0b0178343f06d66c9e83076828564c4ddc48a2abb50932b91c40ef80" + }, + { + "public": "862b4c24ed2fc08223e1ae173d670e1dc251d2693d866504981610ad424d0d7110ea47bc327c3a3dd2ed6ade54647dc48d05e8a856af51f5418681a71d903e3db159a4a68389138e7938e4f72cdb9912d592bc8452bfd4be9a20a59ee915310cd82a5c5ae294abd3097eaa00be420d3ce5bf4ba6f303c1fd52c2dc5f72318caf", + "secret": "e0a7eda48bf754c94fdd49e175204fd6236e630938c85e2ba5d9f3946433f3857aeae6162cce9ac472bca42dc5fe703f614192fbfd9bb871f76e1a8f1ad09a9210ea47bc327c3a3dd2ed6ade54647dc48d05e8a856af51f5418681a71d903e3d911c5a2615d607c9e56f264cea8c43667e56916f6c9b25a4bd4901602ffd66c49dc317f7312a146dedbaa34e63b5f105fcc208230ca3f09abe9f3c9662b15046d82a5c5ae294abd3097eaa00be420d3ce5bf4ba6f303c1fd52c2dc5f72318caf" + }, + { + "public": "4749fc048895ea14232faee6b20b7942e2be0f89b5997ea5576758cd5252f57ad8393ce685eb74859db4e7fb18bcea10446901acc553b2674436c94cfd2555c450ee4995dbfcbea6fc6b6d6a786734184aafff3cf808f7fa0b41027dc28d2550252ba2a7dc798b1851dbfd84fc7ae5b02d5b637dfaac714c1aa41cba3641dc20", + "secret": "de9c5e8f18eba76dbbcbbe6ab6d8c8fc4893b35141a27541248d1efcdc3f4cca1b52f0c2d16ad91371d66f1cb35144befab18b8ea86dee9e3fa4f9212cbb073ad8393ce685eb74859db4e7fb18bcea10446901acc553b2674436c94cfd2555c4c7bf0fad6db92dd636fd02c04d16e3dd05f6cffb3fde1bb043cdd1f324010e30f5398b0b23082ae63b04c226bb0d2d789d281842c592d7c124877cdf0fc5b787252ba2a7dc798b1851dbfd84fc7ae5b02d5b637dfaac714c1aa41cba3641dc20" + }, + { + "public": "5f98241b77f6ce99f7bc761b5da7d49bdcf1c9edd52ae1b2c37a779aa5201b12f78f766e5ddc13f81bd47e04b523e91a9a14f6bc70ca60c6ebd8d564935dcb4bac44c2b24b3fdbe75cfcf641e60b64b8a142e93cacb2be3d0e88aa2cd8de99cca6cbe0f8235e0a11de1f24095318cacd6f80c9a70c8001a869ee4487503f9d03", + "secret": "d34d32175e5d16a9972169f51abb60c07170c9660dae01b9ef1c8b65f285552b323f55fe3926c2666103fa49b6218de3b5b06347b2b55fe1d083811c6bab3cfcf78f766e5ddc13f81bd47e04b523e91a9a14f6bc70ca60c6ebd8d564935dcb4b62e5b200761b5ff15cd874146a9a8cdeea6a782950a7bf9c6e23d9b26a1b0e2b9e9b7c8450fe0510158af5f06e2a07fb5cd40c4fe358f71fac5312ae4597f0f9a6cbe0f8235e0a11de1f24095318cacd6f80c9a70c8001a869ee4487503f9d03" + }, + { + "public": "8c51524faeb68ceec15ae67426f5bbd9f6dfac866b9e5499e57f574e0ad4b0e8754ddf16eefa806e865514a305e8f40e856626d818081cfac1d80d7cbe894a89fdd0940ed948b0160e92c30fc4544229e454ba6caa37e39da2c34ce557c01ff26245d31856cd5f648adbadbc7410ccb29c1f3c253643627cc00f287aa5c65afd", + "secret": "f26b14b48b136057d1b169706cbf182a49e24d18c396b96763a834259c78d73e3b8854dc25e72804ec0310fc2f7a43757628451f67666623b3915d15857691fd754ddf16eefa806e865514a305e8f40e856626d818081cfac1d80d7cbe894a89a22f7691cf1fe3e5fa5d426c7a95baf8905e97aefb9048cc2e3803415d99d6dd03dbd02f159d687dda4a0e510fcda6839d650c39b54618630d8f8a26e37347426245d31856cd5f648adbadbc7410ccb29c1f3c253643627cc00f287aa5c65afd" + }, + { + "public": "e32544bd7e3d2e2751fb5557b6cc2a224067c2546bcef32193245d0b036d5bb7f267b5610bb07323d1df9b45bd58ad444bc5e472d7ac71bd07e23e941910e5e84b3a3368ed0a94cd12c03d8a40c17a40e7dba090312859f950cd86c2b2eabce82e71691f04c737981e45c409470045e3a2505d1d5a8c4cb1fa6e75d602ab6a6e", + "secret": "0047fd925f66beed48a2644b6586e46e0af3f958cd9efb9777fb3597d1459420fe3c3ad5a2cf70ab4ce3b41799205f09062cc7ae89952c49649ad2c0548de8b4f267b5610bb07323d1df9b45bd58ad444bc5e472d7ac71bd07e23e941910e5e822c52ac495105a12ed30eed7f08301bd1a53c2f4c6c2afa15833adcf09702114f5ec5b128670a0927e12b10f5a5f128bc988280b45f42259bbe5bc763e6b6f702e71691f04c737981e45c409470045e3a2505d1d5a8c4cb1fa6e75d602ab6a6e" + }, + { + "public": "3009665e18de85cdb69d7c34b83abbc17587d479c49c01dc9ba0a57f59db87e02c080a5d437464edd6047aeaa2e1d3d2b67ceb19f8034848e30122ce2f1aacfc16ad28e8174b8510104008ba98408670a726d64276a0f407dc038d596791f434022347e39cf6ba7be74d6310bbac148d375343f47026f9f6e19d548818b05152", + "secret": "27a4ccbd6d49f3e042f5844efff7a1218f0e0fca3c3cfe5fc8699f99daef8d6a5c6008331c648c52a8a423abd28ec3bf0a85d240a984ec538da416b7e4e1d5932c080a5d437464edd6047aeaa2e1d3d2b67ceb19f8034848e30122ce2f1aacfca71f9da097e9e34f842653cdc071763ac87dd31b974af4361734d8e7a516cfa2e739a4911283519631cd244aa728e878e4f403602b57000c838c8fad66add122022347e39cf6ba7be74d6310bbac148d375343f47026f9f6e19d548818b05152" + }, + { + "public": "a2139e2e4bf3c823b4200bd5ec3905687ed75860c40ee5cafe74e51fce17cec9ad84e929f8edeea613354fe129582888ba7eba175b3e9e6a4e73cb80617d7361be84152ec5cfc7d86a7ab55dde7582a5873c01003d9bb95d9a6d9c5260041a8ae56236295f0435aea91a97feaf688cef685ff4a9093820a2da28766f9ce90d7e", + "secret": "a0c08d223683e1081d68697d063b875cd6b329648235a888b2665be6b6897dbbf2a71cb0d56a84c47c80c7ed01a691474483b856923018e127eaaaa08506d576ad84e929f8edeea613354fe129582888ba7eba175b3e9e6a4e73cb80617d73612f81142c71d211f0ef2cb5d99c6b98e4edd2f6c664b64d6a6235a85784b5db53e9e8b2eb04bafe8263e07fe30ed2a6ff72fd33724f2b77dc9287a46fc21d8f61e56236295f0435aea91a97feaf688cef685ff4a9093820a2da28766f9ce90d7e" + }, + { + "public": "8fb0deb4bc0f9a10b5dad40bfb33432b62bef91a8526e86b78c179ee1784d438d4fd3344094c0e8d0ccae1af56ac9f614a48011348f0962789eb21220b8bf195b146f621c1b2af98605c893c27b83369ee9d7ce2e5f7985cbc4ca51823a26a0c74fc2ab59001df5676bc3aa223e725f16e9cffe6798f7a38f68cade50e1055b4", + "secret": "4c11d2d456ae3fdec1a9c7e8dc609d335e10ff491e12e4949550addae3b15896ce08250b8304a1a5a666fb490baf66502e1ae52df64623577d1061b9a0e2fc07d4fd3344094c0e8d0ccae1af56ac9f614a48011348f0962789eb21220b8bf1956dbb144e8a30d8552767ba10c78fd1ff9dbaf2b14119d86ec596deb457ee00af151e5fbe3ba58b1142970cdba5ea06cbecc66b42d22f6c0b6e9c9236c8ba44ca74fc2ab59001df5676bc3aa223e725f16e9cffe6798f7a38f68cade50e1055b4" + }, + { + "public": "838487b98dc8373c312bb8ff2376cd778be3c1e60e8da253e72d55ed80c015ffb522a1a0612e9260c5728b4838f08d2c5d35a44437d01bc708a9f5fdf5d6eb64d8a10100c8421ad01723ef9d9986084ee102932f9aee67650e8d632c89f36e75c159fde4ca6bdd9921a674090c2f06570c9fe988cd490d30d72a6464389a8a03", + "secret": "f2cd2029872238863a5bddb109b3a9ee3f2bb2e2c3da734662aa548442f8b189ff9008234034286bb5b670cb7d4a96ac10081f0aaf950d0d3b9afb3ceb95cb7fb522a1a0612e9260c5728b4838f08d2c5d35a44437d01bc708a9f5fdf5d6eb641bae6ecbc191c1883f171989591e4e466813d1f72702f82b5a99803afff6fa1fdc76881c46de1cfff0d65967f24cc8e3e6ce3e2cf4e5c6b0d517fe25728881dac159fde4ca6bdd9921a674090c2f06570c9fe988cd490d30d72a6464389a8a03" + }, + { + "public": "c7f0c9fb6cc6c4e63fa836e3ed8dee913496ebd9dc12e40ead9b4d2393d78da38d1c47083fce7b525258d2706a416c4bb599fe3335ef4f6cffdcdae0f9b9c9cfe55e76d6be80754caca17f15fee2db4813d755ba2a1fcf789a566c3a6740eef5f2ad4b9ce54dc8e7920c1e351d7f1103fb03e6b7595a283f656f5dddb87f2d09", + "secret": "646a1a40256d8c6de6065451482193193bfd1d118c48fe1aeeec8a0c20820631ae497171ba402980480de1fe438f04b590e3df1881a812874251a83ff31befab8d1c47083fce7b525258d2706a416c4bb599fe3335ef4f6cffdcdae0f9b9c9cfe9823411eba49e05b1954929a875b3ceddab08a2e4843af823817bd57b8cb86ae59d6a7a7b8006ddc47b6a470d529dbf523d63272cb96a6cb046ac28be8b4966f2ad4b9ce54dc8e7920c1e351d7f1103fb03e6b7595a283f656f5dddb87f2d09" + }, + { + "public": "84423871144430f107335e0210129f81bb3b58843f21d6a583e456ea89106221f9464d356fc6d55a5de0047b2d60ca5d1b3447b38b605b4d460a07cb1bb747d748c8bc6291fd252bbcb0e1249e224d078c6de87bb56e0a52eae9935f8ae3660064c2b3a5a6a5c14eb23bfc8376fbb1bd731ce08dd1ca2dd64fc48f62dfc4db04", + "secret": "6420f186b591d5e426eacdba2afa0c432acc2b7ab3ccb240d51b11bc679bb0c888c44b543dc2b1dbf1861b82ed44291c9bbbb22457e2eb709797965775e23ebcf9464d356fc6d55a5de0047b2d60ca5d1b3447b38b605b4d460a07cb1bb747d797942a93a4c2dc0b626991a197fd72c04fe5b871ce75ef540318904be85bf8e07084eb287c17c16edc9547aca96581d1e6568ce4abd57253f758635e357cc6ca64c2b3a5a6a5c14eb23bfc8376fbb1bd731ce08dd1ca2dd64fc48f62dfc4db04" + }, + { + "public": "5a5e97d48337a8a418e80c4d0c2dbed8e28e7dedb38134b0d4c1f33313165a4c6da68f11b0c9adb80afa2ed9f7bf2ddf542c84e3ed10247554660b0f2461f4ef9504f5f3eebc208de657bca0bf20efb39fa76ec22a29e891d792254e98066b0084688f10087daeb11eeb4d0aa1e9a76bffc35ee4496a67cdcc690e448d815747", + "secret": "25e9d877c2fb4bbb8a5e1932863bca885faad3ac17d324302e9dffa7db49ac6dc70c30d833862be718e678ae8b186fa6b418c214d88cb55ae1054370bc706db66da68f11b0c9adb80afa2ed9f7bf2ddf542c84e3ed10247554660b0f2461f4ef44b25df16af520a05631187e6aa493393ca0964b4fdaba4cea4edf8905efc81291ab14f4cb986afd229c3c1555d6c371dc1655418a0354a50e72f2cc7fe127d984688f10087daeb11eeb4d0aa1e9a76bffc35ee4496a67cdcc690e448d815747" + }, + { + "public": "5d384a38eb9afef6e0283c00f592a06e5a7833e51ffbe1bdd1fbbc4511bb5d1c31c430c6f8e7ffca1d80104282f260fe185b40f8d824079d090120787328c6974821b3a0028cad5016cf599522923591f66c9957920eb11c32de3731bdb407f240c87e0eb4db5ba8a90478f75b1933e2ebf7d227218b23b0f3f3b1f9c54929ab", + "secret": "0a53106e2667ea22413b2343a1cb69d6a715e84c6896f9c48340b3bb6ec2151d66916d4953dc000ddec28ad5997cf272a00886e8f0178fcad6110e40271c485c31c430c6f8e7ffca1d80104282f260fe185b40f8d824079d090120787328c697a94018a919dde568e98b5bccccf03bc21d993e98e539c22016734623604f7753e20d8a192397704411065c9ffcf8f5154b981862181403092c56672c559c9a9b40c87e0eb4db5ba8a90478f75b1933e2ebf7d227218b23b0f3f3b1f9c54929ab" + }, + { + "public": "40f7fbfb0b5c1cb6899a05760735427d5fcf1343eb396cf24087d0337bb781f446422e0bc9fbf1be8cf056968ddc15c1470f9a361925f1080ae38be1550a870d1950e377918098f1bc113fb7330231a53126b73f9a126091673ad8d6801bf448b2cb3beb42c5cb52c231ade4a23d6046ded21f06c917610db8f2ba788a5dfd46", + "secret": "bfe39abd061d98ac32071028474e12c31132b86181a5c263ab064be099db0336f4571ba5db9550c58b8bf8bf94fa1636ca61a8259d4cba1d0b1335f327485cfd46422e0bc9fbf1be8cf056968ddc15c1470f9a361925f1080ae38be1550a870d98e2e4a14ba733c19eb0a10fa74dcb4aa3d816a00fe66de4da73ca5452a642366c0d5671e5be763383b30876c4628f66ed36837218a1c25141ba4201f37d4f87b2cb3beb42c5cb52c231ade4a23d6046ded21f06c917610db8f2ba788a5dfd46" + }, + { + "public": "2260c90f67cc0a832269862eead82e93826fe1ef0a6a1271fdefb98e76950884c9271803a372835b2e9da0126f8283871106dd5af2f53dd08ca314ff09a4b56a69352c5cf3f79d06e412aff0599632ee6cd06599f4db0cf6e842cf1af7451d55db144f75b5667391d454e6a0b7400c615d970088cff0f71a4c3e2b2e7bc9ee6c", + "secret": "01f39c4280aff1a76871910fb1bf58f4f571c5958c636af2d688e7435e5f0aaa07f329c78d66b1af6d446ee5f37af1762b6f2bc42bbb957930d26bac97e4f841c9271803a372835b2e9da0126f8283871106dd5af2f53dd08ca314ff09a4b56af1ceac0d86e05968fdba1fb24e9f390b8d9ec90b8c461f5da8603ec6cfa9f4d21d31e8fcb1e044ceadd732fca1a0120a812051b63093a9ba015c9022632e314ddb144f75b5667391d454e6a0b7400c615d970088cff0f71a4c3e2b2e7bc9ee6c" + }, + { + "public": "ac58613289ddb05994e3fd328c9fb43fac281a409d5d99d2764c733f13d17b77b8daec400d3d7fe4e7bf951bcaee364613258c737f6ecbd9acc5f950a7dce8836cefcdfab13453675a91ba71bfb8984589d47b8bd1b1d3ca71f7d0ca6a160d33764f6e125fcecf7157b6170bd9766549dd3bd1f6cf477b2e210b14a8b9039091", + "secret": "836411533d99f63713c639fbf900187e60ef5a9f2a2906f2a5bec97fa05f111bef90c6a61b940876809b2cd698c0426e0667cf1fa04e26c026ea1f9dd3a74cc2b8daec400d3d7fe4e7bf951bcaee364613258c737f6ecbd9acc5f950a7dce8835a6e7fea120d07b6035a1f493436b30f0b47683e642528cc40ec3d7b941db0bdb77d793775d060e15de7a8e0ab94e0e9661eaeded976a222eba813c6368847b9764f6e125fcecf7157b6170bd9766549dd3bd1f6cf477b2e210b14a8b9039091" + }, + { + "public": "7eb029259d5ec8a075c66d6969ec09710d57324e55ff49dc8b4ea86dc0a2b3342cb2f8f8db8ff9969c3066c2624eed913357f978070625451daf01f14e635e101567fcc195318ee278165e557b24b7622c643097c8c57120bb5a492b2e1b4961133d0db40038e87fa58be86fd7fc3f564882ae7a6c312a0bb8f0085fba0a2dae", + "secret": "ed36ee5fc1860507be0e47c2216550d6361a3483e70c22e3ad70f43035f70cc5d432b4f621d76dc61ee7c44804c5ce17f38c8bada1617b7105043e0e5019bb152cb2f8f8db8ff9969c3066c2624eed913357f978070625451daf01f14e635e1055ad20f6934ea344563e96fa01bfd9ff3470725b2b73bbb374f4389ee73e67266956bec8b18bae6805a2766fbaae0ba5d3c06de9de62be434aef4f2d3b8aab5e133d0db40038e87fa58be86fd7fc3f564882ae7a6c312a0bb8f0085fba0a2dae" + }, + { + "public": "b717b201a8f013e79ac89ec634a4a1be3270902cc8e71c19a32613db0bc2f67f1427ec05d2e2ff7a2ade48ffd72ed650d062ee07ed07cb089ed040737327f76f36f21596c08c66047f5d9e700bf791184088391476c5414e221204f1e3b43752bd8b56575cbc0fe5d00bbf22c5655ac4723ea2006593ab61bb1d00b24a0d6771", + "secret": "3d3cd39d13f5d0232735cb3dee8cdc2a4ead61aa5094e333b436d4f89f23e6f619df703bedb2d1a15c44f62fb70ecd8547b48d1bd27b3d7f5118c2c489c335461427ec05d2e2ff7a2ade48ffd72ed650d062ee07ed07cb089ed040737327f76f762646ec37d8fcd2cd877b38c1cd70da95116a499a5acaabbefcfdf613d13a4014ea0f2e22461832356d1d80670ce59bacc5b8da197b15b3cc5e0b5dcee7d0debd8b56575cbc0fe5d00bbf22c5655ac4723ea2006593ab61bb1d00b24a0d6771" + }, + { + "public": "0d435bc0a9095fc61256d4457bf0fc6d15f002402e1fabc9342b55c9163a7ab5340893948e3a244122112aed68b945e4326621eddf727bcf642ef0897e7b805d1b56d624ba87f15311aa2147fb70651531f8abe54b0cdce6cde3f4ce056378f241977dfe57f0ba6c017afe09c319785183e0a1b1dd9d3211e224b38e249d7414", + "secret": "b771fe86283beb2f0a3e317ce7ef21ba9229f78dbb3888dd8fb2f952417b611b892bb857bcada11a62c8a0599e3dff67ae63b8c8bdd545aa5da613a67bc1eec5340893948e3a244122112aed68b945e4326621eddf727bcf642ef0897e7b805d9ff35dd62c612d6e9a1de2a7f06d6516c02527332d038530fb2e9ca25af2d53bf28267637ae55086cc4a0d066c7b820b12691b782bc73358407f936b0358298841977dfe57f0ba6c017afe09c319785183e0a1b1dd9d3211e224b38e249d7414" + }, + { + "public": "e31e5eeb51e1f1602120d373937349a42e31b24580e726c27be2a38c672e6ea47a549cef532f3adce53a77e95d08c9d23ec7d3e284c26bab453bcd1392d83dd83193fc5dee5baa02d8ec7a798b484aca067c3efaedce970647ca9228a9b7d9e284a9b6318cad47a777bbef5eebcb67b07f8c80c684f52575dcacdfcb45e4d1e0", + "secret": "603fdde8a351a9da4a55e3921c2dfdc60c7814d02c452cc216ae6b41fa707770e0c9d89289b96e993b667e9cad34df4cd0eef01f4bb6be649f79fb533621f4b87a549cef532f3adce53a77e95d08c9d23ec7d3e284c26bab453bcd1392d83dd888d8020c50dc614b6ea784d1cca4f39e9fcd915c5eaaf307ea66613f08d060d80f70f219be19b1634bcc71b8f14e4f2c0370a853e249996b8e2dc8608bfee70984a9b6318cad47a777bbef5eebcb67b07f8c80c684f52575dcacdfcb45e4d1e0" + }, + { + "public": "744a8ecf0692e4376081e88c02847a4f7fa9f15b7d374b8bf76e8769b21bd2d22d22dbc8f081bfca484a99e3d21dbbfc1a1b89ea3ab107437eb64ba4e6c12d164facbf78cd812aa0c23355ca2e2a641accfca1801e48bfb16fb67fe49b9f7197e48950fd9df6c56f120c5df28a206b4e7d6daa35310e34b171906f18c00c56af", + "secret": "ff4f1df9d65fd24e0302c8f206b1fc35c1ae95c332bd558a82b3ae57b7e8a56aba7a1065278e0f9ba83a6ee8afee63599669595d45005c288b302885a6c525312d22dbc8f081bfca484a99e3d21dbbfc1a1b89ea3ab107437eb64ba4e6c12d1660ea1922b425f44ae2faefa45fca122157ea46043b9800527dbe9d04085f132c4439587b230d0f528edce8f10fb85cd98fa4068c807e5aca6af90fc70f474af5e48950fd9df6c56f120c5df28a206b4e7d6daa35310e34b171906f18c00c56af" + }, + { + "public": "805d485d02718d7639dfb1f9e82338cdd0e0154d90051daad684b37965a8ba8d932a8841eaae6581806f8aec8dae32808bc489ba4c5c3bbc8ea3caf846af2d46afe7e18686749f0fbefe84e034210424fbbf99c8739bb71a2e157184d55d6bf1d99829a194379f9a857524a035295a8c4e83f539599b58bd45097389d92895ea", + "secret": "de3d1961edf3167048a2036deec3bfb176cc133dde17b03eb7125313d7bbaf1d5c8d0201d05966485e5cc9a266ab33d7e55a7efd2dd4118cbee761e0b358e37f932a8841eaae6581806f8aec8dae32808bc489ba4c5c3bbc8ea3caf846af2d464b201781b9ebd39a51b9c8e1d40344afd8c56eaa740ecbb18360238b6317932bdb8172efef7fc37cbfa93c1256bf3785347c94d86d9051170b18b3a07cfc9f32d99829a194379f9a857524a035295a8c4e83f539599b58bd45097389d92895ea" + }, + { + "public": "3ec546723ef6fdfa7706596da635d14ffd55052a4b6d6b8334a1d71756f9aca9f2b996931d792b9aae24428ef91c0d4148219c307bc6540f4af0a9bee452100070ecc63ea909bee274d98ff2271cb99e643c0850e7daae0c81a3ac8408d0898a48307372a42d11cc83a5d2d0a4a448ec59875f13dea44b0cc1c29c6f279f9487", + "secret": "5cdd110cde48c54eed00c1625169acd49983f67ec36059e0151501cb454c529712508e883c3426078112a0a10d522e4af7e6232c0574e657cd75724eb6f4f87df2b996931d792b9aae24428ef91c0d4148219c307bc6540f4af0a9bee45210001f5f3dddd56c52143019a13ef8238ce56e9a6a20a4cca884199d277fa1ddf861eb2f064f02838ddfc2faff5223a93d045a22bb76f0544a33db2504b8f006460748307372a42d11cc83a5d2d0a4a448ec59875f13dea44b0cc1c29c6f279f9487" + }, + { + "public": "b7bedb81bc6dc525f2a358d4b883592a52c8a1cca38d7bacbd760410c1fb174eb736be9acf70ac3d7b4845f5a665f282333f6f5b87fc0f7f0799593ee3bf7f7f11e2b6de9e5c57e4b2e29f5c6294dfff076e1cd462547ca00f93a395cfadcfb176314d21f895eafbcd0863c0a812a14f7182939222bc27fe4fb5bff47587c38e", + "secret": "8ce53e2236a3734c696932e35b6aaf6ef07b706ce8ba99f9f7f0d8e235a7266a40d675cd133cadd16749f53f5ee99fda11c49514d1e69f112d43e0917e65799db736be9acf70ac3d7b4845f5a665f282333f6f5b87fc0f7f0799593ee3bf7f7f8aa7350abafafcda07cf93ac340b3efaa523e5f7b1ee8d630b61ed40201eea7db0654e71d4aac5efd1e32be68ef0d0d7c187f93680f614abde7aa9216f8001ad76314d21f895eafbcd0863c0a812a14f7182939222bc27fe4fb5bff47587c38e" + }, + { + "public": "2dbaeabb63e4a8b3e6d929519bf989cbf5039dccd030097e0c9559b511c904c544fa6a7856c5abaa1f19b55fd61f0930e593cae0ab618f089f51dbaaa4aa0b05e5091be439a6c28c0adef798c3bfe93e560b6ed0a63ac0b6666bb976c8b0108fad2d189e3da3d928c0000ccd5381d5d4e303195df165492772260e2783830017", + "secret": "f0e9664a8aeb799e67ce1c497dc38604cabe623315df74edae9ec9bacb891545079c50745fe97b6520518f44fc7dfaa521bf4fb4c166ddb7fce9752f7b94bcf544fa6a7856c5abaa1f19b55fd61f0930e593cae0ab618f089f51dbaaa4aa0b05ee4a4cd695dd08261a308f80ef50b560d1a16c2b7a0eddba2d59c5e4ac1390e5e389efcdd99c7339013fc52c7816ce7fc394888d78a5f65f963ccd168d4f502fad2d189e3da3d928c0000ccd5381d5d4e303195df165492772260e2783830017" + }, + { + "public": "e8ead06aeca54e7e207891c99ebe1e0d43dd1d68cf223c4171cbae7e108c9e73cff0fb0fe670fff21bac7cd3b32a891f99cbdf67451e4625895882bbe2a1bdd74245e1db8b8079fbc0028352fa71abac27e449c6c910d86bc21cf092f506c8cb8a467e03026ea129d186354e59479f1bf31ba0bfbd5c01c9363ba09b9d603cd2", + "secret": "a1653dd2a5be5f6115848fee826fef70e98d9949bbc90f6ec670424eac6120ccca91e599db9e6141b1184813ead88dc2c24ad0d1ea9460c185f35764c06ffe54cff0fb0fe670fff21bac7cd3b32a891f99cbdf67451e4625895882bbe2a1bdd74454b66f24ebd39488b889bc3a25346b8fcc7e6f6fd2a8d079caf65035f19d5b5fd0306b6173fc6a46a00415737b86360fb6a90ff562b5866f8b315759f770888a467e03026ea129d186354e59479f1bf31ba0bfbd5c01c9363ba09b9d603cd2" + }, + { + "public": "952344f1a11200a672e37e05dac9915aacf9364f3143f82d573e0ce76a7901f5e3e6dd67c3afa193473b7ae890fe27dc43ee50773224800743012d1f38f84708eca08014eba03e852db828c6d06c28bd95a2871c346ddfc706d88a9eead39e0c73760f5dfa950596d494f255cb7fd07973f5fe2aa87e4caf54256b72ac27f0dd", + "secret": "5018cc002bef24518b147ae56a84262d5428baabc16bbb82bb6e35b17b131366897f89eb2cd2f380874760c7cd35ff26405c60e5eb4bccc41070511704e04731e3e6dd67c3afa193473b7ae890fe27dc43ee50773224800743012d1f38f84708f2714afb5f5e36fa447dfc9e6db074e406fda225be341be85cf2b788112554ee7260e8ce2e412d31f5826fb28d2df31e5aa0d5eeddc5a7ad42b3e47f3c3fa64773760f5dfa950596d494f255cb7fd07973f5fe2aa87e4caf54256b72ac27f0dd" + }, + { + "public": "8b07ec70d9ed42fd01049e11ba410b508c5a42021ca009a1c46bd0bbe8c97ba93eb649502ba4fa44ec7338392a11ecf92ea3e7a8967e9244e72ec5cb257bda4cb252ab02d55c562a5e26c52b4252e1b16d6334b4984e5ae11d950fa02896a0d0fe3fb9ec5a8f88df07aa62d035aab10083fbd42295b441a6cfed0aa186a86b32", + "secret": "a3c42a9971b97f972589e745264b856d29725ccdb53552de9bfcb0dcab1834b7a84c4cf73c8a2102cce41f3ea56283d75cd30c3225b6eb046f032ae914592ec23eb649502ba4fa44ec7338392a11ecf92ea3e7a8967e9244e72ec5cb257bda4c23d1ccc62ff357aa61c1f758999a7bf1b974d85b433ee6d6a5021faac5780e11f80c9573682c3ed739b4aec44627bbfe718aac1e4d4a40eda3a26a2bd9cfcb91fe3fb9ec5a8f88df07aa62d035aab10083fbd42295b441a6cfed0aa186a86b32" + }, + { + "public": "117e4b9399274a455aa6aa339011c4c4fe899c7268029a905a99b5ca86fe3a0ac66230621ffc06825ac6ffbd5dedba402b70039eaa4a24ab0e6357142c277898789cac4a7ff751727859b85335bca960f7aea3f74b8760b3488c1a6290c8728b30f06f96ceb85314063ad76bcf599cdb5cd52db1ae67da4729d6035409a45c72", + "secret": "52d111dec37b4dfe625f0884a7fda0632624f26f786a1e593c68df6761df2ca50d0405b4598c1e9e4b45c74c2f68a789738a44353630f1c2059c65725f243569c66230621ffc06825ac6ffbd5dedba402b70039eaa4a24ab0e6357142c277898f3c6dc2f847046a7d5c1254fbe54e2bc9dbd20bd161abc0542f8ea33826605b5ef7e18f77534b22bd792989b7f1244ce854bacc850510c229e1d03f81809db0330f06f96ceb85314063ad76bcf599cdb5cd52db1ae67da4729d6035409a45c72" + }, + { + "public": "98518185a45b9cf53ef1f11b9e1cf84e8f3ca1388005935476de5167cbade7d4f586126a3c7c08379db063631cc4b0602421b51a108087193a28ee613bd3054eb095a027b82d715265b4fcd7bd244370c8d0052a12c6151b976f96703ded0b61ea26e53cf32ce660aed303d383ce339d13ce5a654fbaedea7672896c8e9b6d77", + "secret": "96c457661bfb3fc1af99978e8f3e36aad4148c9e2c588b25a958ebcd471728bccfa0bc998ac6349772eee3c18272ab5ae6b1973cccb2a63abbf40ed3c335abd8f586126a3c7c08379db063631cc4b0602421b51a108087193a28ee613bd3054ea7baee3160dfe29da0547951d9b81647594a642b88f99a39ec8f9e1b6e57fbf2a9fbe376b4847f26eb644537fcf03a8a5911b29c8c11ebf79bd1425ea50e3b76ea26e53cf32ce660aed303d383ce339d13ce5a654fbaedea7672896c8e9b6d77" + }, + { + "public": "369e99b111112648543b3fd7ad8a59278d0da849789e854ef315393042a35218b848883d999a0bef7fa75a90fe779416d83f996edffa0762205303a29e93b29b27ceba94f89ab9dd0a1961c2f2ddda05e5e36cf97cffa83cb479df3ad923407b9c84024144fd25ebe1edf58e1a04ab031646d89cd0eebf09d5abfa853a06dcf6", + "secret": "6f43b50aec2b4b89e4cf4e6c68b4ecebdaeb64f6a99ca8081992f0df31b664dc25b30d6876cb45c9f4fb51abe0d0f64def84ff5f0b12140b42e86debde5e0f77b848883d999a0bef7fa75a90fe779416d83f996edffa0762205303a29e93b29bcdbdc2e81844bf90cadf62056e9a08ee9903e6156f2bcaf1262150e0ff2a6661d238bc10afd68fe8d382ec30f3e28c079cbfc104f684ca216c0d9a91eb93f5a49c84024144fd25ebe1edf58e1a04ab031646d89cd0eebf09d5abfa853a06dcf6" + }, + { + "public": "194adf2ea19eecbe3a594def0df90950231592db1dcd92f20576393ced73cc1359a211a643e74daeb46d3ef133038f1a5715c50dbbe1a55dae5393cee6731c545d22ee009e8d214a7e62c65336f0f3be30dda7aa1a3a1e469b77786becab031610cd5fb5b4cecdf914c00d7c119ed5f44f8cdf27d1fa691a9c9306f407811e0b", + "secret": "7353b29e7a50007e9178adc242a84837e4fdfc6fdcbda640999302396ffca028b00dcf87306426d8b3b69447e2ec00b4ecaa7b91ceaa3ad31056b89bb516d6d359a211a643e74daeb46d3ef133038f1a5715c50dbbe1a55dae5393cee6731c54dc780e0d8a15cd59354c9d1ec68e6ebd0848eed7af5d74f7173fbb0cd0db05e6e4a746736a7c60e0b6fa55ad4fe70cbbecb1113f93244b36a58bf7dbc83b964910cd5fb5b4cecdf914c00d7c119ed5f44f8cdf27d1fa691a9c9306f407811e0b" + }, + { + "public": "d3d415bfab13e061578d52cef6884c7ea61a793534a8ff232a93c1e698fff6a464049af40062d579b3ba06dc69bcae486aecaa730cd49612f4226a438a704f9d574623df7e3aa7dbc98a6bb9a5fa5469d3eeaa2a903c0b3699068af16e758676989a7883c38b5a362feab544b3549f48673f01f67b527971a4540ec70d3c58df", + "secret": "9e5575cfb5a68d81ebd2930e631858373114357c2a7e06ffb116feb395be41452c1d8917177e4a5ae42f0b0009e44bba48521c38dec248ea134064f1329b4d7b64049af40062d579b3ba06dc69bcae486aecaa730cd49612f4226a438a704f9d73e66f3d6b4e0ed76aadd35ad4eaaf34c418f129fed85965d002c2920cc8511c095265ba88c1c67f9f2aeb098f8301c2b205fee1ee2f70eb063a103f3a88f119989a7883c38b5a362feab544b3549f48673f01f67b527971a4540ec70d3c58df" + }, + { + "public": "57e94f6587e59dd6875924d24698e70a6f8a121ab2fcfe5410fa0cfda6e50d6bda6af5bb117b0cd65a06e85cee87d4e44334dce129c5c8244a1f31d99bed35226a967fcbeab8eeb6f2af7c7377064c81262dd5468757b36ccedbd62160c9d6a57a55b96b1b9b2cfecb8e10210b9321df1095c886c18f493a0885f36bccf678fd", + "secret": "9d770522b0f53fe5ad34f990e7b75c0b618ee5909903d36e7c7e1f23775ac359068ee7c223633903ceb5834b804aeabdac35c669b5b0701d18fbf67b5a260681da6af5bb117b0cd65a06e85cee87d4e44334dce129c5c8244a1f31d99bed352214679a8e28949289eb623c0d3800419017aad1d89a32537a5e30cee93de550056376172bd4e7bd26e1b5501c7cd5b28aad2857d5463f50a342fc87325b3311c47a55b96b1b9b2cfecb8e10210b9321df1095c886c18f493a0885f36bccf678fd" + }, + { + "public": "9a02cce2a2304063be6c02e431f0ecb74aa33b0d669fdac317eb602278d2ea6b373103a197c8637a30467c89850ffa449cc5077f7bb73b900b632741206a7dbf7d088db70c08c3fa9425361e998cbb37da196ffe3ba931ca9771a942c057222c106b8f0b75c6ad4971a545511e864027f2e855e7a84f7778afdfe23833d4c495", + "secret": "2243249c980c338411548bef7c88d444c214026579fc14c4c4435d6a4e1f00156348974ff1cae0229fc8f3a762c7fd3a3e5e8996b80165af6b9e42beeb98d7d5373103a197c8637a30467c89850ffa449cc5077f7bb73b900b632741206a7dbf41c1ed0a1879e8e490c3fd74fd8e873dd98af8c852844357f782ee5665d445a32d141d0466bf3360500e600ff143b0fb435bc95c58c312713fbf97f920fcadbd106b8f0b75c6ad4971a545511e864027f2e855e7a84f7778afdfe23833d4c495" + }, + { + "public": "473ebffc232e5e32620cb645a679e7ba19d5ebc951b5fa41e31fe8dc01122c1b015cb6d016081c4cf9f13cfc7e77cdd7d43a620db97a66b97404df77d4657942427f88ebb56bf8e764edee9f432a3a1196716721dcde4c43125c1360c3d34d750fccde94ffe8c13fc836510cd11c02e6161e274f2a1943a6cb285a6e195d56e1", + "secret": "7eb81ba3e6532d062da017401dd2797b2d50223013e1c0012c131e46375bafc1af76b32547578b750e3d108f98373aac9d9b24d763155ba411c7745cfecc0253015cb6d016081c4cf9f13cfc7e77cdd7d43a620db97a66b97404df77d465794201fa77a2d53dbe066de0d4588c70b084c495d46d7b03d25c4b369ded8b6a481f41eb9e70cc0bb2c234e41445a30648b141421bcea638a32fefd8aa12b25fc6d00fccde94ffe8c13fc836510cd11c02e6161e274f2a1943a6cb285a6e195d56e1" + }, + { + "public": "0cfad893173f2558ab86c09c1692d2df59733e5e9f69d25c96fdd4235a2bba8810b283081fdbdcb0ed4788741d46aeda7ba93a7ed03416b0a87c19807d096814860d9b4c16d5f6185355c49f94c4933dbcbbe3b1d8d2da521490f47f4da2949d8dd0d041d672ebbc763c3c96e4ce78cd78020d11c8900b4fd6aa26adbb5a257a", + "secret": "c326ff1d6c979bd4db85fc6b1cdd524bb8680b48a49ab33d1515d98abacad2f38c2d0d4be349670d6900e2b11f0580d0e30279f5daa9c519d51ec81e66f7ecb810b283081fdbdcb0ed4788741d46aeda7ba93a7ed03416b0a87c19807d096814c0cc37c1ed56f75f06c88a225644ee95bbca9a4ae4e959922e890db46d6d25cb43747be0133f64d9b157e5a2d5ec3b997fa877fed7a6c2c4d9ae2132b7a00fb18dd0d041d672ebbc763c3c96e4ce78cd78020d11c8900b4fd6aa26adbb5a257a" + }, + { + "public": "6ce53df1bcb84e0f7689d5137a107765e0b37107b6c99b8643f31868ddef319f4d081f919aaa75bf57e78ca94738e8b6247021905e20eccc9e32b402d4aa03256b9786ec12e85b29d8dab54c214bba8f0e58f77fff057f9a2213532cdb57bf92598fbeeff890492811fa1e41ba43bc136d410c36fc07f5b7a141a58b78b4875c", + "secret": "ca1a2d55757cedf47f6a9f5d3781b007995a0fe7ab7f63da9c670ffb31a6e7edbe460a89c7e290acdcf1a8abc34ec733f11a867fee971aa1cbb2ff458017426e4d081f919aaa75bf57e78ca94738e8b6247021905e20eccc9e32b402d4aa0325975191a4c95a0e3e7b4deca40a908ad0d9dc4be82b3a3d4bfc96b830af2223aaafa45a96e8194653b52abd0528981aedd21c5ed88c5ed678007b2ecb9ecdc9a0598fbeeff890492811fa1e41ba43bc136d410c36fc07f5b7a141a58b78b4875c" + }, + { + "public": "06ae73bee785f3b0da97a3a7eeccb7f761b9469748545fc6f9709dca86dcd6476fe553806c7ce16b0771de8eab86a16451d63bbe1c4d18d7176b93822d3ad9b3ecba9782d31f781bfe593823180b339b8f114186375d3ae963fefa7ed9d72a05460295f2a9f498f75f4dbc50ca15e8c9c332a78043ba71686d9318e245306019", + "secret": "2a54db99a205abdc30bba9634a2a0bc47fdadb1902a824953b86a66b25d8cf915fc63255af7c37b966927fd8422b6fce0a1414672af9d20882fdaafa4a6efdc76fe553806c7ce16b0771de8eab86a16451d63bbe1c4d18d7176b93822d3ad9b3a2fc92a891cb3fe70bd0ddfa629f530d0359c173c1c577e705cae5026ac37e47ae76fff8080a275073676586bee16fd57b7b9a56486579c925c30f8cb367c51d460295f2a9f498f75f4dbc50ca15e8c9c332a78043ba71686d9318e245306019" + }, + { + "public": "f24a6fe9206115f41c5f4e31337f0de2fd81e1495d118de9a9258b26dc3a083b2d3885cbc9c6f5a4141f3c5948d4f62a028a4794fdcf1e1b64890798011ea395d1588d137553395d0963a2c634648802476edd7458aeec488532bba0107277409f92d48edc32a3676b4ee8a1ea24b0e6add9c8aa995aaffd1e3c151b97d54a53", + "secret": "e772b91ce82dbfa9d2477663b66a4ebd99d79ba7917c6345436f0e8941cac9c97f3dbbcbf19cfd847da776b0e005d36e4ac5cee92e210ec27f54f164baaac79d2d3885cbc9c6f5a4141f3c5948d4f62a028a4794fdcf1e1b64890798011ea395afa126b1cc325291bdfbb063e928c09cf9edb70079da49705084673902b0cadf3a3e6168161aee7895bf1a8a24cf2d5ba0293f1e882e81a8be3b85e6d47fbefb9f92d48edc32a3676b4ee8a1ea24b0e6add9c8aa995aaffd1e3c151b97d54a53" + }, + { + "public": "03061858764a431b68e8ad948f2c42e10c502a2aa3d8f657436259f1a5207e9fdb01546343cadb1adcdbab52ebfd143924fdb37287b191329549a1812e86233e4d98b3d4f9b1ea6059a901c8a8ba8bba397879c12ae0cdc8e8329f678cc2381758fea53a8ac53949fe271f1e423ef46343c20285d8b4bea79c5dbe2461bc3d29", + "secret": "f72c908748d5c2a489a1f5785f347fb4f5a706fd3242614b0c10fd24078e9f9d771be4bfd166086386504c4e474857e7f76a04f384f1da3a86b27a3b938d11e4db01546343cadb1adcdbab52ebfd143924fdb37287b191329549a1812e86233ecf682a88fc2bea0097abaf435d301d49518cfd6d9f23f4cdefc00c4ae2d5a90a6704007d49bdb8d4c3461bb234f6365a968b31b13a2956c83d277224a1fa2edb58fea53a8ac53949fe271f1e423ef46343c20285d8b4bea79c5dbe2461bc3d29" + }, + { + "public": "be7dd768614a452e3608daa0caeb17c66b02020b81597eea5bd360dcc5c43f529371b9a0ce6f9f0b89cf4de9857ef943d0727aa04574646c2089dab086ae29f3ee9c85805e836fd3f27598d7be5ad4f9e62cda141abeb79c3f8dcd525f6091948d13b47366b6f66743ceae1fa3d0ef6a86dcd41f3a12cfdaec2d9e6c365059a8", + "secret": "4838c7865bd0b0ce1e08aec9eee76546a801b3bcbbfc97e5f6bc2cd4a2998640c2eb3bbc48df0357e25821e5e957e1f6a02fe96060814b673c4aa1ab725819a79371b9a0ce6f9f0b89cf4de9857ef943d0727aa04574646c2089dab086ae29f3190cb51bc2ba50a2be1f113fa7167526cc2b3e34f9ead04823c49f4a65759f74ffbd456720893b39537151705b7562d65ae66303ab1cf37700bc79c156b57bdb8d13b47366b6f66743ceae1fa3d0ef6a86dcd41f3a12cfdaec2d9e6c365059a8" + }, + { + "public": "2d40c1e9cff84863ee69cdd6398853b2639bbcb0c589f1e9fe585f85ed81a5a2219da0836be79d24d4e79cc4179a68cecc4d62365ad4cbb27e159d62d0a18dfa74d04c25281943a2103bbd626f83e475514f93cafc053107dddd9d6967c4dd5200720a28289658b37028b4430de7171770b4dd90669f2a18271f170aa3df2d26", + "secret": "260d468f1e911ffe67c6f061063ef26ded23397c2e9f12306bef7882cbf424b5a665e289cf1b6b70bddd7d94ca158ac3eb07e6617735f894926b351d3e897853219da0836be79d24d4e79cc4179a68cecc4d62365ad4cbb27e159d62d0a18dfaddff74807c214889e6bea209bd16115effddb850cbdb8c5ebc3b6b8f39221a077e87efbf88089a5b321b12d7dc8b3174446e38f1c4e2d94d615266342e29e7a400720a28289658b37028b4430de7171770b4dd90669f2a18271f170aa3df2d26" + }, + { + "public": "968dd4338b8e5348e209aee079f12a22b76beeafd48e04d3d7d0468b3072e6fd5602614cbfe71dff088e1725b64ff267aa1b825d8041459285be5fe25a68b7cf0d719e63b43e26c05849bae087a16e82ec167ca03db6b5ea5fb874d3fcb57225bb4b4c7452b877774f82046a32d62f163f9d3674e705829cfa692a6cb615e9a6", + "secret": "799183d3835dbc7863c91f052da2f1b76e3d8b80d09b5719074d8d1246e64de231cbd8fb95000a3ec5d117cbbc216fca3d2646ad7959f369a0436253bd9189755602614cbfe71dff088e1725b64ff267aa1b825d8041459285be5fe25a68b7cf825bc14f86445a64add36d1cd53c053bce0a4e6663aceab3246bcf91efb48e97f9ec50285b819d98b308f475d8e7872c87c7aa1e7e248b8175d956d670a9cd6dbb4b4c7452b877774f82046a32d62f163f9d3674e705829cfa692a6cb615e9a6" + }, + { + "public": "ae01f80b18d6ef4b3fe66f8b880a87f9ef65636903acc5d734330f10a82b44c7747bf148a802f13830c4becdd91f2cc3511c4f40cea97bc05401ba18ae3a09479ab472ba72b8046fd132ee8b2e9389bfb2f5c3dbdba07be0d939b83e05ca3d4606d62f8fc76896ac77c34f12e78b2bf476499ef44e72fabdc187919eef8384c0", + "secret": "b2d0885ceeea7f6ec936479c1419c878163b43e45d8b5dbb22f6ece60499f4b23612f2001236265b979c32b49c20c7987064ed4f16d6ab022c748885ab2d9b45747bf148a802f13830c4becdd91f2cc3511c4f40cea97bc05401ba18ae3a0947b39c52f3a1fa3b207d7cbff5f226abff14458d8dc11d7d241526d023b837ab6a2cee96be94843879e09fd500e6be3dbd23c60ef59dfb775d6505a389d4a7f31b06d62f8fc76896ac77c34f12e78b2bf476499ef44e72fabdc187919eef8384c0" + }, + { + "public": "c53db6a2b148e8f2e384e6931ff0fc6483f639e6cf177a519332e16fd022009882e24263097bcccba584f62b62f7fc3822ea30a53b67f72ffff21c5d6a8535d8a7dc5a3542f2a9311d637a8800953f54304f463a69b07d049095fba83d1b8bd33d43bceb0655d52e995e9ffaada1f1ad88d275b5e0600c6a8500c2d2827dd9bd", + "secret": "d20be92103188c4e2674e3d0f29d72c077a7b8be937e8b175b704519442f3021faf3a7e6acd163c4afe7f1aea2db78cb7eca27b3395ae5d6ef75b46157e78c2e82e24263097bcccba584f62b62f7fc3822ea30a53b67f72ffff21c5d6a8535d84e4804c1dc3f9b5148d38914100c75f6bc7f93830b82ebda819ea887ad76ffd0662cb2799808e465923c86b59f2f643896eab0a5a436990e26ad2e0a1b78df2e3d43bceb0655d52e995e9ffaada1f1ad88d275b5e0600c6a8500c2d2827dd9bd" + }, + { + "public": "700f6f49df738c0a4c3d840bad941dfe32020ff6e1a60c4024cc9408b2da7845b55e46f150a1cb60e8914e9f3ace6c8b44f562151c94580a0e2fa7b10aa930e9b6d04de36468137394403baafc0658fab1a2c8ef8c80603c715d1f9bfd7d5677abefab248b8f736ceb10ee8e23b4c2d32ce6d71de71de26d344ca9ac943d09f0", + "secret": "fe9b4f6b0db80612a5b9467a016c939904a5b5ac9ff07e268250da5e0327b8541ee04ba1c325c33bc879ed39828090578c95136ae517280371124a450e7a9bc6b55e46f150a1cb60e8914e9f3ace6c8b44f562151c94580a0e2fa7b10aa930e9bd28f1b5100b013bb51c2f80d91de3d7562fe1910ca56cfb084adfa5ad95f08c9df6ae179c5bca356038aef90622b96d34967f52d5353b6d1af3a8819abb9953abefab248b8f736ceb10ee8e23b4c2d32ce6d71de71de26d344ca9ac943d09f0" + }, + { + "public": "9167f3e6a21c4665ec88d22732abd0a234235db60ef2a291a389dc00943c3ba48ba88cf06c9ec9cc50ff2d26b0a2ebaeb24aafe7b9c525f6c99363b295d8323029e0856d6118e2e5db9d6428eff40447cd75da9de36dd8018566d9f632919fb1b9b86d02573e446e8205af62a4282fd97f532502089b09234472cb907bcc240f", + "secret": "f46ee5d3b1d4d0d6ebfe7f8c1ab0973dc312f23a4d02c75dc85fdcee4d30af4ea7ef5ca6f0a102b371530ad90801828bb90ecd67fb1d478c1c511423f177ae3e8ba88cf06c9ec9cc50ff2d26b0a2ebaeb24aafe7b9c525f6c99363b295d83230f7a20818a648926088fbdbb0d2477379abaeed4bc0c26addc91dd6f2991889c202e48929ec9294b0c4b421800e3b421cb0ee49a6a8fe6f0ad2617cc81e83644cb9b86d02573e446e8205af62a4282fd97f532502089b09234472cb907bcc240f" + }, + { + "public": "9872578e5d39ad912045c1b2b61c996b14d550a8c24fed13c7ef1861e54b40a9092e94f3b92bec971bce5c57e933ffb8e95cba2c2cf95cc5e24e5fef6b007bb5b56daf276879a25685035bb041a066c635d08dcb3fb65d6a6bd52c249f40019578c676733edbb94950e03fb9f0cd61e2d1091e73f8d52b7a1bc2080166090127", + "secret": "ef941b19bc57525fece9d371864f7fae6b06384eb0c35ed080b3ff6d6a8da76e49792716a188a2ebdc2d1975ad2b9de3a7212fcc3ed531db84227f81c1b41d53092e94f3b92bec971bce5c57e933ffb8e95cba2c2cf95cc5e24e5fef6b007bb5a22367abca4939d686434f2b602492e0fee1c8b973c0aba9fa8badaab2c97cdd7f71ee351b5a27e2b9d2a389db0b0740b49c39ffd45d9799d34221449944b54978c676733edbb94950e03fb9f0cd61e2d1091e73f8d52b7a1bc2080166090127" + }, + { + "public": "5f5362dc070d1c176388cf8aa7384cb3afb050483ba3cda908e9ac3bfd20b6b570da20d5ad498cbfe5f40de6ebd3c44fe988b364d6ac1901d5ba63d7a9ce04c0982590300040b42c225699332e08396d3e065d7a3ebdad69a847651db586913264d9ac930a762de12045211d1631e4c04d32dacb1dc670281dec1daf6bc5955c", + "secret": "6c287acdf981de221b719b5e3a595348b41e87c9ce3c25ce8b5e7ef5d06e9d2fcea0cb248493379349df754de21e958c4b6b3595ba90f8ec4faf7331a5a8081770da20d5ad498cbfe5f40de6ebd3c44fe988b364d6ac1901d5ba63d7a9ce04c0fa532802fa7d1c1c784870aae4975dcf596ebb0922447eec66ca207b4efaff50e684721f89ac22e0e4d345f4b465559499d2148ea6c85c8e8faf781e6cfea8ca64d9ac930a762de12045211d1631e4c04d32dacb1dc670281dec1daf6bc5955c" + }, + { + "public": "4c819765c16b0c0cab9ce2808bd50d51aab6353738d4e8824b7748c7f3d0102769c29ea2deae5c7b425c9c2fe6928994af6e6f9bb98fada6aecd6d3fb70c9f82d23d2b49cf46f27f86a1333b865b310145a71ec8411b09c89a60a550f6e2c2ba6f224ec0bdfe3299e979e86dbe552ea2af8516aab717827235cd146e5a3b13bb", + "secret": "ff9f8615a2701e2c15111a0a757b6eaec81eb3179df72a6785b21b2e81cb57911e50025de4679581126ab5044a8ddfa00efbecc4b9eb142acca0bcaa1288d5ba69c29ea2deae5c7b425c9c2fe6928994af6e6f9bb98fada6aecd6d3fb70c9f82ad2f187430b5d2092e8f933887fb5e8811c13699728ca67d7968878cb00ee768479566ada6a7ac513886b8693b6c77dcd4eb39449d128c6051fee6384f7fd7c36f224ec0bdfe3299e979e86dbe552ea2af8516aab717827235cd146e5a3b13bb" + }, + { + "public": "9919af287561335d2a19ae7c5335c4383d658d1714f5ff62dd2fc30d0150f0b48da80603015cf29b74b9d7f6d248c3b1944160ba1637d91ea4f018b3d9b504bc947a366e053a1727f544538a56d022dbcb1d4174e4bd8b4e1f3375b6ef8c4ce6a5c172d839a6b82c30ec938abc652bb09ecf4c6ed8882b0853d1262efdd0e9a6", + "secret": "945585883c5c27234dee9b7ada9f04b30c4e82b77adb0e9fa3d077edc7fc10d95ab0116b4324e01167228aefa837c82f4dc2356591c528d86c19f2b32aa3d5728da80603015cf29b74b9d7f6d248c3b1944160ba1637d91ea4f018b3d9b504bcd192eba58eacd52c7bf6bb70a39809555417bce385e43ff48ce6103f6d4b4f3611551c8d38b964799e28a68efbb85c31ad24a8087d8857b6020a4a5763e6d4f6a5c172d839a6b82c30ec938abc652bb09ecf4c6ed8882b0853d1262efdd0e9a6" + }, + { + "public": "97567eebc0ed433b3cc0040636f2f16635ec13f03605d172d0e7bc51ae259a6cc63c4ee29e9c73ea9963d35d80b1eaeb4f83f877158c4b7c2ca3cc9ce534a8fdbb20a79a0a5a433c0726f83be541dcb0a76990a2bffaed36ab3fc1823b329519446daa3f4ce8b572739b807ddaca078413d60a855ed58666036856debe3dd036", + "secret": "921e158313651c9c880c1e4d84268d7c872b2b615f3705722aa74b17080a4c3b1859e188fcd3f7dbb11778883cbb1c801fc87385ded81668e0669715bd2c3b8ec63c4ee29e9c73ea9963d35d80b1eaeb4f83f877158c4b7c2ca3cc9ce534a8fd2144a87038c7041135b6dbff9b20f372d471036a0155042b720bd1c84bed5fd2c3ff67ec2bd393f5f6b868c35dc97713899934610ff4af8ac3296cfa3cfba44b446daa3f4ce8b572739b807ddaca078413d60a855ed58666036856debe3dd036" + }, + { + "public": "5057bbb90eb921f4db0cf9753d12c2dec3588b35b337293b643d6a9f086d508f1b242967d2f540fe263f882aed1ada06b64c7f85cb1692292ba90801936935656b0ffd621da3a09ba0699a20aa0014bad336c736fd092e5cb5b6abbc96aa17313445a75e59ab09297263236ff040d94c1e2c13645b77bc7fc40e091e8bb5ca94", + "secret": "a7dcef6392e0ea6ef2aafa2ea8bc0bbd096ed187dc0decd6c694481bff8106966512177073dda1dacf79532740a0e626f27c890967a2507f33784757b0aca78a1b242967d2f540fe263f882aed1ada06b64c7f85cb1692292ba9080193693565e8d5509aec3a2097978c70343973e0a455a9802b081425c1c224e6df78c00885eb5dcfb0cb8115562e6db912ae79c1ce3e4f2434441b4e13db3e0d6a2dad57093445a75e59ab09297263236ff040d94c1e2c13645b77bc7fc40e091e8bb5ca94" + }, + { + "public": "71d91e90ed2dc81614d940aa222e0dcebf77776cac645cf16518ef56fbd85e15d9076aae5d438583fe1ad78636ef68363f685ccb3ee4562c542d71de8cccb6872e7ef4a9296cff56329f237a3fc3c6c1f7393dfdad3b685b7b1937e3c069dfe7d2699a6a55ee9872b52c51522d6104418455ab402cdfd05e8dffd8e0adbfd8a7", + "secret": "65860a37d510e06bd1b5cd69f3b667946e4f847b4771b897a88259400f3ab152e5bfffa9fee0aab93e639c239e3813e9af5dad1743bc582fcc960c36147741e9d9076aae5d438583fe1ad78636ef68363f685ccb3ee4562c542d71de8cccb6876bac4cd4aecfadc54122beaebd1e63ba5f9418f9c056986f1b98497176646cd4dfe43eafe4812f7a9e87745588d80164a1866f6284110baf1399e6b1c3f0398dd2699a6a55ee9872b52c51522d6104418455ab402cdfd05e8dffd8e0adbfd8a7" + }, + { + "public": "fc5e39c9760407aaf094fa496ac0fc2a19b886fdb7af7bdd4ef911a93734afad87ada8443d350c9bafe46b1b570b8c8ac980018b2e0af2978382cf6144b0622b2c469baba42d7485f4b4926e889cfe426dad3980ed70b67f3a324fc6fa4bf401f719e84084fb0d47d29089fe8b6549ba5bfe65e2a4a8b0f3cef201ec382877e4", + "secret": "871129b42e5ec16fa4deec48f678bf060269c65009fed5ef1ec2aca267da5595826d1cc39cf9d8014199511b6d2c694f1c776a23e882b5d4328e5a76bd8eb35b87ada8443d350c9bafe46b1b570b8c8ac980018b2e0af2978382cf6144b0622ba98c99266e07083c6c083f005ac156db7bf1c88958f54f48246222ee7d9182ac93e31def50bbcbadb33e56c27ba037ee448214f445ad5898b2b4d9f7c8b720a3f719e84084fb0d47d29089fe8b6549ba5bfe65e2a4a8b0f3cef201ec382877e4" + }, + { + "public": "73d7a15c7a45fdb05df32cdc65dc5bcf68c385ad292f082f8ff2e2ea9cef454c76a6a94c5476f20f1d4e808cf5c5ca30c12cde5b85746ea78b6e8625f2707e02bd917c4d334b04050ae62c27624cacdd74e5a377308c8ffdc7061a2f7a97f42d04f6e40b4977f05356fe1a63c9a0131b3d7a653481c83febf4fd3efb18c4ad74", + "secret": "f7095ab862de773f4914db0f794e1c9261bdfae247943b4d623a7187611e1911ce4eee4f5a5d6ed0e3bba6368bf9d190b6998f74e8bf26e844689badb302566976a6a94c5476f20f1d4e808cf5c5ca30c12cde5b85746ea78b6e8625f2707e020a1e54666b50bd41b849261f853983c403c56949d081855dc768424c2d3030ab3373f7d78f935b232ef2454dd105a5b6c7f76fda36adffa058eb2afb9f9bb9fa04f6e40b4977f05356fe1a63c9a0131b3d7a653481c83febf4fd3efb18c4ad74" + }, + { + "public": "aba7aa170f37b423905493de59b2ecc3083d1ccaec663a800fdf187bcb38cfe6183da8484a1170b0e822f713779b25d7f193634ef494d12b31f934cacc9e79ee76c3d49944d6bae01aeec98bb291e90185b2048ae9944839c27019030cda5de4ace0ca6ba44ff8c26c9b8106a0aedace7194548c8e4760d5b8f20fc554acd8b2", + "secret": "0329f5e215210de13bea8ad59ff4db6222a67efd64e57ea0912d12c746a73d0ff9a7d4616013e42ab13e717e36516adfc6de6a701423dcc3387a8201ca165fe6183da8484a1170b0e822f713779b25d7f193634ef494d12b31f934cacc9e79ee0ceb8ea36b05091d225880ef92849e8e853286484345992a2b67041f9af30acb38b17f8347f8f29f07a2d7b959f34d5036787b9732516ef7bd770e31cf130b4dace0ca6ba44ff8c26c9b8106a0aedace7194548c8e4760d5b8f20fc554acd8b2" + }, + { + "public": "566a626ff22416d2d02894573d06570192b9cf263ad82421a33aaf29b141c1c212e315b37f71dd39d9ba3529261ae8642d04df6fba4ac08478f3d397621cec9ddfe14e0b1a503ec7fd850a08d1c61442e00baa8c0a68915b231fdf4d9c5d09c21d049a1c73c995825b9a12445463adfcd5e50d9993ec5294169ff786b2689793", + "secret": "22dbe4a67d24d9e7fea026aff0033a7d2356bd1c3e150f0a5cbfd31f5db0a25c8e4aaabc50605ae46ccb69fe36bebd2045eea9ac2ce50d33e94e6f5aabad874b12e315b37f71dd39d9ba3529261ae8642d04df6fba4ac08478f3d397621cec9d981acff032aad2b8191b5e56f081e35e93d81625cc9b8f9d68ce8e1e407004af6c97d35ac91b0a88e5a6df86fc2d8e82539feb016befa8994b3b783cc73d35e71d049a1c73c995825b9a12445463adfcd5e50d9993ec5294169ff786b2689793" + }, + { + "public": "b141a70668cb8533d40fd8fae3db8e786e0361c7f82e014be837036644c58dfc173c2ceecd803add099b6d3770812837c9b313e8198b675da5dcabfbcdc7876296c46c389bc489265b8b883936401ab4df2b23e0cd2209ec8c0383efb9dc4bac8c612f9def0c902db455d26affe8c32b523b16083b3d9e638541f3de050c7595", + "secret": "0c68ea877512a44b7c2e9c123effc31e7bf194d9cf440fc09550e6f69c6c6b395d562339ef8aa18c1fe82a7ff546b25c72ccad3f20de96b5ddf707d3d09e2cba173c2ceecd803add099b6d3770812837c9b313e8198b675da5dcabfbcdc78762ce66143f975a1dc24f0bb4a7b844450b9a30e61fb93ec7add929a6587497ab82b731699fdac3b2c58ed6846129bc8f45fc25ca0f704948a8d3e0c2f2a4ec24ff8c612f9def0c902db455d26affe8c32b523b16083b3d9e638541f3de050c7595" + }, + { + "public": "e8846c74958315151ede7ca6584ce67dd657ae9f2105eb1ee416d8b16a641fdd7dc7118c3beb0d722c555eb87cac2536864bdfafdaba7b731a14e80d1121fe4a80da7f46af6f7b06344ff0d52aed2e20aa8e1440fe8bf4985c25e6acaf4ae8584071e263064d2b6d0aad4e52745aed1280387dca779cef4c724bcc0b91eec525", + "secret": "c49418990f2ecd5283dae8f77d6b9a2f5f2e8394765cbb927dc175cd5cd9028429e12b5d78ab8282d95842eb0ca75af302c90eb1b6c4c964f4a4942df38a251c7dc7118c3beb0d722c555eb87cac2536864bdfafdaba7b731a14e80d1121fe4ac03d0b94dfbb2bcaf82bd403520c722036c68d3edcc3bad5e51336444dd82a757b9503e9c7602cb71f02b2c7ef4c8a8e0689c153ca25c496aa3ea75ec31c893b4071e263064d2b6d0aad4e52745aed1280387dca779cef4c724bcc0b91eec525" + }, + { + "public": "1e9f81494e5d65c707b44712507f3dea3e6a13bf4f229dad669be55910c6ea1e1bb9ab9e6fa0ea233793aad529a5b3ca844a87654693b4058f4aeb97422d946bb62942255ab95c5d3187f7d8f441eb118669cbb2e416a8d11680a56623bf9fdb2297e016f78942697a35c1613478597f41d9076cf8a0034e7a4370700e7240cc", + "secret": "80ac8cff66b8584dfa5f472884f35d16614e632247b302e20403b6b8570d4245ea532360b055bee1efbfc82aa8ed31fc649a768b1d18a7321c942c77b1efd2b61bb9ab9e6fa0ea233793aad529a5b3ca844a87654693b4058f4aeb97422d946bc0a2d05f833d53c442e22ee91994f9cfdbfdab98ccd826f666c5721528bbc2e87b5ee5bd8a19c104ab205edf4b70c022ef47afcb7d894bfa0a1d473185b8e8c42297e016f78942697a35c1613478597f41d9076cf8a0034e7a4370700e7240cc" + }, + { + "public": "a12ecb4b2dc1a7e3a427a6dc54ff09f0d41152cb13c57b61ed2c039413a6e7c713b22f372ebf8e49c321c82ab3043af5400064dcdaed3f387440da5796a94b131020766ef528bbc4afa7489d24f1bf8efc67aed70a9644701efa4bce549e18863ce3ea501b16f30435a8ecee6940b75f42c8fa5cfcf26e5d6f75e9a1e5a5795e", + "secret": "331ef7211ba9c8e69e81b200c679eefe737cd087e18860d637b3c0369da4daf2a2a6355fa58fd94a6362f39944343ed81761ac79d7a373e72188aba9ac9928ac13b22f372ebf8e49c321c82ab3043af5400064dcdaed3f387440da5796a94b13a3471e18a580a1f86ee382c53b764a6789267cdac2ae7c098b34acc21f9dbe5fb403291c310c886579e136ff1c0f6aadb7ebe86f1772e017e0c2f0c9aaa5c1ae3ce3ea501b16f30435a8ecee6940b75f42c8fa5cfcf26e5d6f75e9a1e5a5795e" + }, + { + "public": "7c2a777f622652b845452b35cbb567e22e9f7ef583c31a85c29895b2b0eb0271b867c3151c8a2cda9b7d1907d2f5b98f4e3d7626d5ed6310c1ac9d28fafb7bf2a9b76facaf4dd2027f5f22d8f0de6a33630686b121b1afe1e21be0b7269e7d4b55cafaf0f4c57b62f3af04a665a0f7b93fc5b6d7e70698b5f4668b774098ff2b", + "secret": "c0a98bf4ebbc5eb79dd83f814ca016ccc66a613e8bbfe44b25922e771f611a19a39f61bdfcbaad784bd54058eeea02817164eab9a6564382150f5b85013fae21b867c3151c8a2cda9b7d1907d2f5b98f4e3d7626d5ed6310c1ac9d28fafb7bf269edf6635147fe49a74c5267e0f3d751758f4f9be9127abb667e3251dcf65074fcf9ab3ef6961e63d984cf6bb15853de07425ffcf2bdf7adee2d0af133d2cb5e55cafaf0f4c57b62f3af04a665a0f7b93fc5b6d7e70698b5f4668b774098ff2b" + }, + { + "public": "06a300d1cdc1ff65041f6711884bded571e6fa3947f62a8c04c1e5980ee776a432e5b51f9c7a4927f10dafbad204aa0833cf8286b052c3adc7873f15cca9758d69f3756e35c8a03865dab2b0447a75b5e743c87a95fd9ea9c40f9d82077f1bace8b2ce7bb586958e5379b9770b050ccfabf582b24e478ae5ad877fdf9c2b6334", + "secret": "97baef272e2d1c71bab9f05b1b567c5eab42462803b59f02672018bb71f494f51a8d21294e4ac2baece44cd60836cace98e1770629818cccd17b503f5f16554232e5b51f9c7a4927f10dafbad204aa0833cf8286b052c3adc7873f15cca9758dd359f40302387e554e425ef14a009df41c17dd5fbe4736d559a571ceabb85fef127819a21219117c135d43386010fac84051ebca29bbfb37c4184396b1803a4fe8b2ce7bb586958e5379b9770b050ccfabf582b24e478ae5ad877fdf9c2b6334" + }, + { + "public": "ea4061bff68e5564218f910da7bcf95a092eab543be53c8d07015163f7c8eea110fab30b24de1003a409056f1627f945b86599f4b6a5defd65753b714b24119df9f60870ca79141dbc2f133676cf8464f4032689462d338dbc863bf45bf31ac4ca069230f829cc880f4411445c6defa3ae087110954de129a1d3eaec872d8fa5", + "secret": "f496e399edaefae95b47e17bef722f2b99f2d77ad54617bad5e865670addeda4a305ba978dde2b1f1035c22db870194d18b5423467179a9abe8516a8e8aac9d110fab30b24de1003a409056f1627f945b86599f4b6a5defd65753b714b24119d90fbd436bc5287d02330b2e283671f1a15abd40cdbe99abf528c29a8587737d68ea3ffd3e116a9c30e46b698540b0ac5361a6465728c9f8cbded701c48cf139aca069230f829cc880f4411445c6defa3ae087110954de129a1d3eaec872d8fa5" + }, + { + "public": "0f6602041c4c829aec44c66b63718d113ae52bfb399372282591910bd30334de62dae694595848d528afda85b9a4461108d6ffbcee08f2d6e757d4a53d8e688f166dcf685c535bf1517cd88f18f718dfd7aa55facd325f97f22872457838221ecad6a040673afed50dccd9fd12eee18c3b1d9b1046dd41a1a026b0754329e61a", + "secret": "72e28cf583bcdfb885100497de0d83ddc094d1a8d899af90711b890fe906be20a3340d21356aee57f012886102de02dd8dcffbd3b535b4ca8cdd8ff4159cd2a662dae694595848d528afda85b9a4461108d6ffbcee08f2d6e757d4a53d8e688fb34d58bbd2e8b56da6d0a0166de3b3a575fc90501f55302540519893cea5fe2c70d6fbedf9ddf40eb4d468f533525f7573b6d74e4a59b642d06e2fc796080c26cad6a040673afed50dccd9fd12eee18c3b1d9b1046dd41a1a026b0754329e61a" + }, + { + "public": "c4b66819e68410b5d167414961660d30e8d9b913c7e69e710041ffae0dd8fae818ff8f7a44c24670b76f8ad34b311a2f1596e590282181ae23592d1b91ff16cb7cc89880e3091132589c020906f78d6f116f5277027ade716090fbe1330321116c83edeedb652ebb8e15b27162139718192f4963ec10fdd915dbcdf65f0248aa", + "secret": "83ac4dc9a632b79e480a454e7b2aecc004955919d390ad72c75c46d194a5a01563a693fd491feefb79c4e485063ce30df57ced90c1d63d5569b19fb9314e53f218ff8f7a44c24670b76f8ad34b311a2f1596e590282181ae23592d1b91ff16cb876e22fd5b0d86461b71242215dd69bea1636c587cce269351be996a84d7578942e34b3a5571a2d6de6802a6a3aaaf83358b27113a4931759baa002d1e9ed6066c83edeedb652ebb8e15b27162139718192f4963ec10fdd915dbcdf65f0248aa" + }, + { + "public": "66ad7b5af14c35c57b47aabf09b9de02f88ae8456947771ce65e1399d1777b6c8eeaeeccab567021ab61ee787baa12c0de9aaf094f92da0533ad2ac963d45b93456d75c8b0ac5f2605eb1bbdb936bc0b8f9ec71025b5f5843fcadbf3af97fd5dc7a279099c32ea88819a67b2f03ce99059333738ebc01432eceb3a604787bd37", + "secret": "b82ce3861a5f894568020da64ebc1748c09554d2ca72ec44cee63f4492adbb5b08602fd2bd292b58b80b6d14a7487fa098663246475fb952b5a77df836f8be7e8eeaeeccab567021ab61ee787baa12c0de9aaf094f92da0533ad2ac963d45b935fc66382ce91f52b14242b83bf4c9bee4210014376d358e29e6355f21f0f0b076671316ea87437f4794252966190d4edd419461d839f77e0d39f78ee7b5d7b5ec7a279099c32ea88819a67b2f03ce99059333738ebc01432eceb3a604787bd37" + }, + { + "public": "0afb5456bd9220f32b8332901f831bf22ecaa2161e656d0da543ee28b752a00fd95f7ed05e5cf8931cc0a370061f9d8232cf754bf50564a9392a6f14852acf2cdd22906acf8ea93fcbcb73774878246ec951e75450b6a43a0ef4e274efa2a72830d5242628ba80e849b1fc3666f4a5b1fcb7688596cac43d3afb00f6b9104710", + "secret": "4bc2ce0648310faad57b157fd85e17de05ef4e99d352e71c237940c8e9c49676bbe39316f3e890f284f44bcf2e5b56d80c0810b3339503c632a7f9b33a3c9eecd95f7ed05e5cf8931cc0a370061f9d8232cf754bf50564a9392a6f14852acf2cef34d722d2488c00f531c34dd5af4ad04316ade02604951c67e7eeb534bfe9f7314946b1cf2c0a295a1bc6ae0ebc1feb61a743ee3659fd73e3cae5a557d3cc5230d5242628ba80e849b1fc3666f4a5b1fcb7688596cac43d3afb00f6b9104710" + }, + { + "public": "96ffaea7739738f5f26b56d5988b1a83f0b963ca08890d13b2a09c288725734f55266bf7198415fccc54e0bc15ed5d33d9ea94bc0d44033902a73fb35f17d4e84a484e5be4f248a9f046ad5968715f251d4a2358430f8eb86217b214e66bab96f6fb38d7a3862ccf685ad2960d85f127938335ac9a4fbf8cffff6db336c85588", + "secret": "d20e64dcb78f602c2cfe66ba9bdb21da392114e7efdb195672a0c432648c096b4d8da732d454c4103b542180b86f28471021b04049637fa216e78c79acafca9d55266bf7198415fccc54e0bc15ed5d33d9ea94bc0d44033902a73fb35f17d4e881cb830de014948bb547c2054f89800db655c9d5c40e11e6808954daaa9f9a16be1863177dbc46095b7beb45f85b4c285131e19741dc4c663f0cdf0d704f968ef6fb38d7a3862ccf685ad2960d85f127938335ac9a4fbf8cffff6db336c85588" + }, + { + "public": "bef84c82b265acb6593315f4ae3530e88ebddaa2dd2c60cc4163c1c91fa6df1eccbfb6f65686506be9616d7372d5292187fe9ac4ef1bc4c06d9f595ef7eb9b8744bacfc9085a5df302190402d1dc2b66eb97ceffcad1ede85604d31f03b8c96342d5f56b27a0ac52979eb9622224d476b7bbe525a26dcffb82c66dd66dcf01aa", + "secret": "58669f3c24e9737f70fb11e68f6b3cbeb279f81993e95b06929900608dd00df200880566fe4891916f061c6e8b1f51c27361c05211a5df40504773b0176ae967ccbfb6f65686506be9616d7372d5292187fe9ac4ef1bc4c06d9f595ef7eb9b87987a09ae4265d2e0ccc88afe4fa702062140bd6305cd637b329abccee2abae92699b257415e5c48a293d66a346ea0771e0eaad0b658a508bb21086fab75a371042d5f56b27a0ac52979eb9622224d476b7bbe525a26dcffb82c66dd66dcf01aa" + }, + { + "public": "5adbb4498c1f86b69c29e01eb78a2e33c810b2f67af31bf766aa757a26acea2f640f18eb616aa7e7fa74b9aac43987c2379458d34750603f420f0ed95e890ebd5d6fc434352666b4f288edb6b7ef464bea4e638aa6c52e81f7e37af97c5c46d58c41cf3082275e5a3942085450549262ce99ca2b39a539ef2a82b779d7008720", + "secret": "ff913b4f2a002de1c411c1e205ef5f24ce9aeed88646f264d776a2f85f2d5f6e084d6a6d4b5a58909e41375a440f052b1884da8e68e098f429094a5faeb78ada640f18eb616aa7e7fa74b9aac43987c2379458d34750603f420f0ed95e890ebdb842a503736ccf5bdde9b433121807d1b92a6be77309bcf4399c51a50ed8be954546417434cff7468f9b6be5aabbacc7806a3f2c4f121e941b2e34db46b9277a8c41cf3082275e5a3942085450549262ce99ca2b39a539ef2a82b779d7008720" + }, + { + "public": "57cdea78f8f13271f6eccc7b4685f3b80f246ef53a40f389d0fadf72484a1ccd0fc3efa1845678c15c909eb6bfd2689954c178d09a67970f84ed68676a2e13c2a01daf8657d2e58fac407dbce7fb8be2ebf536902092b8b712b02b46bf3af93414bf2b05deeb89df56177e223db7318ad8799e029d9fcc2f473e7349dcef939c", + "secret": "41334f40652fcd6f242746acc853a325f1b754e0424b3669c6956cf6aa3723f5a43b393171927ab80267ed347ab3db069753f1553c7fd57af228fd8ccf44450e0fc3efa1845678c15c909eb6bfd2689954c178d09a67970f84ed68676a2e13c21511afa27889b82a941d289703023e08db6bbdc67d75207a1f49bfef42933022f8f96a29aee2543bcf0198102870fa8b1f6c72d59262099f1d751490d5a6e72d14bf2b05deeb89df56177e223db7318ad8799e029d9fcc2f473e7349dcef939c" + }, + { + "public": "dedcea4f037e09eb8a4f9a6821d52f3b400df5dfda86666aae45b59a7c9043be1b42d3b804059d5e189750a2632f9f1d76169fc9d22ef30c4d4a9d14ea79d3e4f9279b42259ae5da3f9e13d9c14daaec4592e617f09521ac7e07ab688b43bb7d9cf16315cc92378571151d922d33a831649e5ee44cc1acb76b3a8fd044d6de48", + "secret": "7aa2f7b9b59fa30ac688904da719c61ae071102854f0a3c97ba76f1018af14952d068907983219c45634ff8dcf90245c34633d64f6fc1e2ec8d8e5a66da1cfe91b42d3b804059d5e189750a2632f9f1d76169fc9d22ef30c4d4a9d14ea79d3e4640e1f331b3c5bb04e2aa6a632496ad94416b53713596630f9b1151bb190083bd7663feae44af632153deec4710a3c31b2542339240d5b7eb9f1a50df14d7e409cf16315cc92378571151d922d33a831649e5ee44cc1acb76b3a8fd044d6de48" + }, + { + "public": "b6e00962d5ba70386b6e4db7e3b04b21658e130c3de397fc76e68c253350f78698723bbd415a42f1781944e88a2b595c890c3d6e794d4be3204d3ef7fcb7aedba9b80966778c13dc9d5592bde3568e4fa255ce299782d84e868d55b6131b7d3078592e0c2b622891b6613ed42ab1e071a822bdfe05ed9e86558edeab6cb984f5", + "secret": "a1b3597224d08c097a41f765ab4e40719f07f0b88c70ba46032d807d6ab595699363c012e205a4457dba2f144ee209fb4ac8fc644a50b8de801b4f6c002d61b298723bbd415a42f1781944e88a2b595c890c3d6e794d4be3204d3ef7fcb7aedb8280110cdf20d1d585ec057d42449a3dc698b2509cfc962482605ba3cb5e29d7e9299d88a72616f147a2ce50e14b675ee669ce72ad674b62aeabaec8b0be625578592e0c2b622891b6613ed42ab1e071a822bdfe05ed9e86558edeab6cb984f5" + }, + { + "public": "b1af2c942fcf301f3293536b871ae8c9bed4929e4760af9075ab6d527ef01988c7c2bc92b7584b6409ff21d998f1fc965cf9a205acc65da1a8528e3fc7341988467a7e70d4116ff838ab42567fe4fa83441878ed2322a07e9843a3c041e91e6946eb9d2983d9635f51cc20f45edcd16c0a25b3f361502bf32c22a28b367368a8", + "secret": "254ae3c8d16b1e946c3ae445c8b04d382686c93faa59aac1b01ea6ec15c737ebc56b894a7a8ea46458979ba3e51f6e5cef5027469f865a7e4f62222dd0835cffc7c2bc92b7584b6409ff21d998f1fc965cf9a205acc65da1a8528e3fc734198855d1eeff2cbf4671bcf8c33e27ca9a986a5fe816422010e2b492293a96d250f9a4bf9474cd65e40f2129cf6d10dc27d5734da9feb6a9dfcf520bfc81b817003946eb9d2983d9635f51cc20f45edcd16c0a25b3f361502bf32c22a28b367368a8" + }, + { + "public": "c293b68ada2fc3bbe6c8a71509d16c0771b9c7d2fffd230f3e18557d54ae6b5c9f0301394a8bfd8519ae5ac6507b73f8f97511bf6d17bc202166ffb35e11363219c155eb80383b332b200e6cf062871fc52b7ffadd3ea383673fa491dfeceab7cf955eb2cc7aa87ac3ef79188b35a4c60e17b2fd70decd8e5b921205d4f330b1", + "secret": "412f414350e558e5318e9ad7e1815e4c9346e0b091a9b09fca02af4e7479af37448fb0ede6f2ab53bbcfd0de19873a595cbc1a71ee8da62c538a94ce469f7f389f0301394a8bfd8519ae5ac6507b73f8f97511bf6d17bc202166ffb35e1136328bd5371863eda121164ec72fd1bb05be2e087b99f3150d61020d18fda338d76dceb7cd7ee5d16605408e29f146b2f20fef4994f16c27b07491501ace8a590eadcf955eb2cc7aa87ac3ef79188b35a4c60e17b2fd70decd8e5b921205d4f330b1" + }, + { + "public": "158d86b020175c4d18ea446b0d3ed7b7cc9f2cc52ae42617ddbde3396a46590094b4c016c04575ce79cd95957a1b0dcb46621c979d28a1f72cbd2f92d2b750002649dd3894976b3a9ac427a840e1b4a897ef7a0700d9794824642fa0977aebfc3b0c6642cf588710892a0fdea10d06c40674fd046582337fee0834f3f1a80803", + "secret": "55538897e04787f89aa1caa6141377a0eb426b3c02e768ee6b7169ab5aed9b02f2a9041accf9394bf04a3993abd43295e67083e6a00320348d9ee11af837c2df94b4c016c04575ce79cd95957a1b0dcb46621c979d28a1f72cbd2f92d2b750007a246aa20e7454252e4b8dfb24c1b055c7e002dc233e9be09d0c55d5a9843153afe76a00f2c490e707fb861dfb173f23ba263992fdfce2540624e44553e1e1663b0c6642cf588710892a0fdea10d06c40674fd046582337fee0834f3f1a80803" + }, + { + "public": "24de95ac44d2a34cafb13c6ef9d0e33fe11116fe3466d913de9eebb03fd43ecf3a7c6d0f2dee09ee24fbb159a9f217a956a91a28e79e6f2e8004035567b5c124ffe2fa8a9eecff9e5049ab925e4afe093c336bcc8f0e177c3c4e3d57afdc3f7c793eea8fb123c8b5781bb518256a1f578a011efbb8ddebd1c6347de987d841c6", + "secret": "3e77fb389047eca793d0a6d880336f2a55a8f8a71fbe8b6c29201ab6df1fdb5766b5dd08ccd57970a72642d9ebd879b8dd43d0ce7bfeefe4a43415ccb31765853a7c6d0f2dee09ee24fbb159a9f217a956a91a28e79e6f2e8004035567b5c124eea910b4a55afac1efe3e73e9bbf77dc8fff9373808943048ca5c88edfd6bd3d33ed102f5e21a108ea9dd8732dde442fa5957b1ffd48e2603a2ce18f423d0eea793eea8fb123c8b5781bb518256a1f578a011efbb8ddebd1c6347de987d841c6" + }, + { + "public": "0666d281f12c1a4afb7da3ee522a5e49f2e6eb1ed55f9e9a61187312bfecdc110f1c4e9df274c6030c9af4abda14c1d36eb14294b0226778b9f3aa4ffcc831b63932b05727464c4fe45267e0e78d895016096f0b9dd1280734b9e455e5c3b2be57c67c1af4c860b8b77cedc8db6a6039f04f18903f7adf973c0b8e307e856913", + "secret": "bd7b9d7d5845b3901c3b4c9d57942b90b4f905ad4ed3f54a4dc7b8dec2b4fdf77320a1f559bf1848825cde5e164132711fd5b125ffbd6e5dda91687069f3cc170f1c4e9df274c6030c9af4abda14c1d36eb14294b0226778b9f3aa4ffcc831b699b58fe5f70bddc802c6f44d726fd54b2553077db66def6be960986b5a0cff666989f56cfa6a6d63042bd6e0531d935197f27e404b5570da6c490e8229974c1f57c67c1af4c860b8b77cedc8db6a6039f04f18903f7adf973c0b8e307e856913" + }, + { + "public": "d86e65ccc986c0f475c922754db46fc7ec7b58760823f4e63c22cfbaf9002d2cfd4ef60db732e67ff99b188a6ca2b5220811d0c23d35c115fd045c1e214a8af58d5d61176273f16ceba5cdf3764ca4d7330471c567de39974711a3476f3330fcdf7a660e0b67432a73ffb4ea78f9317b2e77856beb2e056cbfb980cd697dabbe", + "secret": "79d2f43fd620ecad06d932d8118066858db84849d9e1faea1c65d947a9b034e41d3a598013dd9657d25c767367ea344833d70bb2b580b11092427f7cd645ad30fd4ef60db732e67ff99b188a6ca2b5220811d0c23d35c115fd045c1e214a8af5a7191b4a061e82b407f76b266da6b31ee1b9a642f18301961f66182060b6585f1226e4c7b7773fad495a1af9fc6874df90dac3b4e354077f9205d3bb9af1fd95df7a660e0b67432a73ffb4ea78f9317b2e77856beb2e056cbfb980cd697dabbe" + }, + { + "public": "ba7277f35a4c7409e7461787d62f883b03740630d80a79713664302cb552cb792494f6647236dc2deeb0083842f5d28b22b01a7098dbc19994c44fe357f35281183cb735c5c6d12f97a9b3b22c8561489e01ec9b771c69724242ad9c92dd0e24cadcca046d29ef02a4d631b6b28222b257bd66e9e359f48ac7020fb97b4bd54d", + "secret": "fa8d5eb550b98b3f65ec0986461d940b47f2fa754b3c2c011634f296f41e5b5c9fb059fb5f1e7ba4e6736370664e1ceff7d255578a74b56062a63ca4cb5abadb2494f6647236dc2deeb0083842f5d28b22b01a7098dbc19994c44fe357f352811014286fa6902b54ec0f2bfe55baf693b748bfb5b02af94c2b74714e06b7be821a71717bff87a22c06309a37128e90900dabe163a60a3dcdf6862fb4dfc60fd6cadcca046d29ef02a4d631b6b28222b257bd66e9e359f48ac7020fb97b4bd54d" + }, + { + "public": "866d071ec0acbe310c4c1fd59c31b9c44f69eb560fd767a255db9473fcb957be97242d5afe8d28d96417c1dc8a3d5d5e758885898ad1da2f75e560f76faeda939b2a4f00cd3297aab220c698d69229f204a518d980dc735dc51c6fe77f1c34cc9b1fdb49b2efbd765abc4fbcca58084c7befb246f8a1d68f1459b5ad2a1c72e0", + "secret": "aa9c61e2fcfc940cea6cd0a023ac85909819b470aacd470933ca62b5ff9d755911d810e4b552d0a67e46b77f494730de10a603bb828e7940563c71e71bb516af97242d5afe8d28d96417c1dc8a3d5d5e758885898ad1da2f75e560f76faeda93b3438687bf005c897245d3775765d42859097ad2d0fbd2edc3bf0ec0213179737a8f111d6916fba461afe0737b9393e7f5dbb0d00fb095cdfa1ad2005e8afe899b1fdb49b2efbd765abc4fbcca58084c7befb246f8a1d68f1459b5ad2a1c72e0" + }, + { + "public": "5218160989aa2aed99d8700deaff2b339bd686b85d37724690cfcc5a7599ca56b207b5b78d9183b58d90a7c437ae192c4f7bcaded7e659c59e7ec5eca6318ef9db5a222b8f9f18d4088f16939d075f15753c7ba535a5c6a6e8cdc2ba8a5d0086c4e592f4604ddf99671847d8a142d0da2bedddea4ecd1efc8748b8fbd5498eb8", + "secret": "ede46da46d926323e71afe621f364dc3de3c4474a6d37a3c1b2609598d1d2b375222b44ab1892b58ffbac56b87dda25009db8003a5b83b0fd2575181958539cdb207b5b78d9183b58d90a7c437ae192c4f7bcaded7e659c59e7ec5eca6318ef94ec934e4860386f4de0aafe90eb1d8eff866fc496160c9da04e1a4fdceeaad3e0a414534681bb54885bee88b0b5b123cfce7132a2b3836e2da1d889b923e2e08c4e592f4604ddf99671847d8a142d0da2bedddea4ecd1efc8748b8fbd5498eb8" + }, + { + "public": "a1d92d15d27b48ca61cf4a46c9e29da8a4fc3e407cd2b29dcb7fc3b6579a27b0b90140a7ae9752da37e370ca5e74a1e1313bc02e88381d8d25cfdd6200348aa6202774c0ad9d6ccc24d837b860403a095760af35793776d3e13cf83a840107fcc52fe2965f1023a837dd11b5039fc79077206ce9b45f6b1f06077b9cc0626b4b", + "secret": "ddea85074163db2f261670b9cf2f4cc065b9c8b1938972cf4594cc90c404c483a855265e7734ad2c9c0bec30b14694a809dbf7a6a0eb6889b67cf6fbc6892bedb90140a7ae9752da37e370ca5e74a1e1313bc02e88381d8d25cfdd6200348aa6fe9e636776c8bcb0adfa9b32a26178c0f2ceda9dc0d545f6ad11a9b4644106deb79366609fb3aeaaf7cd7925f3b89826531871d2913c4c8ee7df5550b13c8426c52fe2965f1023a837dd11b5039fc79077206ce9b45f6b1f06077b9cc0626b4b" + }, + { + "public": "d90e1350345b0a2f06b48dc3ad39251739fec6c07b586e2579521d05813e598337579b92246450e34a761ebe1f7430834da1cf08e07f1e5fae3119c9419d279d66e7f7679b143c32d56eb8a84c5b13f9dd7d3fd774de828d96529d4a5dd9b1ef1b906937f8e1b270fa6bcdd040eb50bcdaacb7563c39b9f2ec4305c1478548f0", + "secret": "46975cf424f3d8f5272106e93e0661f748e6f897ae9b0dafe8858c3bdb54f7a1a6d399591c383e2f653f0f9d464ae468acb7b2997b5a96f1575ddff6793cb3ad37579b92246450e34a761ebe1f7430834da1cf08e07f1e5fae3119c9419d279d8c6ecfaf70f622ba114171940e843cb15d1c0bbf7c08c1f430bb4e9390f6f97a64536245eb823628bce83b5f3e0a190fd79597b63a7dff73563f399d9e0fc7141b906937f8e1b270fa6bcdd040eb50bcdaacb7563c39b9f2ec4305c1478548f0" + }, + { + "public": "4eb4253b8d8c97aea13bc12997859dd493dbdfb4894e9c3157f3aab93db18dd8c4990b43b9cf9e1334c65583fd870c6d15fa438f13dad998e7afa24fb47db4b24c24146bb074bdf854b48d2b55c4327a084106e5c87725015b0e3e1a6f0a33e65641da2b3f0c099900c996393a595e3ecc40dab88cc19c5f3bd6e6742e2da8f1", + "secret": "c2dc49c6180d9d395a6e86271a4611b128437ad4a75784bb3ce0eae34565082964a96588294700e54be69bb5bcc145a6472a1be0b5e1bf1979026d1b5d7997aec4990b43b9cf9e1334c65583fd870c6d15fa438f13dad998e7afa24fb47db4b2ec8231845cac71a31e1a3837b4fd157e5af40089f4f95e8e86ca214a0987ca833cf6293662fd6142f74e0d92e9509bfaa3c6c2b432b0bf0c8ef850e5f95476225641da2b3f0c099900c996393a595e3ecc40dab88cc19c5f3bd6e6742e2da8f1" + }, + { + "public": "3b00a2ded480b70ee238665f392c92ee1daafd2ac1633ff025097f08a73ccb8491b9fb324b0f406a45cb126116787477699cd8a0e078a782659210b861cf7f0f94e363e417e1b9c1a82d318f98094558baa1fc06d71a39ae97ce0d5929a6c4a7040f982ff2858c1a2c223072869d0265b344a269ea2de266190ed70f6ef8ad34", + "secret": "29f11e611a1eb255f312b44b31b9120df917ff6c812588c9a1086e5605787427e73d3f3741f416fb7eeff5d587240e026b782793b7053ca3285665c75b3231b391b9fb324b0f406a45cb126116787477699cd8a0e078a782659210b861cf7f0ff6595f7cbed2c5cacbc0b5e1aab6c018accb8ec3eb0639b350cb5e9aeee2f31abe465815dfec8e19e5d1e9ea17cfa7a8f16073cbeccd6059b655113073c69f97040f982ff2858c1a2c223072869d0265b344a269ea2de266190ed70f6ef8ad34" + }, + { + "public": "eedfb935c57febdd02cd14a8f4f7a0681e9cd6c3b6150e6ae3a3527caceb44537e6d390280e7ce8ae7509a40d2a630582264a57334b4a3ba0616fe89bf6aadf12b5a550315cb593f7e61536dbdca108a1147a32bb33d135b6f0331209793e1cec15d09dc363eb96cc3bd2bb602c3986edae8c3191ff16c894951b3a057531b04", + "secret": "50f8dfd1bede3dc5ec99a1ff0b5301884a95b94aeccd1f44e70aaa7d31a7afded6095c4de4450dd495c1584f092266dce6c4406b3054f66630ea256567ec5cef7e6d390280e7ce8ae7509a40d2a630582264a57334b4a3ba0616fe89bf6aadf1dc646922b472cb135e186365134bb308e537e5872617d4391c005e3c2dc3a704b2d6212eb203126c17c979dd90bdb607f1e4993430474a7c7463e39db43e9be5c15d09dc363eb96cc3bd2bb602c3986edae8c3191ff16c894951b3a057531b04" + }, + { + "public": "5c7d602eaf67e4e4cfbd0eb897b55839d2461d9ddd9189c8e005f7a230ef785b593328c94c7b65337d5cd1938d3f634efa9338ac65c1840ed9f720891d99dacc1be8ddf8791ec06ed8c6fd575c987aab9a2cc1947abd3b9a9ded9f26aa429c4a77796ab122ac3e423aa052b9995ee87372745a2817430441e2bc928b9676d83a", + "secret": "0dab8436ea26e79985cd1ce56bdb049ec9c7f350786660f6ff60cd83d7ccfc1d4984d79bb5a39f401c5f1ccd5b0ce47af7545703d8914187ee134f90a52e7530593328c94c7b65337d5cd1938d3f634efa9338ac65c1840ed9f720891d99daccc84ef4b88725a5f5566910711674396773b441c6248bf9e0691003c15edb2c5c1782f8f05eef54c5f5e13bbbc901a6301211a580ffd87f87dfa51c2b3cab329277796ab122ac3e423aa052b9995ee87372745a2817430441e2bc928b9676d83a" + }, + { + "public": "ac41bae4c375e8327fcdda1d809a5c073da455e45864490f1a103c8ce23abb73f2786e5b8ffcb789b88507b498ec6162f1620a7d73604b6ce721f50d1d2f28d489c7632f6f2a9f0563dc2a7a67355bce5916d7f537cbe323ed0b9938b312910a575989c7b3f7a23f68bb66cc40e256706ecc1ed70bb03dbb8b44ef76e4cb0888", + "secret": "03980ba58b11d0488b0cb9b533655ce986d0fa590cd52e05f3979ef1c0472a44c175907b44ef0ef3ad89ced4b8ab58f730ee8aff7aba89e63b7b3c3ec49c3803f2786e5b8ffcb789b88507b498ec6162f1620a7d73604b6ce721f50d1d2f28d4a1c0f0e405b915ad9cfb0d03f4185bf27058a014e2892c6a96d6ab36374757eddd0be8be48eed7e3719c19c4f41e38f84500a8d01fc805f2cc839758dee6e390575989c7b3f7a23f68bb66cc40e256706ecc1ed70bb03dbb8b44ef76e4cb0888" + }, + { + "public": "ac021e54392992ea4e56f4d20829cb4664a64949423cbab20380bf5bc608f4f3cb26c8e9862d0bdab93d8d7e6af688de7fcc0ede486b15ebd35b3528d1959ca639e9c3a01a71c97e231fe141e39f705535c4de0d3a2a0970d878d5a75af8070c39a251a1abfb2a705e2d1b1baaebb83c0c852b4f58d3112d62d75ac043feddb0", + "secret": "afc84bf19cb727e52455ab6c0ef105879a804a0d54af2d7feb21bd9be64dc9c6d65665d9d50ac74b586cb61c0c0bc9ee3b1edb9195735cbc003aa61a6a2076d9cb26c8e9862d0bdab93d8d7e6af688de7fcc0ede486b15ebd35b3528d1959ca65e131375efb950852ff244f8965560fbc791604d4bff67ec975aeacce88a7cfaa76e00eab034870e305b2b374884fc21465f35bc45165a373d2a690009e340bb39a251a1abfb2a705e2d1b1baaebb83c0c852b4f58d3112d62d75ac043feddb0" + }, + { + "public": "689fe1faa248603cf6efbd77dc402568a693854ed0c238c6fcb80d36a7d95926cd46a2bfb66b797d54e9107dd4f6b98e62bd9344131432cfacc0dc09b12a323f40a9e58a5f97ce9798d33c7d4656feb3e4f03eee408d5a2f6a9ae8d61e06c55c6ff339a40b5ae71b62ab67592a9235084ea9da49d2c539bd88974ff75087cb1d", + "secret": "1dd0c6e1a02e1b97adae859be6db8d8e71a593b312898c9aa8cc544be3d77ed33d141566b7866a45a8cc466212cbed2854cf63fc2cc27e0ea6eb5c2438e5d1f5cd46a2bfb66b797d54e9107dd4f6b98e62bd9344131432cfacc0dc09b12a323feff4b0f60ef6f094e8ffe297fe02444126f5dda097c3b9ea46d22959573b6e8bbcdc52793c1389b4d583ef3af99b3de8c58339d3cf1905a9dd425f0d84da4b256ff339a40b5ae71b62ab67592a9235084ea9da49d2c539bd88974ff75087cb1d" + }, + { + "public": "c390d0e8448f9711bb4e46e56fe79d6a087ee99c48c8027587db24cc687b7672403eeb1df484690844ed416073365d7e8221816240389114f6dc2d96f952b680b642ec4ce39fd1ae66cfebb29f252b108ae2c2a5229ba17de510e1fffe518fcae9a74e6743e682d1d4493f320d3762819a8e05b5b735b5480f7493d3a2295c2b", + "secret": "b854031bff22fdd88d41bf71f96f13a6ba8bd1a2fcdf7d1691f93fe9798625a40fbb3cef3469a3faaf6ba663a5c46ad50071457275b15ad70c221c35ada7ee3f403eeb1df484690844ed416073365d7e8221816240389114f6dc2d96f952b680244d94cdd783892b797df11cadf046fdcedc9176b5bfcba452313c3de6574f43a90d10f570adc637436c19a002defa8f93e58ef85581d87e9bf39edfa3812998e9a74e6743e682d1d4493f320d3762819a8e05b5b735b5480f7493d3a2295c2b" + }, + { + "public": "a8c02aae86c64437adc26f10924429b199766dacb31d5fe638a1214b869091fc2eba0702fc6b3a363683d8f1e381ef2ac90a98a2f77beb6a30e36298a0817aa5862740b7504804b10c4000a212060e31cce1e4ef9de0675237a1095fad07caa6ee95bd2eb746b08950bdbfdab4f9dd0d195a9298b27eab6352138a3be4d341a9", + "secret": "295b611c5da64d8699a5e25e7e6cd7342ff52dfd8e3ff1a4980991ff3cd8366b03f34ac6596fc7c798ff304ed0a35e16346671e639c4aa5e42a3faf7fc35d3352eba0702fc6b3a363683d8f1e381ef2ac90a98a2f77beb6a30e36298a0817aa542d90d8d55f916d136dfa16f0acb1a339b8027db4fcb2c37713a3eb01d7c0038791b9d7d1f74f4bf967a4963f88821b3c3b17744c85fa3f67964bd55a3fc79c2ee95bd2eb746b08950bdbfdab4f9dd0d195a9298b27eab6352138a3be4d341a9" + }, + { + "public": "5632bc745828f215c3af2be27b6940dd29725ccf19a9c6cd493ba297d40a5d871369ae61be49afcc6f6accdf1eed7a04334035e60b92f1587f7dc7480750c628d2e180eb783344fb18a3f51d59f61e3e4ee51841ac8a61237366623df8eccb5148d65e4a8bf00e2eded2c3e77676f4226eddc318997b70699bcb429f63fd1a5a", + "secret": "4b85160e4b0620a208cad14f038e66484c9a7e9ec41c778322b10a03105b7b643c991388f4ba5025d6786e22bff0f38d1e54d5db8fd09cdb4e4dd63c540995d91369ae61be49afcc6f6accdf1eed7a04334035e60b92f1587f7dc7480750c628ee2c0fc1c8405f6b5873451e6402562e920627c2775352c17d64c3354dfb64a618e98119ba88b8355126644a3930e182da2b61b56030f031814f7721a560c69b48d65e4a8bf00e2eded2c3e77676f4226eddc318997b70699bcb429f63fd1a5a" + }, + { + "public": "7af99b9ff8204b60da9f1dca49bdf0abf8fbebc8be5d45201caed89cb7e7b76a8bedd2d5c57c4c741af474ad08f6be3fb4edaa21ca83c6ffd32d155a7fa2d51be91334c41ede61e05adb35417dc1cb99e67a2e2e26cbff9bd253937ab85acb45791c5976320b8eb56f16e320f05983cb53f4786d67752688618686ae24d44ab3", + "secret": "95535e504470a9ccd4c9d89365b3a0a44a18443469f866471cce4a33120b8cab29c9185235f9a83eb27500c14976f49562d60db83144717506caec841435dc618bedd2d5c57c4c741af474ad08f6be3fb4edaa21ca83c6ffd32d155a7fa2d51bbc6270e93a415b5643a82cfb83e4d6f38d8fa6c1c679f9fe4365695b81972f86defa70142f42cd8510212d5b76f1b5feb78e60a3efd9cf5128401165ff5cca4e791c5976320b8eb56f16e320f05983cb53f4786d67752688618686ae24d44ab3" + }, + { + "public": "03663448f00733caf973457e7b7ed2c0cc9fa7f4f9215b6ac2349c2b04d96a8f1645d1357a0457464a2e9bab1b039621f19e0d61162f5a37311ce62336c138b5f863e37a9a4a637d3017654fd7f38cdf08a6081efb2f9a37b47ed358c1fb3dbf86ab5e0ad10a3cdbda1c451505eb1aae6377667d9672c1c48f100500ef062aaa", + "secret": "ea8ef02294164fdd63b1cf00a6f4dd59600e37eb82d3c957c4b0548c9b8ae8416c5c690057287a6d736e3d5c970fded509f4c38f7e59ab8f56000b20fef703331645d1357a0457464a2e9bab1b039621f19e0d61162f5a37311ce62336c138b5cf0b8a6a6d2ad6037a577be9c9d18051603b790884b4284aff273b0a3168ef54065c36d435d8c81e8f081811031f40612f50156fbbfe335d1574ff34372e070b86ab5e0ad10a3cdbda1c451505eb1aae6377667d9672c1c48f100500ef062aaa" + }, + { + "public": "3dca205f50d70342d4978b5e9bf59077d7892a321f014a158bd373eaa7a58a5174379d5dfaa87d08cdd86e5cf4d89654c661221282f5af8503943acb91e260adb91d018e4448f215570cf4c6b009e3ee3c2889a9fee6f3a978b01dd223814b0c5109aa2d7983a812dfab98882528ff5b3abc73007e4105646d8caf32bd95a0ce", + "secret": "3fa930511acb5583dc3fe3378493d8ca9b75d72f37be5f46a5db623dce77deb3075503318c6a2dfaf188e15afb42ccf7508d5d06ea6c81730b9014987ac3808a74379d5dfaa87d08cdd86e5cf4d89654c661221282f5af8503943acb91e260ad354bd9aa1fdbd179b27f6a8fd3284f75ff031a9aaecbfccc87ca8a2813a2ae2c401807f66a8d89e0854672d7f199a6600d2003bfa6a1bb76e4d481821a02cdfc5109aa2d7983a812dfab98882528ff5b3abc73007e4105646d8caf32bd95a0ce" + }, + { + "public": "d8d9740016aa2c6878a2146ee564f8c0adb36b26e2d4244f485adb6cf33c48720d4c65889e9917e5698a0467378ee758ab48deed04592d7235fd720576fe0c96c02f905f295d74e453fc4d209a0ffea22c107016fafbea75d47e6830905bce20057a413d0076fee417c63cc1f03aa0ef19aededdf4107891f1825909d876b774", + "secret": "18c065f7383af9d4c306d431ac2551fef88fc6532cefc222c7039d6d0bc84c3d42898772aac2178eb9ade8a463be8923ffcd0647ff9365bf97588c8712c4f8c50d4c65889e9917e5698a0467378ee758ab48deed04592d7235fd720576fe0c964a1038027f279683e1f7a0b2545e3ef52a824358a358ccb88b4566c1d313594e82449e9b97f984f773425ad7761fc7f64586215ed8529f988bf2d34c0e68b9f5057a413d0076fee417c63cc1f03aa0ef19aededdf4107891f1825909d876b774" + }, + { + "public": "772e564ba34feeecbd83ff64d57bd260e796bdad11329a8e9dd9810cd1338b3d0690b5a2f221a1b16198789dd320c17d30b6267662ae4fdfd7f21b7d1432137ec1dc33a624dbd86246289ebf0106d84b7302bec3483c70743288a633e34eba82d6b2ba85f6fc69246471f2dbc8b82b369d2c2bc69d02a25107fa4392b8f28b5e", + "secret": "6ee406166ca1935f818ac4ea674f02b17d0d8ca3e3537e4d9034a82395877aee1a9b574a98fd36318b05c700bcc21e9103733361e6a435c536a1221f3e141dbc0690b5a2f221a1b16198789dd320c17d30b6267662ae4fdfd7f21b7d1432137e720cae15ebb67d5afd6c84dbab3e6f4f7b59b8d05b058cab5ec98c29e917ed9104234a0394fde4928ffec1821c4153bd4b0b0ccf694d7602501c79c81d11f51bd6b2ba85f6fc69246471f2dbc8b82b369d2c2bc69d02a25107fa4392b8f28b5e" + }, + { + "public": "81deb232ce18936dabf437ff70eb24210c28350d2fa33f76ddeb9ce3ac5713e4c3472083e03443226589c52e08c306e799d778c30afc1d89257885a7d1fbd14f7cb259d8fdde08de5ecc646e38ee112e338e6ecf44770acaa64c98f983dbf0315bb940d58862de6a3b123ca15f75e29849084bbdd7bd93eb5b7fcdb331320b22", + "secret": "e411e96755452322bec09af7e018349571110cabdb1e57e6671a54afecec029e46482fa99b846057650eb351a38f2a70f1de77bae5733f9a385d2d9d88ed9470c3472083e03443226589c52e08c306e799d778c30afc1d89257885a7d1fbd14f6ccc8cd5274afec58707dc4915ef7a8598561228a7f442ce93d82b503c12c6fb65163f854b737f3d9a78d5f37a772e0489aa2b71c2e8573dc67cf7f05eb6bf605bb940d58862de6a3b123ca15f75e29849084bbdd7bd93eb5b7fcdb331320b22" + }, + { + "public": "3100ee6e3f8d1c90b17a8f1c4a191c715b1c01482db07ab7e333b3e3133232b91bda3e0b52f20c4ea1e1365da491477621b828edaeff6faec2d36214811f60b89e0eb4800448bbcc0fd893c24467306b0c118505f0c575dded9e83e2ab767490f922a6b41463eee4c47c954522a91e596eea9796034ee7ea415ddb7ac69d4101", + "secret": "37d921bafcdef6bde76b9502edeb73d41bb3fd2f769d01e2f795e612d39992f4bd6ba6dc8c460745cc6d63c8a68aef25b884924b65192408324a12dc7042a89b1bda3e0b52f20c4ea1e1365da491477621b828edaeff6faec2d36214811f60b854d91952e2d5454f300043e342cec867ac3f174189f54dc6d09d6176abb0bd629823afa9269a01f14251d57f3f1b435ec7109cfc06cf4561f0f9415e7c83516ef922a6b41463eee4c47c954522a91e596eea9796034ee7ea415ddb7ac69d4101" + }, + { + "public": "2e74d33290f2a41794440acb2fdec99efb979d2bc5450a0b5aef5c3c420eae1ebd2a0ed46e8ded86dc6a6147d0e5772f3c75f5d51bc8eaabf018016b3111708d613eec9acdb0019713fd42c4dbcb306c51fa5649666c24175032d97a7b2a79e92b7e62f6caddb030a879ee4a2f6acf2c918281e6115a316955a17b714c41f9d1", + "secret": "9a5a09f211e0717b16d526a9b582fa3012c8a8a497fe2fb400730260cbfc2facfca84f74f6948bc0f1bebb15bcc393b48bff3440dbf05a318d8e83e8e2feeb1fbd2a0ed46e8ded86dc6a6147d0e5772f3c75f5d51bc8eaabf018016b3111708dbe82830bd9195e916d44c53caae4b902d742bb7d4bf3e36710fc0d2fe7718a284738a7ee814da206a8f921915740405af3c9b078c5660b69e66493a9c9c1796a2b7e62f6caddb030a879ee4a2f6acf2c918281e6115a316955a17b714c41f9d1" + }, + { + "public": "c8bcdc238e55c3493e74226f30c1043c6deadaf3365f021d95a815bcfd61a0ea5e18b64c0d4cede341a5d7b30262c12af5eb7cfdb77209f69f7fc21e2ea7dc1ae4e093be606a797c38703d38567f48c799227acb4cb6a83acf4e654260c476beb5529bf1dba4d28bf5b950e87312ad3196a0f337f46b0d13623bb9dae9f0cd51", + "secret": "70f4e8ef06a208a6fae5b4db55e7d9b68077fda51547a287012a7bd5f0ba3c64e26c0441186445f25367e936f5ab244ff0aa8c5c67eb21031778d22ceb9e4ecb5e18b64c0d4cede341a5d7b30262c12af5eb7cfdb77209f69f7fc21e2ea7dc1a1a36607b5e1e852085e5e8d0b3cc081b9f02312f2bef123e168d64edbc74bf49ef1835d619d41d9a77f36d90443bf56864caadd822e5f31ecc0b797681512a96b5529bf1dba4d28bf5b950e87312ad3196a0f337f46b0d13623bb9dae9f0cd51" + }, + { + "public": "db2871ea7cda1013a17c8177c34153b8cac258eea8d9fcd10c32b899d59c3a151c5719ed8ad8f4bbbbafe59c56e2ba1451ff411f9587f3895f787a0c4138b76c4bd62bfdfbea930bad00d08f31941e1b3371e7116f68c5f106ede4d733b017ae2e8dbdf316ef10f5db1217c0a6b9c1eccab988993eaa09792e0639450f25d6d2", + "secret": "eb2af3b702172eb1bbd36b04c7088893a2d6a4160fc8bb566176651085b70e9cf2a1b79c305855b456be9d349a154c09d77696fadc2fc67d3b3cd0de37ae703a1c5719ed8ad8f4bbbbafe59c56e2ba1451ff411f9587f3895f787a0c4138b76cf83f028d14edc8f5132f1ef7b66086f5a4011a51e3aa5e17cf0c1adc732ed84b4bf498e9a0e421695942a57186f72688ce75d052bb3d463f85f565eecc8788f62e8dbdf316ef10f5db1217c0a6b9c1eccab988993eaa09792e0639450f25d6d2" + }, + { + "public": "530749156685451eb96fec2d0c5ba4f09a81b850c29478b2e6b32ab4cf88033954d015040c0fbdc497cf3cd735cf2591cb6d4dd246db27038fd4e584d0e0bd2bdcad8055067cf4b1512df0013a8eb05f5251cbacaa42779d84c3eb5e64834ea726a56e30dc6a1bc83009c1567ef284a23e712944180a4ebac601715d8c660007", + "secret": "ed50a17b0cc991c15adbb1a431bbee539cbcfc9cc3e22339c49040abf6e0c0107c8d9e9a3795c3554a93bfe5a6a94b54673c6a90780a9e8159bf49dddc2092d554d015040c0fbdc497cf3cd735cf2591cb6d4dd246db27038fd4e584d0e0bd2be4f39b5a95a3509c48ea98595c1d26f34fda97f7db321352fafdfeb6923d041d8508c4274083b052ba474b04f32aa2f1dd9522137bee56a4fe13dc844c692fee26a56e30dc6a1bc83009c1567ef284a23e712944180a4ebac601715d8c660007" + }, + { + "public": "733a7c1586e4adc62bf61d1ea5de89953d7c4cc0213436650f8249f1a83abc293b25fc947622c9660779ea7963be30b74fb83927b906c5a6c7122cc32337f3ba625e6457f45dbf801f54c7a9cd75c5717570cb9684c2c50f30be1003e161ea02428b8c42949774647bca891c0fa21e0e87b3c20e6b7a46879163898007c3fa98", + "secret": "25da02923c0cc89e52af956be7b4d5a0a26d366faa1e1e5d5e9556c3f0ff9da3fc073b3132a6b8fd118848855e0ebbdb57f9d4ee397bafddbe195c6bf3e640953b25fc947622c9660779ea7963be30b74fb83927b906c5a6c7122cc32337f3ba4f5a789cb0908811f2a84b38fe84165fa7b1bff81d9b3ddabd74cc56fc75f0c426c8824aca24742706f2341407aa6430eac6563b23de7dcfe512fef9a467f086428b8c42949774647bca891c0fa21e0e87b3c20e6b7a46879163898007c3fa98" + }, + { + "public": "8d93ec3077e59d8d43ab30a1fa78aba5881208c1e587a9b4ecb85b7981f2b2bc0d949642160a3de3f895f54f0862321cbcf43ef9cbea43d2325544fe88610de0aae261113ac66dda9c25a2e03f300d61d5d8ea342bf2ebc1f3fc1183f6bcdd3edd01da03a6b15fe84c0fe561e0605ead872537586788065432bfa571de6cdc08", + "secret": "326f135802b25ccddf7a2f2c93010a17125416d871fa7883f342f4e0ee2616bfec0fc2d2e3f0772b0be07aee7748b8700b41ec5382d9859122ffa6ad662f30e10d949642160a3de3f895f54f0862321cbcf43ef9cbea43d2325544fe88610de085402078d727f2a15af5a586e3db106e6e2ad68b23cc11f7abfbac26e8c6fcbd218601ec0e6246fd7658832d229fe5a47961e5eabd0df1614616c1d26c913eefdd01da03a6b15fe84c0fe561e0605ead872537586788065432bfa571de6cdc08" + }, + { + "public": "ee4df3787b320a9dda7dc8ae2146b0034efee34b13f3a8700702cba003dca5ff614bb1dc778f74fbca93ae9b0157f9ee46047060cdd8a06cdc4281c9850e183b8a37754bcd266ea93d536f1b28170dd069f34ab6bdad751ed37c44d06df2b3bae7213d56c58a46c0c7f51afa0819e0e73d22e579ebda25063aeb07e31cb7e3de", + "secret": "2c3e1d8917ad96c2c4b6e06ad86465fbc48dc4fb593ea54cf1d1db1098d97f71f708c4a071dc3a17f8734eb6fc3f481900742999b7815472ae52170d101daf37614bb1dc778f74fbca93ae9b0157f9ee46047060cdd8a06cdc4281c9850e183b1d92360873514ec19af0864beeed00d89902283b91262cf4eba4c3e69c3c9549dd4537f40c4997d2df1eb102b29682f641d59951ee6d04676fb619c099f170d7e7213d56c58a46c0c7f51afa0819e0e73d22e579ebda25063aeb07e31cb7e3de" + }, + { + "public": "01d08a3c540989e4c94539410a03e8efd3367791e7a393ef117d5009328c0bd4fbe4b07e0c1c131bf8de235bc88a64d9f25db28aafc085c9f918f6543491ce4addace85c2342e4b6e8e0928adc03710cec7ffaaa450cf7f1d79ec91cbba3273a554dd9cbba75e3459e1193b322d6f63346d8ffe9fca66a6d44e4f019f26fb574", + "secret": "8c1444649940aecbd3adffe0c0bfd0764f1e11bbd22a3651f1e6a621d23a55242b0279dcb57a6876da5d95c1a7a6ded9845f6990667c5edd5a53843aed19a040fbe4b07e0c1c131bf8de235bc88a64d9f25db28aafc085c9f918f6543491ce4a6f195ce93b6e1c4bb70f37c0a704db245eea33071bf303d0b48edce281d7ca39b08bab40bc96574e63f350f61d65e598278617e6031623828490647605cc4c87554dd9cbba75e3459e1193b322d6f63346d8ffe9fca66a6d44e4f019f26fb574" + }, + { + "public": "ddd0de4c7c30b009cab97ecd90d56bade3d21b1970ce1dae7f9a1db5e1ea6cf8c7f504dbae81c455363773ce626e36b3f59a0c9d6f5835b40ce492fc982f1ad09b770cd7561199c020b50f0c7544b20089cdf52f6e6dbe45ed95d0246b94f2699a1f17307e52e40800a75f0e107f6a1af601b010eddaa5a02a8abfa00aeb6b3f", + "secret": "b02ae94039a6a1acf431390e6e6395e0ce714635157e22b8b1ab6fe988cea32daa39966dbc850cb8436df9d8d65220641cd6cd9632cf64a5d93f380ce7a67cfdc7f504dbae81c455363773ce626e36b3f59a0c9d6f5835b40ce492fc982f1ad0000ab0a9fb84057f520ce54c2aa36176d7da311a748dc91b40beee3d2ddd3c889668da675bd9690000fe510ea5ca12328680b3bdc62fb37050548c7298a6b9ff9a1f17307e52e40800a75f0e107f6a1af601b010eddaa5a02a8abfa00aeb6b3f" + }, + { + "public": "29c3c883788bfb8ea56e38cac294ad68281717340e7fe956050d73647ceca648eaf39c9bfd6508a6e22aa025bae02787ab981225ed89808aa1b09660f846d8d51d360d0bc1a6ddf78e280a39918cc79e0a937f6ef9e34b73b95b9fab361a8f3e915cede7da914dc01f53aa008e113e6bb35dc7cb272b4535826a5c0788c7bdd3", + "secret": "9a78ba72c19f0c2336b5ebd4a4d0a04689d9552c97823e28994f42d5db2fbfc0feba94ac96b9eb84aeb630da6612f0dd91c19dd6c284464e850f431d34904dbbeaf39c9bfd6508a6e22aa025bae02787ab981225ed89808aa1b09660f846d8d5cc0711583626c9d4a0296724b19952e43c384314ad4fd5a29ee2a9c541865daa726dc805e629bf9b088a9097a264f35bfb9fd78c3c797cfdfddbb37dd6dc42aa915cede7da914dc01f53aa008e113e6bb35dc7cb272b4535826a5c0788c7bdd3" + }, + { + "public": "234388568f7aa4fb725d2cccf614beefcdc2a1d9204b3013c15a1428dba9f650be2e3ac9031f83e1683a9d82c4178ba9b4fe2bb3b514378b202dc84221a4a520ed3349c973e3f296e6bfb4c26d738a0eae83c28f76f5a1c3aec5024177cc64d570a5ce94da1f224c45fd9c507ba4d2dde590048524ae58ac9862294fb16d891e", + "secret": "9f52a62899ad10fc9342fb461caad2f1a903e3bc9cabd86e3a631ceb1235877658e0d6709109d91f6a0beffd242ecdaf576f0c9ba277a4e0f5dfbe62ea09d570be2e3ac9031f83e1683a9d82c4178ba9b4fe2bb3b514378b202dc84221a4a52070caebb50e9ea622a7a12a8fbddde3ee9ddf7bdea513c243bea607143490e62220be1eab53c84af207033dadb909ecd980b94f3aa4db17062baf1c7f2c03a69f70a5ce94da1f224c45fd9c507ba4d2dde590048524ae58ac9862294fb16d891e" + }, + { + "public": "40b586d249c1df60b2995487952113dc1d8dfa68e9655cfbe44e9f8c03b9dfcdb8118ee78ff45a16149fb7bb414938a714fcf5a45b3255adac9ed9f4503f84efa864a66351ddc6fe8cdf4d7031676692538c888aa7610ba1aca6bff251c01e9035e25e74cbee195bb9ccf3d07916a9ce58d7d628f4a5495c5b8cb23f0ec61e74", + "secret": "0514b072f26251813e3d7c2e4c2e288e5e898d28a3b8455989f82d1b534395eb444e96bbd13f30c4f750b61f13768e2921175bfa4793f3108e4532081ac7bb94b8118ee78ff45a16149fb7bb414938a714fcf5a45b3255adac9ed9f4503f84ef656775047bc3ffa3dfeddfcc4c5d1bc323903c5f1b3e32a7c23d05d391ab48dd428faf7bad5b4fb740a015184d3d10ee00167ca5fbe4226d46eb25c5dc9567fd35e25e74cbee195bb9ccf3d07916a9ce58d7d628f4a5495c5b8cb23f0ec61e74" + }, + { + "public": "d607b455a451340171630b64c5ff365647e25e317006baa56432de775b481544d8b564a28e9066be2406b8af1f7a650fa22a82982b6df4eeb50c061311ccd12c43c69561b78055798c05ea2fd7339fd8c4ded1f4734639ab73ac0b7bdf47e96bb37b94f425e2347b78c8216a51954c1b322a888bb41653b468a77318c59204c9", + "secret": "84e5a67e55722f33bfa44cac24164458b569b548e2abc1a619d9e0e8fb058ca3b109dd19305bcb552291072a1cf2ecbd2bc9c1ba7fe31fb2970e8a0b73f8eff2d8b564a28e9066be2406b8af1f7a650fa22a82982b6df4eeb50c061311ccd12c94d1a5333139c9361ebfc52cfabb6b951da9f0d21d0dfb6fe08351ab696cec8221c5dde9169bf65ad8fe4408bd7e5e55156db3595f923fc9968652da0b2432d2b37b94f425e2347b78c8216a51954c1b322a888bb41653b468a77318c59204c9" + }, + { + "public": "15037c3c0bdc81a00d37300c1a726126a8fac4e2e5a5555a4368b141a5b2fbb3e416887f336ce513ed54493165ae970660b69a23e88efa1a780c9532e6f00a8c625548dce1343225effcc970d8d5ec8c9b9eca0f64f7bbc4c81ea519677c1f9619440463116c03a02ea292bcfbd1f840e4f7cf85b16a5b60b300910af2156798", + "secret": "2030830f044a8f95ffb20710846e42f2257fd56f9cbbecb2e1eff90f0c825e0f138748bb3637c0468a73d2e284a67ed379c063a9266d1a84d971bfc58615ec9fe416887f336ce513ed54493165ae970660b69a23e88efa1a780c9532e6f00a8c29a9532cdb41b40e7d6e3ecd297aa860d4d704e17dbb1b2433591617da5890e960f624a779a68fe589b047d9ee57d33004fea3e1e85303e0f05e613d042d215019440463116c03a02ea292bcfbd1f840e4f7cf85b16a5b60b300910af2156798" + }, + { + "public": "6d732c36c902cb88ab88ca4494940726927a9969b758906297d2c34a692d4a236cc47c8f1fc31fdb80366466c9952faa8625448b4b977caf8dec23ab9aa37d2c00dcb4b133a7dc63253f9179829d78c535659f9608fe32835bea73f1350c0f13ab993b9ee8b4aef994022ff757146cc2369494d875d3841960695ec376b7f53d", + "secret": "6d37472e610271f3f4f868b4a003365c59c418a388abf6b9a826a977286afe05c04587c4c202b2efaeca30480c3ad8651b28da4f1338e5c952f2ada2b947bc8a6cc47c8f1fc31fdb80366466c9952faa8625448b4b977caf8dec23ab9aa37d2ca951ee31b5410303f6fb5e54163cb8c84bbf7a520f964c62be0f69c365ec4dd55d2d7517a8c3e8c1aa8c5a13003d408fd5fb02026cd2061a4d8cd0a261be52cfab993b9ee8b4aef994022ff757146cc2369494d875d3841960695ec376b7f53d" + }, + { + "public": "a6c60936994b99aeffa2f6b9fe2c861a97fb4cae4ef37d228398ddd2f6ccbfaf776c7c6c10aea53ffe296d2ac39020a9eeb475ce3b9a606f85d8c3452d2c05f9cf49ab5c6962ff3d87e70da9d3fabc26707f5c6cdecedfad40b9b82ef4b76b6f9aaba6c9528695bd1a35c57fa2461302b1977e156d105e71019c4e9960e1d695", + "secret": "7103c2fbcff239507fb1279462115a129d316f54a911a21086add81fad0f1166f113bf9166d6ad4ed82d75d7fc95cb0540bd3af924490c27c0905b950ae6fc3e776c7c6c10aea53ffe296d2ac39020a9eeb475ce3b9a606f85d8c3452d2c05f9dc8f6971b0d61018115af79b7debb102d8085098e82787e2b458d3e7031227d9ba50ccaebdf570af1e02cf9c975a5f6413956c37eed0a91f35d5b535698e727b9aaba6c9528695bd1a35c57fa2461302b1977e156d105e71019c4e9960e1d695" + }, + { + "public": "4b9a1e3dbc7f1925d22718abe7df938170fee19d329ba2a4c8a5908e889a6b31f21abb4a66cf641a50cb8d392c592c0dab615e2cdbf23a96386328041b70952c45476e00faf35186a14a0090dcae8ceb2b159909c417fa1aa32e3238c0661b8be8ee4fbc67da060586e8dfbb98b6025f07188efb26a5df6c02224a63b2762cd1", + "secret": "10835ce5fefa599fd1b359d8702912a3ca5364636df837e3bb24ce95ab7b8b77824c0f7b0a3225ce9f9c1330b2a817869337753c68a18e4477d19848e320179ff21abb4a66cf641a50cb8d392c592c0dab615e2cdbf23a96386328041b70952ccfe5d98cb09e489d862d2b33a2c97cfd806b8eb0da3385e6302121fdbe3f6cd8b209c22c02b6c72e966d23f0a47a6e5962f9413a260456da9718460a6b74a9a8e8ee4fbc67da060586e8dfbb98b6025f07188efb26a5df6c02224a63b2762cd1" + }, + { + "public": "f9e3b2418d0d1917c490fbbd6bb68d1648b4a266ce6831a750fc2a4c509f4cc76c1339e3b7a23aacca2bab3514a667502041257475644c6920709e0370aebea39b0463131dd951fe4f3ad88bcef8762361fe565c03f89ce1030fc466e87bfd768df48e6b4c3c1a86a41fe2c6220f60f3764e058fde6e23c2f5be73150f7d5b47", + "secret": "25506056ed57a3b7c1e02f11f7e413f5d817e8f7b175d6bf7a18eb78b8bb54634934e67796b8330a19ce852abf62694b72b176d8365b3e686cd510a67581189b6c1339e3b7a23aacca2bab3514a667502041257475644c6920709e0370aebea3c4a4e93fac17b806399b07c998d1a8a585ed743f3063b3d36493659057978777ac1f1163d341cb7eba89fb85d63be56474e2d25e526a5a338faa4777496d3df88df48e6b4c3c1a86a41fe2c6220f60f3764e058fde6e23c2f5be73150f7d5b47" + }, + { + "public": "6767393f05582ebf1658d1f62ed5afdd305ecc3d6b2fc5ab422e097d295485fdc44ea600084e5048b36f63d7fe2ed08168de7b78625dcc82aed56cf64a38c3c43e12c4da29cec2ec605f39677d44bf9b314f353d99356705878ed56d8f2340b4212e8e42795f50530289eff2a1949f5280fde1d1070c72f18e78f0fb3e7f31b6", + "secret": "49cc6945814706065932cac02b7eb32789f5a24be1378c3ff2a11873c082258e39f10fc87bb667e717db3cd95a5b1569fe1ce7c5700e0e1278bfdb8537304257c44ea600084e5048b36f63d7fe2ed08168de7b78625dcc82aed56cf64a38c3c479f4cb06c644ca8bf0a100e1c03159ea49554e350a15278296997e07184a717f0e6a973367d165d143b3d6f812412f201e64a1759ed77dc236ad6e0f94fefa3e212e8e42795f50530289eff2a1949f5280fde1d1070c72f18e78f0fb3e7f31b6" + }, + { + "public": "01908de39a359b159b4575e653374883d745c91d75d3c0f62971039e3dba2967d0d1dd42276791fd55bbbbcedc8ad49bd2443234b9c75f96d00bfcfdbcff93b26c81fed2ffca715055801b40a026592504243797bd713354e041ae4990f2982479bb3bcb9b9b43b57734be15047c32f81b8e303535198842e6148ab405305b34", + "secret": "dd5ae49966cbf663ff97a68c96522357655cf2a0da79ed62d2a8078374d77e4fb09d1c0ff58e685562cd3b5f299e315a37752ae3d32d81a97963d23e99e41152d0d1dd42276791fd55bbbbcedc8ad49bd2443234b9c75f96d00bfcfdbcff93b297e23d43b5f15d5ccf8102f72d9266d84aedaf8dbf8c58f273ff028b16d85c185683511865f435be95ca5cbb38311bfa14023601e5c843555d53f052c075e9bc79bb3bcb9b9b43b57734be15047c32f81b8e303535198842e6148ab405305b34" + }, + { + "public": "ccc4f1c0d066c9accf5881e40207bf166b810491a2a8ec146d993910b4769d1739b63a5ef03d06f7d4eb1d5b871db315f3eeb644edd71d711c8d233205eec2dc2f174462b7aa11e95e69f40d137443c2ea2f72583264f8cc42aacad7d02d9f5f0bcde1c6fad0913cd2fcec235564c210198e4965d67cab47704c7576f5cdb03b", + "secret": "81594e6139f7ed070ff978f6753dd01d8389df1b0697847bbac586a00fdfd36c4cdac14153e4b07abf9dd429580eea0aea6fe6f68c70d1853e68dbaa4591a95439b63a5ef03d06f7d4eb1d5b871db315f3eeb644edd71d711c8d233205eec2dc71d7b6ee4d875293c498957a8c80a2f8c982dec6604d7df7359462bf2ce4212921d82a66298bb34f6ae62b652d393ffc8f079df77aef3ae9ae6813d547e68c4c0bcde1c6fad0913cd2fcec235564c210198e4965d67cab47704c7576f5cdb03b" + }, + { + "public": "ec89612c418f6edaa4d801e4569b57dcc2843d4259eb32a644882f301008c2392640430ff8a06ec50209ec8fec4ff33871dbbe0b165322140d3b2eb590b9fe0bef4432b5e81a1eb6ed4e67b75c122bff15235de7d234a38ed236e57641354cf8e7c390f86dfb3018c3e6e02bd62d7fee1511d6259aeb94bf1f60a274ab961641", + "secret": "4fac16a216da346f194b460803e8689e0aad395bed371eda5657c06191574a02ca01ec7761b018e050821fb8435b40f14f22fb3050fa004fb438504bef38428c2640430ff8a06ec50209ec8fec4ff33871dbbe0b165322140d3b2eb590b9fe0bbea9ab59246f551462267080ee6009ea23a1a51984ec9c04076379a1cff067bf522b77f1e3b2a4d49fa3659b874cf0c5827ad48d58050fa9611c6902a1faaf09e7c390f86dfb3018c3e6e02bd62d7fee1511d6259aeb94bf1f60a274ab961641" + }, + { + "public": "c11c8c1cc3f35532d54abc2eeace474439c2ef2bb2d1ab6e618cc07677d0d86c68c39c2ed3088d170b147b2b78da4a7d5887bde82ba9879aa0880f62c1c2babaa562ab1931afec70354ad6937b124dbd3cc4c481ff7e33963a6979b1bc661e8a8e9bb1a14d5873e62ffeff12ac707c4382f24c5ff1fa5093ef52b14931647d9b", + "secret": "3139c5fade80c9abcaa1acb3394d66f32c82c5954f0247f6c8157f3bfa23ceb8f9f2e885f15192ac2b440653d87ecbe77a2fcd8fc073d8ee39613d3603fb519568c39c2ed3088d170b147b2b78da4a7d5887bde82ba9879aa0880f62c1c2baba70415ff4b879a415e113711299222c39ad13ff08ad645fa9d6e8b82223b85f35484fce1d45f53e8c7afeca8473eb6c26492c1a429bfc55843431d608d7b45e098e9bb1a14d5873e62ffeff12ac707c4382f24c5ff1fa5093ef52b14931647d9b" + }, + { + "public": "cb352a4fa70a34b20d74baedc2992acf544cf0a28ff6568ab1cfce059882ef5d0a2cb70e979b6352e2ed49bca1b35f3520d5863ba4492b03695c06f128c505e9713367bf4f21c5f6a44e7f2ffc54d8f762996decaf3394d1a2c8fde4f518a35cbdcda42d21c450a99a4cb48ce2cec393b8ed9659163eea422b234d970f64b92b", + "secret": "e44941dcde099d77add9f5e930befaf05d8b3bae725eb9f5f1619eca89c7522077a10a2aa6e6b5aec86159a66a7b3764a1da9f537247ba7e311808bb218d39fa0a2cb70e979b6352e2ed49bca1b35f3520d5863ba4492b03695c06f128c505e9aa6dd2af996e52a843551e3ed5dd362629095e7267f4aa732c3940b0c1a120d7d6eab5d9e1a717356ad713dc910e374d720f5860e32d489b31c3acd9fe23fedfbdcda42d21c450a99a4cb48ce2cec393b8ed9659163eea422b234d970f64b92b" + }, + { + "public": "3bf821e1efed92012981a6169a6ddf261409e3e5fa02249d8fd561874b9cac340e1d6a724ecf3ba308daf832c70a33c0c2603b8b91d4fdbd68aa136a94996e9bdd1ba231f9d2283de3c437c3f15434ad2083aaeef90dde62df2dc3ee73e7a0da4c6338b3957fbf0c54b9086c83a0ea569b51e881ee6ea48407ef2b1f3f474d28", + "secret": "91009fa5fd299facf0f20169363b06fadc5f70dfceafa6a8756f8149133a0b8d088deb978cf5627aed766b8666c383ce7eb9b2d49dec01eb077040c87faa22f50e1d6a724ecf3ba308daf832c70a33c0c2603b8b91d4fdbd68aa136a94996e9ba3b94075ba8fcaf642d6bcdbd306fa6a6e5775251150e53991e66a6ad8e693cebd0f730d180ca786ce61abd5f681482a05981d91f44b79a9ce2766425894c0384c6338b3957fbf0c54b9086c83a0ea569b51e881ee6ea48407ef2b1f3f474d28" + }, + { + "public": "7b64d1b02e889ad3747f5b2a463895537ceb181fdac1b10a2dac27823069f771a4427863487845a205f6d1f2dd787a91d0513d1d2d3fdd433588c588775a4deb2620e896ffc00f207abb025ca4635a00bc7019d47eaff218c2f1302030e7ba3b4f9bfe5e514df3f7d68f3fc48c53b985400377cb9e11fb0573a1de16e10a1965", + "secret": "d2c7c4ad12564807663b8dea25dd6034e6aedb8a6d5de8e208dc1402bef67c83902431d3c4f5534e256ba384b9246447e815667ddc4a468c437934edee16305ba4427863487845a205f6d1f2dd787a91d0513d1d2d3fdd433588c588775a4debaaa73978c074ca6bb410099006169a96f8c74aeaf8bafc90925056beac476b58a9f10f45fa2fed3c5df9091f9d8e2bbe11b3d2051a9c1d36486ff6b50003e7bc4f9bfe5e514df3f7d68f3fc48c53b985400377cb9e11fb0573a1de16e10a1965" + }, + { + "public": "96125c2f05ef4723f8212fee4db9f5ceffecfe0b849010bb3fdb28d37c463ecf2e99e8dfaa2877e852e7149dd3ffbeb5b0df2701e605c54bf301949dadf4c6b115333ba0fd7be07785bee33415fa18c1254b605e08431f237a784c48d20f0e48f4309388c9ed8848b61393ceef2277e4f8fbb821e144b0e7941231f860ae4916", + "secret": "529e50a43538d728ac5f28dccc6df9a550cb30b5fbbc208a1c27157867759270bb45ee3b425be1140b85c14d4a0355bbe9ac14af97424e8368ec94b686319c952e99e8dfaa2877e852e7149dd3ffbeb5b0df2701e605c54bf301949dadf4c6b174424fef8159d1ee276d670ed7d20952017df234b387055bb67fd453a9d427f6721c4918d56d78512a97292f4e782e25a5d798751b8603dfb4ea8f9d713350cef4309388c9ed8848b61393ceef2277e4f8fbb821e144b0e7941231f860ae4916" + }, + { + "public": "ce16073d7ac4a6ed0afcb9f1364025127fc0726380e0e4198bbd2f6c155cea06c6aed7a8f5bcea9d57f2c46476367a5546fcd734da0a1ebc4283a532c16cc66859e34264c4f52aa3cff2c62b491a596f1bd057c5cba3281a7c5b59b4e54fbca990944bf49ef7836b204661a819ed9ecb2dc23fe8bb7c5047360d1bc8aa9a1ec2", + "secret": "f17c82bf5caf5aedc07e7a65f30c83febac7ed07f377029f0527c043a825d58c83167b209965869f3e0d25f8d94ce0fd7a4ecc5c32fedf8f4e6cf33bdc05bcadc6aed7a8f5bcea9d57f2c46476367a5546fcd734da0a1ebc4283a532c16cc668af90534095c07415cf9161aba91674db564e4bf2e94f63f0c7a67809aefc26c243705a798beea33715f42d04329ab87e34393e421c2cbb4c105bcd352d8c1d9f90944bf49ef7836b204661a819ed9ecb2dc23fe8bb7c5047360d1bc8aa9a1ec2" + }, + { + "public": "508aba367d54e4ee349124147e5d9e5d14dd4bd366dfe4794a2620f14ca1ed7bb4cceb77db09778c8ed6b3ac98d9f65faf9989fc24dd984e02e431a84b24b5d6c70efdea56a2805c10fa28295c7cdd4da788d8256399c2f4a948c1499be76639bd635423b29d11a9095d98fc2d7ca006f21000a19b7bca238600e357dc2227ed", + "secret": "c113e2e9533a93b340a173708d9710243f37f9d01c3e639968c67a3f724cafa1d6e9d9977d5a1f0a0e0eb789c5aa82850f89379f90a1f5f98311f403f72db96fb4cceb77db09778c8ed6b3ac98d9f65faf9989fc24dd984e02e431a84b24b5d6dc69270780d0b2b2c9283adb882faf2ca6b6c7bfb3118a8cdf9cd9f4b519ca80b1a981c9df49099e33d53c397a4f023b4b503ec4f64dc09693b7e4072113a085bd635423b29d11a9095d98fc2d7ca006f21000a19b7bca238600e357dc2227ed" + }, + { + "public": "cbe002f4039dc4368a455d60d83104fae2a6f831e19c02ab27fc48362e509d6a917dff6b9cf6e72349122d69036611f128ec80174535addc1587f2ac390b1f081603dd9f9af9d5c68fe6a3f8a450cb67ec2b136dee924dd060c46968e1c1bbf41454f6a5c4f7a15b54e3765a6b29f6edfcd443bbae89aa4f2a56cffccefd10c1", + "secret": "0f47f9fa65489c5e1c9a12f589543492c0af427b9c040c6f1b5788c056f94281bda1871ebd611583c6ecad41ae48ecc097c843b443e11157cc33784065cf7f52917dff6b9cf6e72349122d69036611f128ec80174535addc1587f2ac390b1f0877c4a31d9e4f9ce918ca7059d51762306ac11f79d575dc0df96259d7aa8094b0675453cecce792ad2dfd6c59f13c706742efff5ea3345203b2aad766d99a05aa1454f6a5c4f7a15b54e3765a6b29f6edfcd443bbae89aa4f2a56cffccefd10c1" + }, + { + "public": "9af74aa260c926fe4e64ea6b21e54b77acfd145f6d08cb6205d253a88b7a92abbf09e87204e9b85db153f8c852df3edc75688031486745409080790a1e745e5280117a05e14aea6929c818009d0424ad98f6cceddaf28b1f2c87a32335925cc0f83211f60cc5e20a385451114158c85b21f3ddb3f8820859ad0f855f3b5527ce", + "secret": "a78aee9fdddbcb6f8567bc6701bd3353291c04cc7ffb7f090ae2049bb7401e0fcf4691a3a89e1bb72e5ac3209b8443820b031798842a76b4462023ff2386bcc7bf09e87204e9b85db153f8c852df3edc75688031486745409080790a1e745e521d6376b5ea43c15ab8aafc2b8291e83c4385e4752d0d2367ee6f52771b6e579ef60b03ae1b6b610c9b7cf73d1263642eb9007f3f7a3e69cf93a125e676f12683f83211f60cc5e20a385451114158c85b21f3ddb3f8820859ad0f855f3b5527ce" + }, + { + "public": "b26c366df13e4125e2ca28ec5337ea398b41ef41daf7a23fd62af2d8ed036c4be2347f6c85dd25c8da6fdae9ce8b79fbee768214aa01582b9fcf6be9f0266a88b7709b49a4112c93f7aee5f48a7f2efbee784c68cb69290bcde9742c490e20b2af0b06fd7c88d61a9262ae5f126d792fa24f5ce07f4f2ff456d7097dc0e2ae64", + "secret": "8b49ce333c447774ab6c3d90815f9ee33049355e414a11f1d545a88bdb50a61f5aecb037c602817031af6c93fd56263fbfa5cd95904739de1f34706fd64c39a1e2347f6c85dd25c8da6fdae9ce8b79fbee768214aa01582b9fcf6be9f0266a88aa9588899252ff19d7c87bcd5c4c21b92b7377988846372c6b644dc49cda5abc21151e523bebb190c11882fe4f8fdd42ed7f1816a297c65046e6f4e53808fd5aaf0b06fd7c88d61a9262ae5f126d792fa24f5ce07f4f2ff456d7097dc0e2ae64" + }, + { + "public": "60b96da99f2840abc8b89efe8403a4c876692e69cd515b55704227d6cc915b2c95c7e6e5a6d25962bc4bda37d75fd9b38bd9d25916413e4c547dc835b22d6515cf17da290c3ef7c18d3f80b0d4b8bc8042d08c9d3b9cfeb3cc22f0cbf15c24704e70dfc213f2e9e6b25c37477bdd6fbbdb0005a919954c5d4820c371f16ce6a3", + "secret": "5367436c96a551b1a7783fec2fb827efeca5a0f4df5d2bcaea8588fb5a95bcee948f4c7eda213cfbbfa7d5f99970b4cfb888cc66fe9aabc6d0da7fec44bd710d95c7e6e5a6d25962bc4bda37d75fd9b38bd9d25916413e4c547dc835b22d65153ed606082756825911b958433d7ec8e2f8eeebb69ab31c48463f7a1694630f1746e524cc1afea035ca4888d0e03bc8151ce7fbc7edcc2ceaff2470bc96eb1f574e70dfc213f2e9e6b25c37477bdd6fbbdb0005a919954c5d4820c371f16ce6a3" + }, + { + "public": "6dc7322af1c4eb6d3e37385489bd25d1e6b2bcca579cf4934266914180ecce76a1cc9f79ea1f8002b84016bd3aa1955d42c61946a285e29d78cdfaf6af604ebc50fc0fd702b87a125dbbff1f0989e48a3738c4b0416207c252cadc045622c79ea061bb14982df1a019410a7c86d57b565402b1a7e3db6deeaeb2ac4e68c2c366", + "secret": "d3c15914c567f23d9e7a95228b2f607ea10554801ce6fb34b88207c9843b1aa7c7f30625e18e7f3c5f7e05c1b57553d717d913dec095a59d8de4d2de7d63729ea1cc9f79ea1f8002b84016bd3aa1955d42c61946a285e29d78cdfaf6af604ebc53e0d0d65df8f33eebe6d4afe7bd06d9ca924c851a66092e70ede37869891473ff066c37f703307e7ae89a1647b31baa9b2cb731ac80b43275fc85e024cde820a061bb14982df1a019410a7c86d57b565402b1a7e3db6deeaeb2ac4e68c2c366" + }, + { + "public": "2672571541fc25ea0d19164dfcca02dc0ea222f0d3f9cd3e8d82945ba69b05936f58934d4e36331fb5c1b5a5aa222cc9bd51071ac806818d0f4b9bab5f09a536f14ada7f1c9c862189e974f0fbce9c6e3bbcdfcebb07a50fc0d2ecf84a24c4f17666f9be20707189a7bfc7a633c9c436b3bb7b5588e0e48d98869e1f10cd74dc", + "secret": "0a1f48b7b7c1a77e6e8db967015f44a2fa87f199c34b3fe2877143c5550f132a06a777afddd7242d7a9f0086d6b00360ec444b46a7b0be3961d61c990f1f60036f58934d4e36331fb5c1b5a5aa222cc9bd51071ac806818d0f4b9bab5f09a5361f7d52a3586d4350c9e554153e508785b37878561b0df4b3d8e61f01aa0392e99485527520cd2de305544eba2ffdabab1140896669517626a91f2011b315eae17666f9be20707189a7bfc7a633c9c436b3bb7b5588e0e48d98869e1f10cd74dc" + }, + { + "public": "655159235fe3eb2887e954623f5adc8d5ad1ed46f056fd13cff56f40c57f432e9507768a4c2c787064ccc360a71fa22423c878528cf5756ce15481bef1dad3f766376ee9dcda054366a53ee7d36ca3f90ded86b0b2908c74496e047b9a508248745330a92881229da73790c97d20e3f759140a4e096d9a59ff6b5e02b7f8be8d", + "secret": "258e00cc21e2bab35ea5f8eb26f098fda043e2403676a88efea6048b5e29b53368f9c462aa4fee5d160cd4d9a0263876504e94c59159e067c1921d687d9afe789507768a4c2c787064ccc360a71fa22423c878528cf5756ce15481bef1dad3f728a309d5daa6daad872aef8b482355066cc48df16e992e23553a64ec5b4933eade7c2ccf6b09fbbaf7a6f0a2f3955f3fc2208128ab0ec644fcf1a8d2bae1ec0c745330a92881229da73790c97d20e3f759140a4e096d9a59ff6b5e02b7f8be8d" + }, + { + "public": "cb539dc95eb142e52539458c1b8efcbe150d06a928b82a28ec7e608dc9d04a130860442b34bfb92e1e6800c27575efae27c5c5ce1f92c04095221da2639b09959d3b8a2615307d3031ae9a24655f45f3248fbde2b29205042b71f8dbed422df063bfa31cfb692191a984ffeccaff788e148014b47bd0e3b429f0fdeaf30926c3", + "secret": "972510a06103ebfa2a55ae74f5ed5a6d33412c805f4f44cf5f125290ed20cf76a60d7789cf64ba0d057b0a4a7a56fe5c5f65edfaf89776955a1307cfc8bc83560860442b34bfb92e1e6800c27575efae27c5c5ce1f92c04095221da2639b09956cefffa2fcd59a38f4ea199e672700b744196740aa580e4e9cd916a17c6809dfc6ebac0da26153e860c85d85cfca54f16c61418a7cd3e079af75517d1b75f3f263bfa31cfb692191a984ffeccaff788e148014b47bd0e3b429f0fdeaf30926c3" + }, + { + "public": "66fd0678dbad2ece70528d6e0d622e9998eeca6a33e62559a60dfca30594bbd74130a0dec10ac36f797b16da91b1b10694ffe71574b5bf456c820e54fe1fed441ce67f48e2e911015f5051bb28fc39be46b77d512f1e868544d017974655ef30881ff7d04b0a045281082f27d71ba53cd76481f876736f164c05d14fda0b0162", + "secret": "3823d393a7b0a7766f5c85d0155de27c4455b4b1bc2d60bbaf7b268105c9f842a25377015f39fbac90d2d6ca6d04ac1399e02eb290861db77d4230f6c5dc8a5b4130a0dec10ac36f797b16da91b1b10694ffe71574b5bf456c820e54fe1fed4496a8130c29e337cc8c705e635967b7f1b7c348f5f0336965310f3206913e58da24c59c203ec0909ab0ebe0b08d68482cfce8be03276ef26ddde8b2a68554efd0881ff7d04b0a045281082f27d71ba53cd76481f876736f164c05d14fda0b0162" + }, + { + "public": "c1b4e35c7e991fde90340b5b4fa86d0d9c9ad23a4c8c7f1bd5f92a3ec588384abe15d77345143401d0578926c2892ee2f68316aa4483c6d6e0a102c78c36fee53639299e21fbeafef5393a42769adf99d0fbfa167bba4d29afc7baa0d2bb17486bfac701f9d96fc970a5c702cba2cd46ce0d0aaec5045a5a4cf14c63344383d8", + "secret": "179ba138d7dcf7534706ba69741269462412972d94a6f08ba4afc3ca1e1d6455a4df588d476e316d888aacfdaff5f2f26d45f0f311d884ad2da26d4f1bf9ba03be15d77345143401d0578926c2892ee2f68316aa4483c6d6e0a102c78c36fee59db9b66dd2e2d6427730d8588ef9258e4f44f41b926b53670347f88ad749d00525a7724ce52960b85a4d8211191c069aec0189df5a86652d6ffae3d4868831e56bfac701f9d96fc970a5c702cba2cd46ce0d0aaec5045a5a4cf14c63344383d8" + }, + { + "public": "1aa73dabd2feb815a0e678e68aec37bb8b1a8603b8161265747459783a833c8bbcc17c9a8b647b2468c5e7eb232fb651fe7eb3ed2f6fa71d9fc233487f219020da577acc36cd444d8f266912c7f9df5837ad45d2d54d5a29b177f476e0c07eddbcf0dc973b1872236c94d15626886f0b7bc9797bbb60026c2fb4aa534b8dad18", + "secret": "bb627ace8666b513bd2da2ec6d07395441e0ca8a5e33c91d5bb2a756fd486f48c36ccfea28f82175f6da0146ce7a2a44156bcb36f9a4ddaa88fba24ea7a8a6cabcc17c9a8b647b2468c5e7eb232fb651fe7eb3ed2f6fa71d9fc233487f21902016e1d69e89f7aa5dd6ce5558b3823a69fe42ae591378c58563b39040968cb19038197fa9bdfac675966e83b2da3896a802bdc76eceafe4659b0269d2e8a42525bcf0dc973b1872236c94d15626886f0b7bc9797bbb60026c2fb4aa534b8dad18" + }, + { + "public": "11de017f8745c05d7a276a2445b7afdca4560cb320af32f01cf2bef061344d2644994850555f570622b98fbe7d2192e0f0c3e6624dcaf20ecc7a6e96c4cd1d2ebe78b61e669521927998a493aa255a2371734a96fe103b0ecfceeaf3cf9d0f82070859fc95d6d201fb0661271bd9ada74ebec6b3d2f8b1b094f1dd2aae9df883", + "secret": "c25b2855d9badeb75d64d52ebafacb655f8bcd4b0a3697f31c63132ae9e7d247d514731349f0abfdd67903cfe3f495fea0e736b0579452314e91d9c6964ca0fd44994850555f570622b98fbe7d2192e0f0c3e6624dcaf20ecc7a6e96c4cd1d2e11c10da143edfee0732b4713d14c7cf0481b61b299831dc9bb7ad5cde7fb3c54449a975611cb9000ef3a35cd8ffe5ec409f481258824f42ce02db8c751ebc423070859fc95d6d201fb0661271bd9ada74ebec6b3d2f8b1b094f1dd2aae9df883" + }, + { + "public": "9cd80a661f99443ddb9c122d41807eadc5f456512089771e3dc33480f9de48584768e1555668a76c99062b48be4086eec27c4c052b29f4fa2a72c2e0a71ca22def9de288a03d064b19260a6f20d36826cc7be04136bedd3298295d8de58e0f9d8aeba458c7ff27e99a44fa95e5e58a6cde08821dbf486b774e1f8a3dff38ae67", + "secret": "c172ab7605a2b3ed4e2116417287abcde331e3d5ab2b8b821393120550998a5a8e8188f7cfd0e1ad4857a32c7e1054c863a305cdb457068ec18d0e53b56978054768e1555668a76c99062b48be4086eec27c4c052b29f4fa2a72c2e0a71ca22d39f07b4a68aa860555c0a64c9ff72025b7ade7880afac8b0473ae3540c840d612b99c9fd20919708a699af3c0cedae6e3fd6644cb74ef60a0fa7b1e2bb96ff8a8aeba458c7ff27e99a44fa95e5e58a6cde08821dbf486b774e1f8a3dff38ae67" + }, + { + "public": "c03f33d738c4d3f3497a746ea23c0e5ee4c4a38f223f272a4253751b6c442de0c7598abb94efd9b4fdb1e435a420a2c00e981b27a9f02c72b23cd58cbc6d0160ee0b7b0993ef5d67b17fbe06140a302724edf31ca1e65b2c01f6e94298ec40321c998b9eec3df8d3135104f30951ebcfd5cbc160df8e027fa4838949d1aaf86b", + "secret": "1d3aa726d7d188ccd0fb8b2b5bd0a36cf3c94730d6dc564d558964f57428e46cecd1c56df1cb9afe38c3ef9ee706f6c858228659ff2e1f20bb2d34a241b1fee4c7598abb94efd9b4fdb1e435a420a2c00e981b27a9f02c72b23cd58cbc6d0160a5ba1726b024bb5f2029b159af991413b5e611cf8bcc26974fd7269ec23c297c18423e8fb85fea88c33368b5778a68b2921f5f34ade065cc15f6d71539e3848b1c998b9eec3df8d3135104f30951ebcfd5cbc160df8e027fa4838949d1aaf86b" + }, + { + "public": "47b1b2d1f324a3692b59ffd80665f4412007a7eb16753e9cef4fe8b7fb9c2255e13eeeb931931e195f6b5a6faf4a7e0aea1568dd45e202b535e744511bad0dc9e1a6a14b88e7e64b5000fae1bdf6034449ce265138158b92c0942034486b4d186f327bc2be9c6a721e29d6a50167c780d6795dcd2661e477a43aa2737337b354", + "secret": "e7a0d702697813da450cc88e062eef0e96ecaa2fe199e040d57ffec4c2bd7128ad721b446021cd1aca303ef93e2401a09949d235d7e1c5f1e66db9ee770d614ae13eeeb931931e195f6b5a6faf4a7e0aea1568dd45e202b535e744511bad0dc97d30ed9951d0d6c788bd7c989e76260ae94e6c289e1bc5eb2a58954454c388573e837795050c81475385a3f23361c7a87fcf2a72646581c267c1f051b4559e696f327bc2be9c6a721e29d6a50167c780d6795dcd2661e477a43aa2737337b354" + }, + { + "public": "d34ff5f1238a53ee310397b8ab80a192b6ac435f4079400ee6d2b469eba71c8070355fb9948101fd90c7e1c9ea105ad02397f346250f147df33bd3b5345d34c097341cf6f5078f0fe59ddb9e8cd411f1ee22a2b3276b7e0e4e302d89aec7e500dfae3fe1a389f1391249f4a2b4823420dacf9ebe39bd488837ea178aa56d31e1", + "secret": "9069374f07060734b73a77f74b9f3f1cd192d9f471719d9f76f1686f92714173f65d811311cf4df6044eea09bf09c8328058825f704f387a2ac7c6f88cbbc74d70355fb9948101fd90c7e1c9ea105ad02397f346250f147df33bd3b5345d34c0e7114418494bc8ad45988890fba81325962e1b65d1011fbb433e78e08db8ea04e6001aa2af2a997add566d5169e4bbb83982937856b2eb9d2dcdc94e9b99f4fadfae3fe1a389f1391249f4a2b4823420dacf9ebe39bd488837ea178aa56d31e1" + }, + { + "public": "a3d1ab90193e04596293147efdd1478d10e4ca8f4650f41429535192d509fb6b387fcd869b341b28cb9b1f97b5d30e0ea8cf24a2971149825f96817e9c895b7d0acd92dedeb68ccb08450db8eb0fa1c7d15baeba0499949df80ea91aafead8a4ed779345d1782a0e1461aaa56c1ad87f61ebf8f24235f90e9764450eadc0db14", + "secret": "f520a6c8afa6d50357419ca38d7e4092731e2d97b7a01dd3872f655c76fea4621fe5b4c00ed9c59b756804e3c2bea1df22767ee13ab95753a3cd2f00751dcc99387fcd869b341b28cb9b1f97b5d30e0ea8cf24a2971149825f96817e9c895b7d1397025abc4e13f8e4fa7bf0008890acaa0d2db4e048fa79ab477526fc41ee41da3ee95140e6059564980e0bff4b3c1593b29b7da2d3d4650e8d2242470fe34fed779345d1782a0e1461aaa56c1ad87f61ebf8f24235f90e9764450eadc0db14" + }, + { + "public": "93381b7bac8a92f92dca741188588b72288c401732b5b7a3b6d77e66fced4e84c977cb574c3ad5f91368908543b2251eafc844cf749ff783a7e0aa10998b1aeced5529227b8aa0d6d03fc66d69b92260530b853c93a65a8e030323478c63501d97969edd74a9d575fba8126c57e83fd621f2996a98c9c59ebb91d410190faff0", + "secret": "d019285df5c970a7c884ab6d89591ce7d7a147c5b8b4cc3dc0748229df72bdaf71490bdf2e1d56fe4521973cf2750fddbfe57ba5be318fe4173c3b8b0a3d1dd8c977cb574c3ad5f91368908543b2251eafc844cf749ff783a7e0aa10998b1aecc0436b94a436b9083dd69987df961a63458686c2d098e9dcc7f2531417e7a9db4da844053d37f9068de34e42f0c02cccd44e213ef341d60411bc443622f085d797969edd74a9d575fba8126c57e83fd621f2996a98c9c59ebb91d410190faff0" + }, + { + "public": "7deb84597a999b5bc4d7aaa0ecab0e12c719434b0d671ca0130bba08eba8eadfa994f8cabeded792985a70249489f302387211220773f83a872741f3a97403d1dbbefd6085ca364b7e4bbd9d4ffad7ac4f380ed33f9582690a88bef8eb97452bd0d92d7d76eee8dee547bffd48b150fb6a427bcc50f5ebef2bcf1889caf0b28a", + "secret": "d12a3a2793ac87624a8ddfea19f633cf69e870238f75485ee869cc67bfc4b6fda4331221dc7ff4c4c441791a7c620f72a494b7e351869f1176e15c91967ed4ada994f8cabeded792985a70249489f302387211220773f83a872741f3a97403d1071bca257e279fe3a6bcbe27dff21dfda7e12f5a9188ffb11ed3ffab36e9745d46cae2ac64a59a60fb08d69f5241c742f964a76c4e6ba18ddc9af8ff365190b2d0d92d7d76eee8dee547bffd48b150fb6a427bcc50f5ebef2bcf1889caf0b28a" + }, + { + "public": "61ae50fe84772c9b359b4f99e94c74715398506f545465323f306e93fbb0c5189654c1c3d24a33183c816fc394c7f935a1a259daed862cf01fabb56ee0a2f3bbdc05c54a5f3d0f3f115bea1c614a1c1cdd5fba111c726d6c367655ead4b76f43d8b42ca7b3e5d3a8465700a6171db890f7d3e9d98c6d16ef6b14178af307906c", + "secret": "6b88f8aa2b753f31b2caba39e18d0bf16deea7230f986c19bec7247d8a0efc100e8a1c96ae7602f1966500f65c7c7de3f2af8ea7c16372065d2d6b9b1fe1ba0a9654c1c3d24a33183c816fc394c7f935a1a259daed862cf01fabb56ee0a2f3bb0da7fa9c10fd447eb2ebaad7437cca74d39bf2492dbf2f6f52fe669fc743386f52b2992b45bd82f23f5d29f171f88c7f5d006ba5eab1ea77a573104085fd14b4d8b42ca7b3e5d3a8465700a6171db890f7d3e9d98c6d16ef6b14178af307906c" + }, + { + "public": "e8cca6d919f09f4336b650a8fb26331239585cd4161857cae8815a1cb20989045f1893e88d4b586334b005872978f49f34c5152628def49a51e29655a9f59563d8251ea85392829cdc94a601b90d678181ab2788a862c9032a61fc7d588d89a1e52e8fcb7eefafdf5b407d2012e791818fb97caac6fc9f1aac90ad8b50776197", + "secret": "3b9e96eeee7749e9659b5d516854e49afaf0a0f2140a15d284c2f91d5dbf6801389d51275afd42501699221916c3c8daa085b71c3296851c694a8ba6430273f15f1893e88d4b586334b005872978f49f34c5152628def49a51e29655a9f59563a0025a9ddb4635b3f5ec5160a0bc3f2d6c370dd0a33cd4daf558285fcc27371d63686d9c879a8e606d4fb5c6af072014c785b60c35bd1b89cf45881dfe841823e52e8fcb7eefafdf5b407d2012e791818fb97caac6fc9f1aac90ad8b50776197" + }, + { + "public": "8ccd36d72306599e5d6f2deeb848d9ca7e3c9089ca39c18d0643802762d17cecb1676c777508cdcf12e4fbff6db9785a815611e3bc2a8147483f1fcea75b5cbb15c3b84e065dd6a2deb197a4106f1c338127e24738987b7d56d5a0349479195d48a54662c84ebea106abced1e7889fc84c37c14beb249e0948864eea7d386d23", + "secret": "f6d92a04b138eb58e62739c04666b4f420dcac9f76c7adcbd055bf55363654512ef4a7664726a599151b8666176a18e894836d7668b35c7dd2341b56e5d72c2fb1676c777508cdcf12e4fbff6db9785a815611e3bc2a8147483f1fcea75b5cbb670871650435ef84b590a84b433a499888d12cba0e293f563b0ef3c1cf969bb85d3efe70f32b220aef6a0908dca2f1ba82fb138ef757cfb0233f761e7e86450a48a54662c84ebea106abced1e7889fc84c37c14beb249e0948864eea7d386d23" + }, + { + "public": "40e1224caaa28af1b4591833cc8f0e7901f87e30e48aeb2552fea657e12e7a713c9b0beda7793e1291405eda80f4dac38d5caf16437aef01e90f132a510a0d20715160cb1b0804cf1dad74e33f6b7eb65d936a9cb0204f2ad5b0212537c920ac86378527982fc3352752b881b1c91f455f2398e1d027a9a7fd405727501f1e4c", + "secret": "d7a03f9a4d0cbf422129395a8c16136905865ede63c54da8a55946295fa5c79a3729a66c3b8897308303d97bee43bbedf33f034237af51a184cfdc0b6a5fa49e3c9b0beda7793e1291405eda80f4dac38d5caf16437aef01e90f132a510a0d20f08fc8ddb56ce63df075f33ecf52943af5c7cf28e19e4202af4cbc0607bdc21573783a94f04cbcd2b39ee5f4a31d81b6a825576eca857d17d278c711acd07c1886378527982fc3352752b881b1c91f455f2398e1d027a9a7fd405727501f1e4c" + }, + { + "public": "614ea5c7de11d93fe95f2de38956794267f95ffca1cba7f86c7b1a05a16201a873e2f75d5b62fbca481e59ba1250fdf3fd681e1d7712fd6a18fc094c3389f7bad54aa40cc6269567f3d60421995f6e03952d678293dd0e4b17b30a3666ff5169a1b68f25de7027afbbb89c94ccf045ae6c029de77ae74e1aadbd63cd4a9985a4", + "secret": "41b85c6fa2adea8c91f87af1fa9ad04e8324d2a30128fce358681cc5d14cb66bece0841186b63512f4f1b1c6e8dd711b8002be7c21d06176bcb930f35c67905e73e2f75d5b62fbca481e59ba1250fdf3fd681e1d7712fd6a18fc094c3389f7ba905afaba266f9cf82e106440b3ffc3a761c1af7ef6bebc43cf51909a3490b8a2b8aab0b5ace5d6dc152348a8b12a8ec5ae27b3a0a95f58e41520d7a4203f26e1a1b68f25de7027afbbb89c94ccf045ae6c029de77ae74e1aadbd63cd4a9985a4" + }, + { + "public": "4098b8c2ee9eb7ec3a726c47da9801bbfae0de82a5f1f47aad07fe2bd59927519dd65a1eaba4641a71895adc6fdde4779e89cbb868ed01774d3bee122d8c30960a8373db58645990ee4a1b6faff8deb1437d269bfb020f1b5c28550552fd47ca02e3d63ff10bae0e95e43b67fc69778906c0dddd0d677338ea9cd47b7fcde988", + "secret": "40ef887c1ffe6c5ab45f41156199aa89dabb750b2792eed2c14e348e26ea43a17745db7707d9e64ed67fc175dd58b860a8c746e6afdf9595a9357416816f70ee9dd65a1eaba4641a71895adc6fdde4779e89cbb868ed01774d3bee122d8c309654a4a64e3dce937c66e156c679fb6811c6979ad7caaff1e13b276ac70f4435bb96d83acdf5b10f5c41c5c57e0ade73b5855308c473c6a33fa3cf510eb3cae80f02e3d63ff10bae0e95e43b67fc69778906c0dddd0d677338ea9cd47b7fcde988" + }, + { + "public": "72c0e54afd72cdfa394d1bf6d5dc8c8df907babf12ae63ae71d4348a54e5638f0c712d244cbe9ba919282572b123848c3805e0e82b6737e28948b5c4715ad1046b976c3e43d029630fc933e813e5b7b0ed83644f1327d6cfe36686f7e3714d560c0fcedb8bab4ea026ff58e1f859ca7a5ac7987ef4bb89aff4b3a95461c4bad3", + "secret": "30b142c5b3f16371bf67b5c1b59df1de8a694dff6fcc467e1b949c0d790cbef0301698b0ef1877655b9970cd42a14a73a680cb0678549caca133bd827d638f040c712d244cbe9ba919282572b123848c3805e0e82b6737e28948b5c4715ad104a0034d12c031eb8ed452fea296eb92e7ccd74a0ce086badd450d5c7367ac2db0a7a0e827ecf21010d0f2718e0ff99c8614c6902eb7900235f3cd10208cb2ae360c0fcedb8bab4ea026ff58e1f859ca7a5ac7987ef4bb89aff4b3a95461c4bad3" + }, + { + "public": "84ce6ae628faacbc3b72c0e02f4acaffac504ca276a2fc37e161c35542d9ed43b1650c54441d0fec04b01b4ea6926f39b314d3b2113f80bc133c13dcdab2e6fd25648ae567b6548a3cc4f951f68f2797dd8b36841cba9d8d697d35e904945569719aa790cc8c1b232bc3a69fdd6fb6eeda9f115b0749d29df7163d1ddd9212a8", + "secret": "02bf5dd48eb47e2a70485936126eb32e8d71afc989f00b49dddda2960cb2105403fd2cea5ea6270e0da89626448d838706de30aa66213dc80d8bf086e0f478ecb1650c54441d0fec04b01b4ea6926f39b314d3b2113f80bc133c13dcdab2e6fd7743f0d2a5d5341df4e8f801b84833b1bb559a49b71943dd1d207708fb0ee3e25137f0be4594b1a613e834b854239cc38c22bcc6114119b2c838d9ae5e49b789719aa790cc8c1b232bc3a69fdd6fb6eeda9f115b0749d29df7163d1ddd9212a8" + }, + { + "public": "68721dd942a4bc040f91778ca80f88c38587e20ea664782a2a34f8983c904c30a96902f9540caffa148ba21193d766d97bdae1a84e4a815526ae108d9e4818898419f85a76ab7bf2a3a8350e0fb8333488a581a1cc0e7c89bcda29d988a962a32204bebe6847b043f50a17a89d243decb55a64f734e16bd0f8f734ce5ae0926c", + "secret": "22f01dff45904cd6e1a341ea3c4c3b03c381675d943aec783baf06fdd8c4c384bd783f1d41216c2972ef82864e69c0a28017ce790ce0daaa1368bbb920dfb972a96902f9540caffa148ba21193d766d97bdae1a84e4a815526ae108d9e481889d0cb4297be81f4316d3d5853c9cebd5ae294d0b1c1121593532e48b7b56e54d93a7bd7c11941215d6ee8a88d11f9312cc83a262b0d5f4c25189b1a63e8f680bf2204bebe6847b043f50a17a89d243decb55a64f734e16bd0f8f734ce5ae0926c" + }, + { + "public": "8114aa30bc4ffbe4db8015130df9649edfbadb291e443b8856c4fdc7f38c7c12a237a8c68c7266bbd929f3fc4fd705b75a0d00f9dd7812486a176d6f4163160022531d82f0799be1a12f1e98e141d816743615030fd56624cc01d033015ab10e375236f60f34475ca7845dd2930314ee126692b3706223b203252a5e10c0fe0d", + "secret": "b98b4924ef7ebcbd5d51bff93b75baeb5c8f2738d47455c80f2d22b88f2df5926fd2611173acbaf50da524ac44ba378de319871ada4bd3f7152af5b617dc68fea237a8c68c7266bbd929f3fc4fd705b75a0d00f9dd7812486a176d6f4163160072b738abbe7cc16b0a6fe83ada31f0712db548002ce517ce318d2a040e44d7d4ea7777372475c676d6b3ee599d12f537ee2c674b6e62fbe26f20330057a85cfb375236f60f34475ca7845dd2930314ee126692b3706223b203252a5e10c0fe0d" + }, + { + "public": "94191a2d171c2d9d995cf6b4f09c2f300182339b693a68ad1cc0f049fa6b07bf0fddc4cd0fb710c4a1894234b865d8747fed168a294e511be639f1abd8c4eec94ff15a5f417929b8d20789ee81f7ffe5c86527cb19e74160a1ef4a24c7ee429565d242f281ebb5bb3ab85cc0387affb2ad461c4c75a8802b03b403c0dcb41d6c", + "secret": "c6798625e908d8933958b013820791b559ea6d71e2f1d192f7ecb1293e2a9385c86102a34f55151ec805d5bdc27f2010eca359df1c4092099cd546470308bbc60fddc4cd0fb710c4a1894234b865d8747fed168a294e511be639f1abd8c4eec9a7b64077fa4de42b697465cf4e7464b44a7d16e1134752ff690cac5bba0bd566668682dcff2c652759ced7c191c14beda65fc34ea38009d402ee2422b0f676fa65d242f281ebb5bb3ab85cc0387affb2ad461c4c75a8802b03b403c0dcb41d6c" + }, + { + "public": "de7ad7e3de2622b119b810525cac92b70f2369875772def2b4167fd2fe2692952f823b56740b91368e138b3ca06d990429e0a3e9f3fbba7b5ca85c182b748652f22474cfa8c197a1580efaad4f5d96ca3aaff081125924f9a5116eb045bfb04649157cd790ca1830cb9bf09c55631ae50b79d36147eee06631c50b5625f3d396", + "secret": "26b77a0c937df289676800f1cfa1b8accc4439536830ff6c22b49e62a60af4c7f668be9b15cc4c31fbc7fcf29faad78284c6989112bee7fc7446f8d792e97d4e2f823b56740b91368e138b3ca06d990429e0a3e9f3fbba7b5ca85c182b7486527982e0272c1992b6fcf5c7d9ae7d0c37b3888a5fcb3838c707bc00439ea5f9b05261a2d86c8dca53fdc1283c9926f4080e98c2136247b159e30c3b8755bb1e3c49157cd790ca1830cb9bf09c55631ae50b79d36147eee06631c50b5625f3d396" + }, + { + "public": "a62ea2ccb8ef6573841bff6f49089b9957c1bd18f4c7ec355474ba5a508381e8b38f39b30e572544fc5b8b26474638543c19529adc7e3f9f5fbb442cfbfe5c06121334eab1fb3cb4d9fa91d9b9680d45b644bf4d41a3d020d8b56ba414912c51b5300b31aaba79abf7674a81df0419a37352b2c3a6983c25d93cf4bbd02d8d1b", + "secret": "2df789224363d3dd804a06c2b76debf6174d1467a3db8c5320cecb994943050c5413eff066815f157a301924dbdb30713f3df0be78d331ff1ec7f8763d288fd1b38f39b30e572544fc5b8b26474638543c19529adc7e3f9f5fbb442cfbfe5c06fb60832f69404192a357e4446767013553bdb10791565a8698ddec806a18fefebe66389943035b737eaefd316e20d546169ad6299684e346ce324413af7e705cb5300b31aaba79abf7674a81df0419a37352b2c3a6983c25d93cf4bbd02d8d1b" + }, + { + "public": "02ae9147c718dbe7de0ea095c5a4385b5429945a4c295e2beeba955fa5d7e838167c2911e39f6aa0fd847397ab4ecb4994af84a96323a462b86b0362be7dc9c8fcec6e06223825836f5778cf049907ab269646ced127443100180010af102fe9b8e8dddb032264cb1eae4ccfdf2362f50c3b9ce2d34333e765c5b0cd8fa9f48c", + "secret": "bdb017986d524d6ad7d365d6ac015714cd3ffdc58cee1926833cf4813698b24e0b3541b0379f5856ad0c206d96c922d2aae31972ded15ec459d4fda9a3e70247167c2911e39f6aa0fd847397ab4ecb4994af84a96323a462b86b0362be7dc9c8310845b135f3f304315ecee670dc9ab5c1b2e14dc0425fe3f5fc422fe672b91e3e7b2b6f964966da3227cbe026a0c7f5b7b1c7ce654bb8e8ac3bd5774dcecd59b8e8dddb032264cb1eae4ccfdf2362f50c3b9ce2d34333e765c5b0cd8fa9f48c" + }, + { + "public": "a8b9590880c9e80ef0384d5b801e785395d81603bd7dfe7978e325680294705fef8ac9c895b83f3321e47778fbf65582e23322a173df5075933c08d51f12152e315d39b891f9411e69011ab2c70f5d341abe19a9a3d93cac3f1c64f4c9c7a0961728370bf72be225c0ee2d31133440d00d3811ce6af51e4a064e038b96535fc1", + "secret": "ec29ea584a65177d3c5957848a26d7f348bb079d95a3dad702efc56899221803fe88b4071fdaa16e26e8418fbd552c28be0438ed0e1c08560ac634d93e606092ef8ac9c895b83f3321e47778fbf65582e23322a173df5075933c08d51f12152e0dca85506f7a69bf08ffc8e6c0be98afcb3d73a3d47dec018e28044b21b80ff1825bfb861a728f10fbe896261996a97e8b3ce579317cfac17ab595ab24b419221728370bf72be225c0ee2d31133440d00d3811ce6af51e4a064e038b96535fc1" + }, + { + "public": "00517da315cef2d02d09d69f80df76598ac81bdbbf6a48c2b92c511b6ea2cb4a13b00f0fae244ceb1f13a1bbbe4fcb9f4056b5632b8df667d3d42bb125e4343b76db2e55b6602a79a860874bf8014f08a6cd31f33d20dc6ca32ed72c8f1d6d8f8d58dd6c8a664250236de2d7b090c052af689237634a364a7a40595a13b43752", + "secret": "3f0acdf8a5623bda06dd72858d4318babe2a354008475fb1c320ea411e8e512bb0e32149d30e4a3702ccfc00cf4a7a823381b74b69f77a06f8957a1d44fb8df913b00f0fae244ceb1f13a1bbbe4fcb9f4056b5632b8df667d3d42bb125e4343b86dbde9d6178eb26d4bf9d8340baeddf45bff1ca07aab781a46c002349f83118530c8bd488138f4fea485c51e9d5dedcfdfc4bc9169eaea2d6f006fbc6afec3f8d58dd6c8a664250236de2d7b090c052af689237634a364a7a40595a13b43752" + }, + { + "public": "031ab3c4812917c83adec898dbc193c66a9014458a7f0eb8e293d641a69da404ca7f0e2f232615a9fc236f43515bb46cd90019c16879223bc68b29dfbdc657427019184847457a3020232107ab2925e083cd45022d287609fb25c28020cbf913f64026856da3c987f7debd1f0f1f3e4a528f0b368c3a510414e7ca480e3b3b12", + "secret": "cb8788dc13a2ce448f60f40cfa2df59f44e62680fa8e9143043a01c8ff5e0bcf18a19861417a20492b1f1aec84669cf3d7a8892fe878e0b34e11920341a5e971ca7f0e2f232615a9fc236f43515bb46cd90019c16879223bc68b29dfbdc657421311fbfd6e0064b551319ad3c2390d6460137cc59c5d5348292d347f07e5cca48c8fce3ac1610c8a0bcf216026164a5bae65538b9b2cda71d8cecccad1a5a54ef64026856da3c987f7debd1f0f1f3e4a528f0b368c3a510414e7ca480e3b3b12" + }, + { + "public": "70803f1b29171460324cff5e88d9f1b6f74e4312d94405042fa7b1e8ebb11d3925908f5167fccc24eb6bc37ebadabe7d44cd901e13495b551f6f2e6dcaeb768e944cd2caa1050506d692e48ebcec632231c39d6be116574529901981646728d360664448519c11373c5465970ea97b3c95817a5bfd65db21d8573063b3d0d8e4", + "secret": "dfe2071543c5a1acbfdbddc0455f41395a480a4c7f4e62a472569f1f9929064f46faea325f38482f0d2eb9de97598e83c0a3d9576656b7c7c736a7361d6d8af525908f5167fccc24eb6bc37ebadabe7d44cd901e13495b551f6f2e6dcaeb768e5374f3c981548e28d306223e879267939386b2e37eb09e66ea04bd431bede4b1192d351aed62f8ff9ee586d3502c697ae3c3134e0b6d3632e5a30627d821bfc960664448519c11373c5465970ea97b3c95817a5bfd65db21d8573063b3d0d8e4" + }, + { + "public": "b4d295091b9d4bd029eee3edabd198b8dc793429925773a57bfbe3ee81b5551dd7fb55500482a4a7db730f757b7768ad7509a008acbd451c3b98a8bc384350a752c66dcc1b98c8aa95f588d5ed4639a8163533d74f84e2a82a3c4fd57f90a4d8b80588594e165c146408d06c37c3fa5dc2f1b4fd0a7795223948c79633afbf8d", + "secret": "300b6f3d6e53843545ecc2b7826ac1f143c177701572370415d268cd0c10ab9144a47a7552db0b47c2974a8bc4d15ca7b191c4304dd1ac77f9c3a9ab3599bd28d7fb55500482a4a7db730f757b7768ad7509a008acbd451c3b98a8bc384350a7ea800dd666444a2525134edacdd71ab6155e7c12af9fdfac91c6be1e8c15c0ad7b987f40d1e228fc5ebd7ae9f6b535fc0b55bc68b0f7d9f28d50855f2a5dee3ab80588594e165c146408d06c37c3fa5dc2f1b4fd0a7795223948c79633afbf8d" + }, + { + "public": "d38f66d685b0ee6d63ad14e44108d0320b001566283cdfe793e40902c07f835162d095c336af33ab2838c28be360f89534f693008cafdb2c0bb9974b2378f405ff1df8cc3ae7df46135b15b80fa9b7800ee006e58479fe253ada693c2a5f98ba5ee2f8d99c83bb352f186bd117b133d8c0e70768f140b78cf6cc0521ec83182c", + "secret": "a985ef8341937af1c2feb2cfadee68da9bab6f0e5499d315862caf36817b8d71d80d25344b57c64b74f23d34fdb217d9af200de0334b19452b2917a99d5e296962d095c336af33ab2838c28be360f89534f693008cafdb2c0bb9974b2378f405af01ad2870584ef3f38e7aa41890a2e3d8d1e56771e90e213e1871c09bdef274299dd2db37b1b01abe4b0966303aa6c981fea9d9dc3de1689a6db9ab2c81c8c95ee2f8d99c83bb352f186bd117b133d8c0e70768f140b78cf6cc0521ec83182c" + }, + { + "public": "6ebd76e8b5c21ef51244190c0e14312a1dcf785ad3a036f58eb189f37e1efc0ca453206633556f17ff0e3cdec032b60879b70883404a50e2a44c581a7ebe2fd510629018c62d9a903115b0e603b7c6ebd88004720d9cca2b08ecde6079b150762841b6bd2c990c8367b351aad42faf3b0e8e94bb2ec1b139d937fba0722537b4", + "secret": "fe68a0b9d2445f759b8c653fdc96fde6ad00f4a20b3d0e605cdd733e62d0d7f39c0b5c72ebe6c91ae5b369bcb93325a92171be7d197210e8cf84e801f8ab9a1aa453206633556f17ff0e3cdec032b60879b70883404a50e2a44c581a7ebe2fd5343537e941cd9fbfb5b66b4459ca4b7f19deecc3f507745accc69ff0983af199304d59ba6a73f462ad855355c4f91e02aeb97843acebebfddc7b2172d50952ff2841b6bd2c990c8367b351aad42faf3b0e8e94bb2ec1b139d937fba0722537b4" + }, + { + "public": "cb10518a41a13d4c469853c952c15b71243d6fa7dc838cce91320f07902f2315e9472272e18e28697f8476bc3cfb20ef490476d215b5831ab6e99bf3405b9f7ee6dc31d81db98aa6d3cd3f9574efc1b1d6fca0a8d96dda511cdace7bb4af2e909a230b3a3a971aebc1112bdbbba48c80b2fc93f7eb914e97ce04a1aee8b1bf93", + "secret": "af857a89ae0974dea48bce3eadf641f624279ff3b66147c098f70a7f61d75be0d85991923464264f8036f1efd5f4855aab76808ebd8ce7bf31fb58d467a04d65e9472272e18e28697f8476bc3cfb20ef490476d215b5831ab6e99bf3405b9f7e55f1ac1f2f3376756fac7ea333a651a4b1c21f3d2d0767b84774b45b1a4fa2e82791861af211a5838129155527b260e0717a5ac9b61b4d6da23a268bc88f23a79a230b3a3a971aebc1112bdbbba48c80b2fc93f7eb914e97ce04a1aee8b1bf93" + }, + { + "public": "12e688837265c8a96cd73dae41e128250c899996cb513b9a1bba13de42f79d3e8669bb6919d1e6ccae10aadd45ad6c79faedd70bd6170eef5f6a4068befac2c4d7853f5ecb9e58580a86bb8442d494c43fc6cb8f9501d6f65a69ad8c0cce15bc51fc738683b86a8e773c7021d4ed0908cd21da6cbb432d4adb046832f23839d6", + "secret": "d73a5517a7d6c826c99592ce2fabc3eb6e86f1f3f3d3c465bb10428cfcebe7c0407621315655061cb566db70057363be17717bf1a4f23f2f57e462ae0465ea5f8669bb6919d1e6ccae10aadd45ad6c79faedd70bd6170eef5f6a4068befac2c4e0360009dc29272ec88b446f062f38e48fe9ad7a40022bc609fcdd9e3e3133c03f44752851c71f53186a2e5561303e5a5aba8f51a00228510c14a6203359e80551fc738683b86a8e773c7021d4ed0908cd21da6cbb432d4adb046832f23839d6" + }, + { + "public": "04d86563409dcb1f3aa3f8467b6f3326488b1a997682b42929c2bead2a9d3154c122226ba461abf37b141e175f5c8154b0e4df33f56463c7da8bc3fd6cd56db13aa6edb3727f2eab95778eb7044a9ea788f5bf6c60d2f020355fc2f5e5beb22323fa817bdd6479fdb482bdec508da7b4f73a918ada9d2debe70b88eb88ec6ad3", + "secret": "7585e6bb2e68433488888a9eff470d2c22dfb415fd1a2b067629376046066174c09dc6e2ee083f1857e5e82cea29df2c85488609a52b3f458daa6a02a9d544f1c122226ba461abf37b141e175f5c8154b0e4df33f56463c7da8bc3fd6cd56db1f6456d23c5ab3fa28a13cedf93d69e4e5d37498be2f26b06eafb224afc8256a486507d5a5cb4a4fea9800ce9038a867ae58d15bac79b4ddcfe91168f0d22842523fa817bdd6479fdb482bdec508da7b4f73a918ada9d2debe70b88eb88ec6ad3" + }, + { + "public": "99a552328720f88686f71f87f18a25417c7e6dce28ee17ea585cb6ddd2f92f606759859a1b95048a0025da9eb5ab9d2113632904a80723e4de0f71381d4b6602dcdf2c8b4339b96390983dc5ae322dffc5decad8c46889562a04681534985e87473db3fef2b8dc1b716028f8bce2eed1ba40177e3119d61e7ae821520e5db1d0", + "secret": "54c7e3fdfaa47875fb163044dea6a3e86be92bd3b405654dcea3d009cccb4ef47aa8332069caf0c2559d18a75e6dc9064bc55fe0e5c6120b710f3bf91ab093356759859a1b95048a0025da9eb5ab9d2113632904a80723e4de0f71381d4b66020df55a4c8f76d5b0af859cb086bdfb741ebbffc9d96314c8a4d7b1d246b818d644187a30021cefc805514964b5a9cd6db32e2c8c0dc534b3af53187d727f2f8d473db3fef2b8dc1b716028f8bce2eed1ba40177e3119d61e7ae821520e5db1d0" + }, + { + "public": "9e0deb59e505aba4cd431a69999dc4ae45c94e3a01f908bad82d25dd71d8635c9e9b4d92fc79035b4bbf4d817306d553eddb74e56091b0de2aafc23c349d552a787c5061dde1b5e1d41ba7490118c7010c330f2f5d2af1d15ab096ace65e35f77a30ca969cdf4dd92c7c39ed58283084e8b9a752a44a69dfdd516022565bb25c", + "secret": "a073975aa0d1d8a677a87e20c8f3ba3864a0b2979ab7a43eee4edec1e41788fe4f06e04c4362d717feeb26496e676ec54b1436322ae9d6ec15917045be6e1f949e9b4d92fc79035b4bbf4d817306d553eddb74e56091b0de2aafc23c349d552a772946567f217206f694b5225fb29a35b2850e8ddddd12e3d870825b0464bd40b9a2172b709df8f93c56183846e4696d88a9ac5be52528cc4e200ecc34bfb2227a30ca969cdf4dd92c7c39ed58283084e8b9a752a44a69dfdd516022565bb25c" + }, + { + "public": "d43c8f7193b6a71762edca5eeb4e711c8ac9926b9089188efd1d675ef53667e3c9e689968c90bdeff8e1ffd58d47b856a8a809549947feb104e63be4f9c8f701ee7aaf549f8db6ed3cc594371b06ab0f5b2e8da3a041ca5b8cc2de8f16e4de523fc93bb33c0c9c433ac76732f6bd7e184084ebaaeeab0b5ad28d9d6e12bf5f1a", + "secret": "4f51cf2ec6cf6664e2857e3885fa87e6ebf45bd04b32118a39a753fe372e958db358ac35ad12205432b3a1b83882f9da118f278cf86807991a7b786f7152dc0ac9e689968c90bdeff8e1ffd58d47b856a8a809549947feb104e63be4f9c8f701d2bd5b985e6cb1e566a6013bd19b4b2f08fdfcaf50e171c4c9c921a75ac123dd65dbbf54e751944e0623b6f4a7fe86c8d4e480e044f43eb38364e6002cdb0ea43fc93bb33c0c9c433ac76732f6bd7e184084ebaaeeab0b5ad28d9d6e12bf5f1a" + }, + { + "public": "ccb3c1079e9f7b517d29c27f560801e2007fdf2b8a35cced0375de52ac1dde9819272b1f84ae4879614b64239bbbb7760925d14a347954d3284ace475328e7e023ead8857a5e1627a9bf1651e5da35f426a6d16b4775805abb409d8c7e6de35979f52dbee37675af7dffa13faa4491a23cf3dd474fa928f00c598d8690353625", + "secret": "3b6bb2d508c988f61b1b743cea6969c9c1f23ceeba9aedd2617851d9ad20787e60dee214d99ab5fd11fce637ab11bdc11b56329c7e73d5deb4dcb092ddaf0fea19272b1f84ae4879614b64239bbbb7760925d14a347954d3284ace475328e7e01f2b9e032bda94020508164c01fb24a4714bda1f1546c53ebd1c49d0fcf059ee26c9951fb5fc997b7f45935bbe56d0744ce7282fc6aa464069b05c87c666a09d79f52dbee37675af7dffa13faa4491a23cf3dd474fa928f00c598d8690353625" + }, + { + "public": "10a0939a69ba567bb5a99b41a69848ba826bf9db5aaff49de0dd8c46df6ed0c15f743c2756c6e4b870292eb8011bfb576c7f99636579555509db058c802735ebd446b850fa02eb3c580ef81c6a0d7492d8a508eee684b717bb0f98aacd07fd848f94c908957ccf1255c5c3281f691ef607c7d3b0682da72631ff634cad6a4d54", + "secret": "f49945321a84e6025bb39376e214831466f39fe18c215d839a7f49b52659ee48a359c309f03f0a53195cdcf7753a25777c5d0d060ce1cd06c33e89184818554c5f743c2756c6e4b870292eb8011bfb576c7f99636579555509db058c802735eb6d97701bd5b688375a9c5243f9eaec990a784ec3959b3305f2c11fe25658c746c91677a8d0e45595dd739b0cef8aa45c01a72d7cbd093dbc43cc2648a119a01a8f94c908957ccf1255c5c3281f691ef607c7d3b0682da72631ff634cad6a4d54" + }, + { + "public": "7a6fd379a0facfd1581c9804247d3a2edbce73baa45366e3c88d9d86a657d764d6ce47eebee056e75bafb0299b71fb702198970f4ee143bb712948ea5ed4131f7c39edf24a9f68d35783df2e4a9044e84aadf93a989b08feb0dc70c91a7d8002d4059f2d0a6e66ad84cdcc5489f2da0f1b4d7a965ed8b0b34129f21443ae3172", + "secret": "e5a8ae97aa4879a3d846babe662188abfdc92c5aab2fc1856c051e22d8d1ead264e28dfbbce68470488f7ea4659fa65cec295bd08529e4c549ed01ed36ba4357d6ce47eebee056e75bafb0299b71fb702198970f4ee143bb712948ea5ed4131f43f5c5455f7826c4ed76c4a45b95cb598c267f72acda4ba1cb26d1cbf30095a93a9040c45d9cd1e161d1da530b6702c4720d026417e8e63ca592911fe44c4207d4059f2d0a6e66ad84cdcc5489f2da0f1b4d7a965ed8b0b34129f21443ae3172" + }, + { + "public": "3e5e138ac9c37ff7cc718f0788a677ba3f8d7f1924ed0479d45e0544a50c7ab4da05cb0c2751be122df757f9dc8ab4962f468a4ff7c832f821d3b702d2eca50861bc725e3ebada7e34d1f26eff2c493080fe4c7caf50bf33f9ce4fe6f9f41a1c5cc312605c324ae51b2e7c90c567e4e95bc61633b9de3360696aaca87a240704", + "secret": "b18cf246090b72a7d32960f1d8eae184553d6379b0ebfa2c437ff5cf0d59a8932d23fe138c8dd5a482de162377185d8ba73cf32d3c2b12606925d48dbde7879dda05cb0c2751be122df757f9dc8ab4962f468a4ff7c832f821d3b702d2eca508329c5c56453c63f0823abf5cb88d5ead7a1c409103407c842d23a4aaa1752d396b7cb8f7f3fa641c23845824e120cdd7780ce66e873910cde395d6e67c5b668d5cc312605c324ae51b2e7c90c567e4e95bc61633b9de3360696aaca87a240704" + }, + { + "public": "6b68a1da465ee3904bb4d360be48bf34315b64314ddd598165e1a771837d4543df048a50bbf6048deca50cf3c803d3f5a95cf37e0075538abd7b8eb8b66b13e6388e65b7a0644ac65b696e7596266acf0e7d807f3db8234b9094708b7fb9b26ebf8c33d4229e69311d1ac34f151229ea6f44f58f8c3bee4c4719446fc51000df", + "secret": "13fcbeed2fbb1eea511b7286c39b8b041204362dd27d942a7930108e969ea6b12b8c7193a9e0b1da86a4bd35ad94700debbefb46412061371fb5549047b8982adf048a50bbf6048deca50cf3c803d3f5a95cf37e0075538abd7b8eb8b66b13e618891332949e4da9a093f2a5012e784c425b237e71f77873c6d8677884f0aae467067b841ef9e060806291da428d290e1074c207b691ba76ae1dab821a120efbbf8c33d4229e69311d1ac34f151229ea6f44f58f8c3bee4c4719446fc51000df" + }, + { + "public": "a6a68b394d81dbe70aaf4e8982d6e9c009d2af4553c4893b70c51d386f77315d0d85be743d6be5de764aac34229e678a72a78cedd89d3a294f81b135d6ee6be7a223cbd8adc56d0fb5107ae493040e8997f05b23bda8a0bb38bee86d2525ceea8646a7f3c74fe1018e35d452c4171c1d74adf058bb09239432534bede5b5d7b2", + "secret": "e350767e8ae42789c0e5dde678956ac7cf0ed91bf6f018b9862e423e98aec0284eba0ec4eaf9fc2ddd93501c4681709ee183b73b0d834e2f05c92d8eae7574c40d85be743d6be5de764aac34229e678a72a78cedd89d3a294f81b135d6ee6be7479622ac6d5f06a8bccba3156cb3e36c12de65b8f8a78e44e6e84e06d7212344dbc2dce3e07510f6bd5fac3af7777d015bc6ff038c646ce284c455359800f87e8646a7f3c74fe1018e35d452c4171c1d74adf058bb09239432534bede5b5d7b2" + }, + { + "public": "2563b2a2bef4ac7e393aa5942738868515f9778faa58b28c3e227e3b239b1c43f091c0e22599b746ae17e19a9f7989cebfdf9b3f58a1a592ef992bf6dba366a88aa8d632e684f66048a1d5952f42ec7989495be1937727dd1b8f327103495b2c92fb9207da4c03f2f632564125709672de2d4c94c5b72a5f6694fddf7eeb6eac", + "secret": "5d540c71ad4a8abb5799abf1c467db2ce4a738b611a2c0c1e2be025a7b1171d945e8504e90446a6ac9c8a012fbdd84520458f7dca1e82ebb415664d7e9b08f88f091c0e22599b746ae17e19a9f7989cebfdf9b3f58a1a592ef992bf6dba366a8565d240f5e36c2cb26c731940c22eb7bd274d22f9b96deb7e753f745b266240b8ba05619d4562464f08d7c46b2e9fa327bf7b0977c5bf18bc1d0818b86a3230792fb9207da4c03f2f632564125709672de2d4c94c5b72a5f6694fddf7eeb6eac" + }, + { + "public": "803116bce228a882f65a75a7ab56b95383c9970752cdd84e8d28f7963d83e67a60db0fff3ff97e64f3c392566bbbdba3bf80491bb24e444a0034a245d6f9351165c1fa7aada55ed3b1a29efe1c44c86030bfcf2bbbfa9911867b97c2ad1d078152574dc329afc4ee7b8e8583025af712146ff04935b4993c70fe58a6d6b64ad5", + "secret": "a009da1b430b7e3310ecdba614940434fbd15705d209d610655d4a9036726555a03dbf42ef9db8fb9dc753990a29aa1c6179c4c09bb54a4ea8774845ffecca7d60db0fff3ff97e64f3c392566bbbdba3bf80491bb24e444a0034a245d6f93511763aa871604fb5bd5d327e031a999685a13629ec966c950355703cf07de7ba4d88ad5f43d4982be3e94c7da278aaa6455ed7c233b0d3fd75652909ac3600703552574dc329afc4ee7b8e8583025af712146ff04935b4993c70fe58a6d6b64ad5" + }, + { + "public": "f04196493328783711722a23a01ff7c3395c2a2eaca5f89d3c970349d7c4ec52811e329cd87294ce563a08f3f5e0f0c9fd29061a60d68ec27b7ab854cf7aa59fa422a103f1be6b615caa0b5993da1bf4c76144abde1bfc4cb916c3c2cc1afd9f3567d3ffa2dc978c336315c7a9816ac63d77738fbbeccdcc42e7accbd77d4155", + "secret": "b2ed5db2062facb13ef141a06d85c13d484b5880afdbb88e73fef53481522e2ab9605f52714b90312d6413612f6d40556adc2c68f0c83b4e600dc01b9087d3d9811e329cd87294ce563a08f3f5e0f0c9fd29061a60d68ec27b7ab854cf7aa59f7ed0d06ae4278402df6d7bc04a2cb044c4c271cc2fbdfe4ceee98ea109ab5591c520e9a8c58450691d752d27cf2ccf7c56379647228d9d6e5b38ba0cf861f8603567d3ffa2dc978c336315c7a9816ac63d77738fbbeccdcc42e7accbd77d4155" + }, + { + "public": "ff36aadb162a7402afbfebab44f6c7497565de3a76a1d127a1fa90607b96b62a831964891adb7e17301c261ac84b4d100fa3f2bb8f028e354c09a1ca172279e6ba4d05eead74287996a8ed71cbea760e7dcd85cbb7a962749302c60dd1f12c9d21c1fe92fea22817e8ccd0a1f84517d82a3643716a8c45a3b2f95668e31f53ea", + "secret": "9f04e76f4f0bbe11455b113aed96542e3b7bc9c61b14e8b875f5890e2932d13097cf11120c521258c4c172b1d2d6013ccbffd41e29a1f1461c326db9e053bdc9831964891adb7e17301c261ac84b4d100fa3f2bb8f028e354c09a1ca172279e61b2b8e2cf6e2a37bf0768bde5242c19d8855f0e5ae73a7f58d9a2559c3ad05b76578da8c96ad496751b0156bc06d9e91c8fd409610ebdc372d99797e06f485a821c1fe92fea22817e8ccd0a1f84517d82a3643716a8c45a3b2f95668e31f53ea" + }, + { + "public": "21e3bece4d5705c187ff39cd537f3bfa57122973a79cf7615b7dc4c6e2dfd7f150df7d53e2ab0f8e9331cfc9f1ca211011e9bb02f8b082e4a70e45e1adc7d543b3f629814460ad7bbb7a1845e48c70349639f6bb647b66fa9d9548326f8092a7501fc18dbcfc8730b4e57249ea5bb92312309d36b9300a1a105dc2fb1c8b4879", + "secret": "39d71144c0500b9543fe9696fe07cfcdcde4178a11907ab24c929f6b88d4647fd515331edaf90e23b00d45d5a085adaf96760424818846cb2b6362c46aae873250df7d53e2ab0f8e9331cfc9f1ca211011e9bb02f8b082e4a70e45e1adc7d54396c6821f9fab777d195f6dcce99ab1278441044f632bef5829d815cfa3698f86526884c2741ef3cb8483eaa7245eed8182d5248288b96e3ccd6b70e8a1c86b46501fc18dbcfc8730b4e57249ea5bb92312309d36b9300a1a105dc2fb1c8b4879" + }, + { + "public": "baf10c4fb4aa517edc0af73671e5c8b7d232cfdf5529ae3be0ac866274c3f61a059f3df5793ae6dec2560d33ae19e2df1e5b3ab1c1aa4a6f42ee217e9483ec436184e5e63e0548e15d32e089e831fdd1465c4c9256f456f9325debfb05e37f24f209dc889e7173c5c310210290f22dd688d2c395ab7ee1a93078500dbc28121d", + "secret": "2a7d0347b612451d11fd101e18746456e6658eb5bc59b85f2a20699e953527af6a90c2712b36527f211578e22859e54aa12f305ca14faff19028ae208f29ac34059f3df5793ae6dec2560d33ae19e2df1e5b3ab1c1aa4a6f42ee217e9483ec43ae4172f80291f0391ef00826d8e1e549ad83645840c060700c5e2dd8c7eaedfb676f8171db0088afbba8b67586a8ee859e38d19d1fe4d7015ee0aedc9f0e35a2f209dc889e7173c5c310210290f22dd688d2c395ab7ee1a93078500dbc28121d" + }, + { + "public": "71da7322bd1392f93721d7499c02ceb1f64fbbeee1792dd2c831f3d486c58308d8ef1cbcb1c2983dcbc7ddfd96a79a6867937a3170cdf3ec74408940173b7ba06d62260077f772af15f7090fa43d3cb697b09a0b8f8abf1b419c0acc60736424e4df3910fcc3f6b6418de8d6291828bcf03aab18f05163660e8fd18668b9ef6e", + "secret": "9f0440ee8cfde94a3d4788fb908a2da4616b79cdd9e5e0051223d07b0d5bed236ddfb99c2c684adb190a1c81698c19c7c77ca41b9cd889859edf679ae6c12860d8ef1cbcb1c2983dcbc7ddfd96a79a6867937a3170cdf3ec74408940173b7ba05ee0a585b0555cd3d588e144ea0e124667b96b37ded127f001f09c8244534d3b20a6c2da29b9c2d06aeaaa59b2bcb45d82048349931ef5ed5a2a4516bdb4da53e4df3910fcc3f6b6418de8d6291828bcf03aab18f05163660e8fd18668b9ef6e" + }, + { + "public": "f228e252e684e97d4ea81343456a0a02cd78ae761f6290866d335ed5d5457446c5fcaaebd059de11817b6049daaabf557637525d6831654f4d2e461c38ff4db83511a310ef62d97da5e6e477fea919d84537fd9cc39ae3eb0609914e11427a0f386003b6633a1138f7ef4ff9f344ac7774942da5175602e949240b822a6efa75", + "secret": "52d5c01f22357005bc4761fe436f4f7c451b39f4b3ae9bc824350e3a61de45d9acf1a9c30fc7e01ab57bcaeb5758b1bf41f15aa70a9a6501da83054692515627c5fcaaebd059de11817b6049daaabf557637525d6831654f4d2e461c38ff4db8f9f222790d3908a630e227f803e5d111fc891d45e0fb77a98d001aff67a9f100099f1001de7f40dc6a0d695dad1712e2b94301d768e52b84bed4d8bae7f62eda386003b6633a1138f7ef4ff9f344ac7774942da5175602e949240b822a6efa75" + }, + { + "public": "e38ee8603b4e6ae6314049470301dda05ab1c05f30a88076493e786e2cea0ec35d9de2be305beed10b502b801e89419fc19e25256598e6372dd29e6019dec5e86e675d3d8b1d58100f97474546c3ce282771cc4c07eb3c8b738cccd32d879c844bb7203370a849e6434b4b090794d3994e8edbd7cf1799aff53cbc7f2b58a602", + "secret": "18cefa7a98c6e49be1110df10515366c76fe6e1fba80253da26faab7e7f4273966129046dbdac4c140651973e6c3caf40981d0d13fe10fad44d8ede9faac1f095d9de2be305beed10b502b801e89419fc19e25256598e6372dd29e6019dec5e89dfcdfc92daba79849a7c406a81003aa07b6b94aed4a288a63941d8c62adb1a93672ced60679000256b1278ea4fb023b3353420aac136442af101a76de2f33924bb7203370a849e6434b4b090794d3994e8edbd7cf1799aff53cbc7f2b58a602" + }, + { + "public": "35b291d6aa229ee5d5358f1075ee8ac68ea2201c9e77590a942a35666d573051ac72a54703d96763827d37f47b83da9a406070ce5dc9c9e5bc651e270deddf8f0d5a01b232671f555f5c410d52af0a774d73e71e36404601a5eac03f473bb2c9deecec980be496b499fadff1c6808952ba9fbe87bbf25c23a78c1ceafdc82b26", + "secret": "133a621ece1151c9b9ec5fc662a2b3076251ffc325b7dd6a738168af196f5bd787995c1f6db1b84408a14c9f25e7ab5021b1e45db33be1273d7254dcfb887355ac72a54703d96763827d37f47b83da9a406070ce5dc9c9e5bc651e270deddf8ff1d1b9152a0ee256d8f5a4bbfd026a8e7534240d27825e30a9a3f66daf154d40e5700ca0535da1cbce2b18e42a55fb0095a0e75042a10db3b0592eb04b8c1178deecec980be496b499fadff1c6808952ba9fbe87bbf25c23a78c1ceafdc82b26" + }, + { + "public": "f64febf30c656b0945e4a66cd8cd413190fa5f9418fed8469c07233f2a0767a254428e40e4d55e2c8d88e342bd9acebd4e658f4c9c156006f6ed37dbec1a4485a4fa8ca1e4f0e20b8e2c88d2afd4c57f46080c5a68fda4b50a1d250c5c638c9b4bf481aa9b409b035c7ffa989508a94a2e3e39191e149cdf6a088eb1f73a8a4b", + "secret": "f024678625195822f9ed29a297966f524d835ce05ccd89a446cf9d0db88b4d847af24828b7164b94a68a65912499f99b97fc25e6b3d8cfbaf6aa91c04cee886654428e40e4d55e2c8d88e342bd9acebd4e658f4c9c156006f6ed37dbec1a44854c80bbd9b8855baa38e6292f9e8cd7dd359de18bc259af30eca91b975761922a4e06d6697b67b538aa27d4ca162147d19280da74c53520d211fb9960ba1d8c1b4bf481aa9b409b035c7ffa989508a94a2e3e39191e149cdf6a088eb1f73a8a4b" + }, + { + "public": "1ecc6a6346596e4f438c427522800aef2d9e1dbeb45d2e6badc4e7405368fc3b967e2832dbee0e7eb4821c832eaa52cb6694df9dfd1f1d76b247f932accecb64ba2c70918bf5c28b2279fd352c99f06d4fa0baee5c8ae24c039c269b026ede8f690f825eb41e64e172e48ffb4d240b99131c66fd006e82887307960141071d96", + "secret": "a40cbbaa5f8fcf792b2367db42f402102d0121acaf5d9b41d41b5cfdc69b70f3d400b37dd71bb3ff2ac801ece7d976255412c9afe970fce5b29702fc6ae803c7967e2832dbee0e7eb4821c832eaa52cb6694df9dfd1f1d76b247f932accecb64e8da44ca110f7254a5165c32fde562c65e3e6b2c37182d5f73c998e50a72ac9ab95bc6ab1f4f95089fa1b5378d13525da0514cad7aa108a18ec5d43108524997690f825eb41e64e172e48ffb4d240b99131c66fd006e82887307960141071d96" + }, + { + "public": "f26f48ad621060b321054e29346f795e0d39aff92156f766d59b284b1f0921674d4ca35f8a9fe27aa467debbb4a55caac2788f3ef2a0976aaa44154dfe59478da1e4517a67b480269eb893f50632e727be8a59e9b66c490693cc67407ec1be269a08915eafdfc66e05a26aab7a1799a3a3befb53020babc061aa210bebbd93ff", + "secret": "d928ed03046ad36cc57f82cf98d1faff717e568f81172b4672d4936264be2e40f170c6875de3397f5cf428d96a07da192a3a23f724b609b787644e05954d07594d4ca35f8a9fe27aa467debbb4a55caac2788f3ef2a0976aaa44154dfe59478dbcc7fbd035fa6a69310f935abc7780ea29f4f420f3be7c919f62080679e2d21a6d294273c5c2033a9c70163cc3cc3295c0e9c648e5a2b2162a4ff16666abf9079a08915eafdfc66e05a26aab7a1799a3a3befb53020babc061aa210bebbd93ff" + }, + { + "public": "1e4ec591f7003feab7f374b024054a20d073e1ee710e7c1b638f7ec588bcfa8c81eac23411e4281414ae1ae0f44bfc3c7a33a4d2f6fb5bde2f6b6c188442f91a1f3f781d526b475ae61a52ca2d95c42bccaf949b4d008925a8e1ca81fa517c6caf19bbcb4bfb5b0048f69af5c44dcd7f4fb1fd0b5935ce89f79ac4cb629a822e", + "secret": "441515479e88b62b302f97dbaa17cddff02a16ae2b148731dd67cdbe093f7e9d13575a75416fffcaa66287f3a6827659a8b9972ec761c6fca44cb67a8869b16481eac23411e4281414ae1ae0f44bfc3c7a33a4d2f6fb5bde2f6b6c188442f91a020e3af6523ee81bc1695e9198e5120c5ae99a8581b845af2100797f3a5cefb808d0e25066d6e4d1b4caf7b420694635b616641d0887fd826d1f99c5546573e1af19bbcb4bfb5b0048f69af5c44dcd7f4fb1fd0b5935ce89f79ac4cb629a822e" + }, + { + "public": "a03d1a6a4ee241c5ab392f05b0763db30416e6a6a99b754b426ca9c33b770a10fcdf88547464ea80da78ea0a76aea607395b26b51fbd2d048a995ac5b86a97877100f78e80e5d1c03f471449debb75a32374b676da34830e776fe2d8ba27f700c16bef347ef56c0998663919fd8a8e3f89376288fdb1dcb0e1f92a56073965c1", + "secret": "adaa9b5ee978daee505544ad70fef1da78ec67961fa10b19947b4b33ae89fcfa1cc70189586b30f8d46b27cea5c59662ee5035563cf7eaaa2b04b4bcf21d8da0fcdf88547464ea80da78ea0a76aea607395b26b51fbd2d048a995ac5b86a9787145af2506f73969c7a56e6a43b5b714ca6d0ac832217d61439b891baf0ceae4209a922d3d30eef4edc0b695adb738b713eca583dcd5890a006857b451e3762e3c16bef347ef56c0998663919fd8a8e3f89376288fdb1dcb0e1f92a56073965c1" + }, + { + "public": "c6e217eebb6159f8eb1ce7fcc672015315a241f30e01b8c847ecb726adae2c30de503a1dcb08a176cea5bdf36e5da986aa9e298e8c1e91737935f5bfe4b72f61c6b820801988103f0c0cffad736fcee76e859138126131d3c6bbfa7f240ccbe404d5412324e3ac2853f0bc49b11f36dd7d97e1c8a3112a7f4defc9146afb791b", + "secret": "1aa4a860a3d8e7f603445f29a0d9e92686241ea211159f71c0bc8d2077097e6bfff002aa8a39ff0e1bd554ed06ea0008c0ff0439ed946fd72f78ca890832fa7fde503a1dcb08a176cea5bdf36e5da986aa9e298e8c1e91737935f5bfe4b72f612391bde8ca0d0f9e86cd420f41c4fced1b75bd149c637e3877766ec0ae4cd18d6a7a7d88e10811fe6f2d1a15103865cf086122a0ed4290934eee4b4a206dce2c04d5412324e3ac2853f0bc49b11f36dd7d97e1c8a3112a7f4defc9146afb791b" + }, + { + "public": "1b373257f3fc76997167bd727449432e93bf1af9813e7e0c5ff1142f35107e07ec89c28e19684511b964f2027bafd362d038ff78bedc487b37c70e87d67cdfa4ec2ecc17a0b1f4b6273723482933897f4bac1576ca11612b35de4e3191253f51b5d2c68f1a871e29e39b41bca7173daa8472de7c69967c3039d95ea94995cab1", + "secret": "77b2679f6b381e97cd9ac05a11c8cc238ffee12ae3b0572d46d5a4d4c20e6aa2df931959a61196d4e4218e79c9852e754254831770be8531cae92f962eb45683ec89c28e19684511b964f2027bafd362d038ff78bedc487b37c70e87d67cdfa45fde1d659b0f60722c16508283fbc6503b96ee008970d127cfee6d1238eab262f815d8630ca3a3990695c541b23ed90341b42b1e227a39434062968b0dcad83eb5d2c68f1a871e29e39b41bca7173daa8472de7c69967c3039d95ea94995cab1" + }, + { + "public": "21ca77ebe9d0a91e3f40cec4b8819b9acdb1456ebf4f24c9589b85f754b6361c1045e968034f0766d3cb11bba0b75c8919223fe75e02b57e365a67f0de3991cb44b2095fedfa1d9fb3b854f7043231b2e7552905cdf0b306564ce92769ba12a4d5b2d9a655019006f8cc3c352c007bd2c9aae107447145c5d6f5e1ed3ac8f163", + "secret": "c32f5a6f5e57fbfd8a8a66090c6abfe5da0406c7442cedb34618b76455c428a2495b7fcc26d6733b09487a671558f974cdecc84f29afb53f2420493753bbe0341045e968034f0766d3cb11bba0b75c8919223fe75e02b57e365a67f0de3991cb09471d34c93d57308b4d492e9de38dfcdeb7285393bc8f007189057b1afc8de6e7c0ba657b3f62612d450289d0af2f5c3af699327b6f081ad4bddf1b3feae479d5b2d9a655019006f8cc3c352c007bd2c9aae107447145c5d6f5e1ed3ac8f163" + }, + { + "public": "8b23bad9384976de850cd2ba2cec29f918b75e30512ee8e626b38611b7c43bad625ded0ae4fd9d83b1d100a25b17c5f46dcf9e1a41b9a11bb572174daacd70aa903387d8ef9db4e7e82f95eddec209f44e0b2f9b07563a5bfb75be4c0f8284f179c7a29ef1b13606650d8ecb5644488e2877e0abda678cf70b0ba25b47e18b5b", + "secret": "f652c932339c9fcf723c64a4475bd6e4fbc21464b8a0e1de611d47c68596b8c717629d5894ac8d0da7b79b0167cfe015f271118d46029c992e4d7255c2559bf4625ded0ae4fd9d83b1d100a25b17c5f46dcf9e1a41b9a11bb572174daacd70aa1a680ef15a8f74533d7556a18dd65c6516c20a2cb62f13e9dbca5c35bcdaf247550a58599a7be9d6196614006c7287b77c58b4791edf1ece4b651728476c6ad179c7a29ef1b13606650d8ecb5644488e2877e0abda678cf70b0ba25b47e18b5b" + }, + { + "public": "48af843c6bb121684f6d8a1bf30782a4c63ecf00f88ff28ea0ec3388e7a87822a0b8c58d99f0cb27c98250f9d8d74f4efe3300390af072c7c94ab0ffeba93783e8ea1483e48f179f9b420db26605ce94431d37e1184f899439ec9315e753985b50b3c7da116fd30c634e12f20d11f16b1e1a13cdc8f380f7b95ad7d20cab1278", + "secret": "33f70458e79c7693232afbd56970efacf6e1df042b8ef2c76c7ef3471dea1be0598b9e04eb6f7beed13b9f2129c3002c7d827f5e5077898fb67efcb88f84780ea0b8c58d99f0cb27c98250f9d8d74f4efe3300390af072c7c94ab0ffeba93783db5fb7a405e03a07f64a775e52df801d23814de9e5e9fd98092c56920d61f5ed96705761ced6ae34fcab6a2279e25d7a1a6af3944db8a47572713941c3c3437150b3c7da116fd30c634e12f20d11f16b1e1a13cdc8f380f7b95ad7d20cab1278" + }, + { + "public": "571452e50043af12446a09098ab7fa009afc20b1ad5f66772e35bff3ab297fdff4408c83c6436031a426ef6eae6fc43854418a9199fd6ee76ef9e2ba2af7464d6b8494a33e22c4d3df121d45dff213e4e277ef438475e51e28e52a148506e1c2228889f5ae2cc55a52d13c4e956e8464699eeb4a263195256eab9c4dd2889ea7", + "secret": "02453700785c9d8d8b3d3e817d79f514225ccccc5b5b38c15a81cf0eb1ec35eb4e439cbe23f7732f44999b9e752a94571d2cb8fcb0297dfe785dcbe52eb4296bf4408c83c6436031a426ef6eae6fc43854418a9199fd6ee76ef9e2ba2af7464df11ad241f3f7f45bcbe404c9837cf41239f1b348d17933aca2d496f2303c512f79c382e97e456b673406ce5202c09069ddc4388005883fa71488ee6120ce53cb228889f5ae2cc55a52d13c4e956e8464699eeb4a263195256eab9c4dd2889ea7" + }, + { + "public": "381d83c0527c079e90a7e4c51831c0c55c614d8310df3c6553d5584975f69e9e6aeedaa35954a442f3b27a05296582eccc71c9012bcfe10f19e2adf429e7d5b8215a71b9908e8f4cc76dd01621ee867ff9582030a8176e5dc4945d2727502be531e278861d30fb35c788b52d90db73a4a6b2a98a84f9ece6d566faa46d34dd36", + "secret": "dc2d2cc455a93f516b74c137875a0062f2bc6b459bb3abe67ee8ec70569e3b926c149b274447d95a42a6e7459a5404ee8f876791e03f027e96e63afd5b385fa56aeedaa35954a442f3b27a05296582eccc71c9012bcfe10f19e2adf429e7d5b8cb0d01113da272b3456909b76b1f5e11a00c296ae3a4e9e096d3b38c35ee2f3e42d33a553086adc327b95c17549ea1ab8f5c5bfbecc3e6bf930b382352cfb30531e278861d30fb35c788b52d90db73a4a6b2a98a84f9ece6d566faa46d34dd36" + }, + { + "public": "e5fe3d6bb2970f09972a2adc43aafd83750123effead4610dd941adf181c9c83aefe40a5c8b494aa01ae23a0caa40618841f486242dd0dfbc9d11a8809603996fb62a9778cf4b11571e4b10c798f2de56a629c32d20bb283bbe20968e4bbe504462e8121e56b8ef7de7f068fe82b852acd149e4e3a9502d804ca52108616b83e", + "secret": "d111c1f1d6bb95e18b4567cd9e8e89a4e9352af1fb5a91a00c8558f4cebe714afd86086638d8cde0167d19cad69c838011b74abbb50accba30603668b7218ed1aefe40a5c8b494aa01ae23a0caa40618841f486242dd0dfbc9d11a8809603996785a3f4641e17f357e3092ce2fc1b2f21b77b4ba2bd3e04a5164b8c64ca3f8fd7bdae5f81c5a6ea65d9271efa5e4dce845a125e0efeb8f9aac9b71650ee31d36462e8121e56b8ef7de7f068fe82b852acd149e4e3a9502d804ca52108616b83e" + }, + { + "public": "c7fd81fe3b2fb592a2df59ad90e6bbc35023f9574be8fb9fd85f6a6230d5ec95166fa29a38531a011764ca998e731afcca93b8387ccd3a34175bca956d26c7c919e883fa11363340535ae347f43886068288a81c79f07926dc06764e4dfe966fb57b8d776e27a52c51b89f206cc7dced846a0721907c6e4b5d32f3cee4647a78", + "secret": "aff01b985bfce8dfe0d5b34aa0f76c2632a8dfacac1888f8638a647c10dde1760567e20bab411caa8dfd0be5910027013ecc294c481a8557a288e55b4be9e452166fa29a38531a011764ca998e731afcca93b8387ccd3a34175bca956d26c7c9d643b4680241a2b7ae5d1447619ad04eee826c770698d4d514699418fcdacc96023cc310b152bad374dbaab9794197618d448f7e32655b9621a4cfecbc452d92b57b8d776e27a52c51b89f206cc7dced846a0721907c6e4b5d32f3cee4647a78" + }, + { + "public": "11b7e285f7c3b649434180882123d6c7580beb0e28aee185188adfe389e89a391dbdd1e10fb94bec55b461bca947ba00a3d2f5057c684222b568b6270f68b626b78b94e1610fa7311f8e0079d034d5c9f1fb5ff5b60b4245ceb36616520174ec3d6e324b462db8f5ac97fab3c14c16f0b9415ee1b518d344420239e4fa521a59", + "secret": "05b58fcd30e6adfca19e09d6793e5e85852da3ee03aee8144f33b05777957d566be366ea0d117c48545bbcf47e3c18b593b86e3dba98d9f425ca882fb48963b01dbdd1e10fb94bec55b461bca947ba00a3d2f5057c684222b568b6270f68b6266dabe85760b9017c874dbeaa2b23a2f140133499b07f5557d8c06c5ad9bdc553fa5b2bfb747f517d5391be171f42de59bfd02273f0a76052c597d4fa1f04b67a3d6e324b462db8f5ac97fab3c14c16f0b9415ee1b518d344420239e4fa521a59" + }, + { + "public": "f133feb6e1db15ed4cc89dcba8f73f9c7f956e13d07406eb96b1598f7cb613e1511e7e6f6014f68c5f7fa4fcae45c93ac4ee246e037660500ee0aa216261761586f2fdd25a094126d3c489d65eb15d1921abf7249b3850a2b30cdfb00157a1bd15aae3e2fa8a8e0d00312a95a41834161a7f3462b62bfec19766dd0e735d413e", + "secret": "50ac5169235e3044d457757e3f75cf448263e153ea046322a5e5d5614be02b8373d28fec8286e2a473ae4fb024470fd73b7ff2d0863bcf93753c8fecea7da94e511e7e6f6014f68c5f7fa4fcae45c93ac4ee246e037660500ee0aa21626176159aaa015c4d67d60b08d50efba795d82832582814783c8f5e6251b7264f6ecbc3a5cfffaa0e4be626d5d98d70f6810106cbcb7223c1b58f8afdffd82112ec80af15aae3e2fa8a8e0d00312a95a41834161a7f3462b62bfec19766dd0e735d413e" + }, + { + "public": "81925a0d416195599a6123cd31198951038b7b12882055362f8dd8ba4210de1ba2595878f57efe63afd06c62e816f1480b0ce04e1a9957f2692409d4b05c9626ba05204cd09e90e34b8b042345b1b06cdccb2f125d7cde0517d9c3a1ea470cdd2d07f36fa6bc0a0f47d3d652c815927d97aad285c276112195e24b06f7a4bef2", + "secret": "ba8510769bf4ccd538b4fb8864daf1a1fc0b02fa7717b43f3bc122535ce60faf556d768d1a3214be52d0b03f105f6302389c55ab5d6c331c4407415bf494b4aba2595878f57efe63afd06c62e816f1480b0ce04e1a9957f2692409d4b05c962618e3a10e5cbc11dd383ee32be95423a07358edcf98e1e3e113225582fa0b68bf1aa540e8ecf8a06efd076c11cb72d8450e7b2d88a86c146ea2b5acd741cdfe682d07f36fa6bc0a0f47d3d652c815927d97aad285c276112195e24b06f7a4bef2" + }, + { + "public": "b495e69c86331c46ea46a0a21b97aad933f9399e73dd794284e5074899328d07bb52077b0b3145f954a959edfdf5359c8b4d90dafd77cfc59f7910237de5ad889503e7a0f8723179840237b439124688741a3d38730845eb180f62d6deca8dba351171079b97001c2296137e7943008fa83f576e8d85fec836f6d1eaeaaa982f", + "secret": "ab3b797d235faf3c9ff5686ac65ff0e8d85c11bd8c05c0d88a64c21b6ee3264e90e43031f4ae235e4fee93189655e4670489f11c77cbab1bd32753cceb647133bb52077b0b3145f954a959edfdf5359c8b4d90dafd77cfc59f7910237de5ad88016e9e41e21c5efad887ead459feb367a3a0e280b63aa75f4caa2de28c45907337bfd8cc609fd0a1d75fb4b5d51bae30606da8b8dfa3e0fdea3338cfd125ab81351171079b97001c2296137e7943008fa83f576e8d85fec836f6d1eaeaaa982f" + }, + { + "public": "d6c6d8f975950aad82cb0bbcd8bba5193b510e3a6af37f559c98eb666d3416283944182f665d6f83999bcedf9f94ae7bb599bcabfc3237217d3e5d0153e52ccc343973ac2d6f24873a3a76d839ca98726ab088fba860d18c1c22bceaf08fc69764ca7e74e68bb147663983f35bf29382d42a71298a6e393208d57f99f22c76f6", + "secret": "235f3d580282cccd29e582b44ded94a89a52d5d686d345f2d3424b69e2a170c8b9f5966c9ee69e221e06a3021eaf6908311abb83408fda36a65dfe223db813a03944182f665d6f83999bcedf9f94ae7bb599bcabfc3237217d3e5d0153e52cccb5602cc45f6fe314aced5bce704d070a117f2fad75f8b9995d3030d357974c39031d0acdcffccd8506c91910d3eecf4360c7d5885c78dcb9cdffb9e4f431eb1264ca7e74e68bb147663983f35bf29382d42a71298a6e393208d57f99f22c76f6" + }, + { + "public": "6149f72b82566c2fdd48f2e9177604a9098b60b20c3b3e5a364d10f733611aa8bdaf12f9fc885141215441c701588814a565590b384b361c097bd0d8880dd045bc23537a35f85c8a5355c9a55c691424738809d2830f4a9bf624b06cb6d75cbc1fdc8547dee693072d4ee95c7a756f8c309c02e9c75a407a86d674d463f4ebb1", + "secret": "7c1068c543b22e66a00b25b3b04e28811bd95352ae314793d3b5535764d8024ce6b75fa1748a12e1b576b7a45bc075523929b5806d2c5566186e02b4d512a34fbdaf12f9fc885141215441c701588814a565590b384b361c097bd0d8880dd045b4af0fe36422fd029b88b9baede3aae363d9467ebb931c03fb38093909d39a37ef2499ff421a50ad1e52556e2f20a96eef0cab8d8bc8c0ca8f17e86ad0f26c3c1fdc8547dee693072d4ee95c7a756f8c309c02e9c75a407a86d674d463f4ebb1" + }, + { + "public": "9c30062855d4593fadc327e951dbf833a7b00ea4da381343f3977a05d7877478a932b905d761f2ecd21caf2c4bb246db49342bcfd201e6dfce93c5c0b90c38b659c9747a8ad70b5f81a16b264cba9a786c21a012e431ce8f700bcce1fe21dcc4b343c813501bdbc0202c9fcd6e9a4644475acef905c86cf7e06c8ea59503d216", + "secret": "3c27465f0d1506e805ceeb8426b6d072b4c88f243e43036d26b03bc4a2043f80f4b057edb63e695642ed067aa08f0effd2fd35a3ccea9cacb3778b6a480883a7a932b905d761f2ecd21caf2c4bb246db49342bcfd201e6dfce93c5c0b90c38b669b379629fd2fc76fc485710f4cf2ddde3b289b8f334e50939c525a8b38f735cd32763e4d44daf9dc361652e4e86363ee3f8644bdd872ad57761527d98f57c20b343c813501bdbc0202c9fcd6e9a4644475acef905c86cf7e06c8ea59503d216" + }, + { + "public": "0c5f5d35ac7add164f3d2f40f796041e664bd51bc43dc6a2722bdb909c8917a13bcfd5f6ce089b87cafa45074dfbe84aeed15275742e30a1ab4febdc1fed33e19316feb8421c0cd3d2df88d2ca119d785aab5dc8151069f4a14c5675334dcbebfcd69464d3bb52a03221a0e91528e2ae7bc9e57a7214949e28b8d50876c72674", + "secret": "a581e0ce91d58b87aa1c6af9045016d5dd191fc7c24d21d9a349ac594856daa7929e5b0bace9010566943502b4e6b687f2f8f00340faea1afc92f9cb29decbe23bcfd5f6ce089b87cafa45074dfbe84aeed15275742e30a1ab4febdc1fed33e1515dc8c7f8cad64dce4fd2ddadc1a2ed948ba5b4d9924e7381ce33ddfba3cc7703cc38ad93a03cdc412d263ceb7c015b66c35a5c1e77eced7b74a52400e68498fcd69464d3bb52a03221a0e91528e2ae7bc9e57a7214949e28b8d50876c72674" + }, + { + "public": "55c45d813c01e442ce4eb32160fd215163a9e2922f48b296b80d633ce6497e75d7acdc78b088a68c005e68ae769222598ee14cd9491a911867b900cd5d0aff8b96bda3b2c0e88b2d552ef79472856b569f5a85aedb8c7ce23ab7514c8cb9b3b5b4cfc38eac2b55442709e9e74d49e77b67f4881425bcb99c7c0b650c3717869d", + "secret": "87e533f4bf8e5e56c12553e9c7c46f39b9eb9f343fdc268a25864789ec2f192f6e1aa3248be74fef811d0320743958d9b7b31e77fc1b8df41e78d1012a742677d7acdc78b088a68c005e68ae769222598ee14cd9491a911867b900cd5d0aff8b3a7c2e41aa98e4988c911fa47c96e9775b3a3ab1a5db44ea4b4340d3a619ec1b62414a40fb996187b70b645375d4ecfd37048e490d62000cf3927d0478e982b2b4cfc38eac2b55442709e9e74d49e77b67f4881425bcb99c7c0b650c3717869d" + }, + { + "public": "88a1f742132460917ba9d0de19f9295e77069e943d550fa5ffa57fd16b18c1e4395dfd57bc17b9dc06a1dbf6f4c1c16efc053ec7315b8d6bf43e16e70b5cac087e692a7375d762ebaa745928f02d5d1998318b05ab97d8333e65838d39280047d457c0ff9d4ea4a2856f7802d1455dc9fb23ad327d4fe0eaca5b6927cf1810f8", + "secret": "f8a7d73548184228df32dc2feffa80acb9033d0f66e7f0dee1331360265891e77eefb4c61da09a57c9454d430658fd617443be27c9c800664d1c6cbd24f492f3395dfd57bc17b9dc06a1dbf6f4c1c16efc053ec7315b8d6bf43e16e70b5cac08cab71fff809abd7725082c23e322c6c4a12aca38b087a9f980d1c766fec6fe8392bb773dba7769d60d3019005d4426031d2a15d57dec710fdb2cde6198b2f7fbd457c0ff9d4ea4a2856f7802d1455dc9fb23ad327d4fe0eaca5b6927cf1810f8" + }, + { + "public": "e5207a57729aac39d6267d9d91eef6a79936c49240ef83d1d6ce3e0de1d50448255a7e0f93f3ee688b001beb5c256e78c626bd8d88fc8d3c585cc3486556fca0406aba3ce2443919f1b71ae3ad9aec52158d20af41080b2b75fcfa9522ff2fbd38288f79e123558d68f6e3310cd70fa54d3754a88ba9c1e5f03eb376ddb79abd", + "secret": "bd66f4737497f954ab1ad19646f16ba6d4a05e299a3f39c030e5f367ebfa21b00068c0542f1f64c8aeb5a63302db87e4b59fb017d8fc08d6d8b84469ae0e2e63255a7e0f93f3ee688b001beb5c256e78c626bd8d88fc8d3c585cc3486556fca0d22852ee0ca55191520193cb94c3657651126b3589fcb42a3b7829a7184f3defe11b241a7df4ea18d7da4ed37066ba1e5e0f2844c875afdaad6ff087ced27c4538288f79e123558d68f6e3310cd70fa54d3754a88ba9c1e5f03eb376ddb79abd" + }, + { + "public": "801245cf79e93bc485379973107b9f474240e068c25fdbc8b24b1e2a7180bff77cd91e71532faf9f7610366e3818069052bcd0b692d29a690abbc06ad7178cdcacf68728938c699b07a776373a3cf197fcfb29956b51b8d401ea24c6f801c42ff6ab5098c32ec126958f3bacba07173ea19194254992dd6cb6e1e8328dc0ca2c", + "secret": "33e9da4401c1e3d7ffb1749b637b790e019bb3be897e2d4bb62d9ada4805cbca624566422e15cc21186813c3c20b2b756b3511a743964bd0811d2c159ba63fbb7cd91e71532faf9f7610366e3818069052bcd0b692d29a690abbc06ad7178cdc3b18a5fb81150e533d51cc2c3ab28fc323acd10a7b91bb3286a622b2150ff451d0f4aded65b2e1a2ff2007b93cbc338bceab9d6148c636e701866fb3b9affe78f6ab5098c32ec126958f3bacba07173ea19194254992dd6cb6e1e8328dc0ca2c" + }, + { + "public": "4758d50ce92d95d94c8e848ac0f825d96ace1462e596cb1a3e0259ea3271057defc21bc0cb245f0e4c8c65033ecfd05f1aaa0979aa283dba2a432ed3b80bd21172780dc515d8f36c341d0ff096ef5488dc90554c597e2c3c09bd5351813424b5327a8f3d9f2822d52ecb236e6aa2280877b1d2d473be862f00b55a4a53b399b5", + "secret": "bbb9a8e1de572adec63cf0f444e3185794eb6b01cc4df8bd2dd78e49141b796b28602a0313816053f41395d42af33de0fbd0019f18c632bdf07972fc7acb4a16efc21bc0cb245f0e4c8c65033ecfd05f1aaa0979aa283dba2a432ed3b80bd211ffa96a51f7a88441e1f96b1a79ae298ad272156f9be4cee317f36de4aa0eac1c9ae33554d03c2db8c075269a7c3c3232630f42cc5d9be06dcffb2b9de28e4d5e327a8f3d9f2822d52ecb236e6aa2280877b1d2d473be862f00b55a4a53b399b5" + }, + { + "public": "69c9bf46662b91fb5f812fb25816d538198260270d8c4abda4cdfe5d8f019fb1eab4a236847cbb8cd0c5c95d5bbd3c52193494cd0574c3fc8800e9d2ff1ebbb29cf06181b2d533dc7f63c4f464fb39f2725f781e5acac6cb420e77a493e3bc40cfb38a36b678faa1899b46693606f0676414bd3676daecc1ea80593d4b5d4b2c", + "secret": "4cd6a5c9c53ccad1de97241ad642eddad8f07ba12c8210c7fb75d0539511c0d449d013258256d6e3791d1649b96ba0591466eeb331878fbfd7440124a4b85223eab4a236847cbb8cd0c5c95d5bbd3c52193494cd0574c3fc8800e9d2ff1ebbb2074fcc3c5b93f2b0f402acaa14cad9fd0d1968f3eedaf0308f5604e451d698318353f1e0b5a26400a549e9a596fc3d4f55f2b5f6333f834cff017c5b72b17468cfb38a36b678faa1899b46693606f0676414bd3676daecc1ea80593d4b5d4b2c" + }, + { + "public": "4e7622665c1e827d2deb282fdb3a6a66c5a02bf68bf40f9eb2bc6fba7e8029b2040519f35dbc7ac0cfb70fa86f3348d41f9b94ea46c8620d162db7cef122f9bea160208b3454e05095b3711acde133b31c273aa5fa79a0efa17af2e8aea1ad59b070ca352094b51e5b46f921dec7f97a400298c1134cb01821b370810d5fd49b", + "secret": "0ebf0cf1bdff05fbdfec635a0038fd4a0d9eb198b33455cc9c5dc9d9e5f9faeed64cf14e6c0031ff9653991b988355106259349e1f1ae1621fc5f35d04f6ab44040519f35dbc7ac0cfb70fa86f3348d41f9b94ea46c8620d162db7cef122f9be5fed2c554322e89e006c407da0051ddf6c91dcd27abc8532b01cff3272707fb9e55f814499b58be2b13b318bdee849470cd4ece7d1781e7232c7ca15e3608828b070ca352094b51e5b46f921dec7f97a400298c1134cb01821b370810d5fd49b" + }, + { + "public": "2b1e8f23bdfe152de7004054a9112fb302779be26be695e9c4ca7bec1c96fad6dcba8a9f4416f19336d36c83e6aacdc61b55c53b5c4924bc87c3fcef22a3651b5937df6df3491728555bdeff5e309fb842d1168183f3057153b4b7e3d585d20aa9c7f6ff1cc517954c9aad9e48df8fd67cd6608e5061dd057c9ce670fb0be655", + "secret": "6ba06860415b581aea456e383ecc529db159ae3750bb2f5738afa7fded839e86c6dc7963f978fc8813a245614dc8791ff4237461fa490bc57cd7281d73748eacdcba8a9f4416f19336d36c83e6aacdc61b55c53b5c4924bc87c3fcef22a3651bdce6a089232ecc81b9b13206ab958081b1836c8dc6720d7bad0fe94166c99fc6f3a13f643f9fb278d2242d28d1bf0394abe4da5e4705cf4dd98d80afdb24f6d3a9c7f6ff1cc517954c9aad9e48df8fd67cd6608e5061dd057c9ce670fb0be655" + }, + { + "public": "644dae5e2aef883886309554d07d4409d56fa4071ea55273674a35e2ee8b642b833af1cb30ca77796199d7dbf215ef0415ce04bbd2748641f16f34f2fe44ee6d1cf00d649270a0642c7e2d244b80b9057c75575efed1d04e9def375e7b2e4455d60e4b209bfb1e0af94f3676de81c78c9159c6b8f92a938ccbbd8ded99ec91b9", + "secret": "13af589641360a792f05dea2e2ee7cae6e67fb63c26eaf6e5949554d396ab63d06bc19d58c819ff1b9f13c9121485dbe8ef1373cf41781e7db3415d7a8295876833af1cb30ca77796199d7dbf215ef0415ce04bbd2748641f16f34f2fe44ee6d68f3933c05bb774859c03745fc2a8926641aa151b3244baf55768506b04cd35ecfc66a484769b5d3e2e498ca72cfea5fe89cecff32d9c97aee22209d520329fcd60e4b209bfb1e0af94f3676de81c78c9159c6b8f92a938ccbbd8ded99ec91b9" + }, + { + "public": "63c7abc2ff04ae74eba7f799c2e58523eae91248d785a44d960e4cc4dfc3f557dfdd039db3808447a901041ef08b1c1a778cf787a6b2c0d59714de3cd40bbd427778b3173396a2bf867bc937c4ee6e2083ea2850aa131f9222420531d13501f01e90c868a0e1418e11811335d3347821f4ec68705a419f4539c3057210adac15", + "secret": "fe1cccf34217dd3f1ad40989dd759ceb4c39a3c900c8262d1c5a56bbbc9c6545d8bc2a6e1152a687aea3f766d61fddf0a33a5e8c9703b242a739d61f3e3a9d40dfdd039db3808447a901041ef08b1c1a778cf787a6b2c0d59714de3cd40bbd42865b86784834b75cd87f3d438dab6960d689085f3a022dce7922e6493eca70ffdaf2344fb09f04cb387da74d1ace8d5783f4effefa1e041acbd9e8f22da6582c1e90c868a0e1418e11811335d3347821f4ec68705a419f4539c3057210adac15" + }, + { + "public": "f903fa3a1d4eb1005cf0099fb8b03b79cafb97b0133cb87b77eca900a354cf14378716f70da45149a0610c0f0510bc67394f81a9073e75911b0dff5a9f5974c616d478924e0c714a0250ecf553b567658950792446ab679d9164d7c5cb6eb0f7c64048769922c515a5864823d9c162ab691c03a564f6163b59a05c1a3f71dbfe", + "secret": "0c742ea118d32647e87bb6b8850847ac3ef85e60311169f09b8ef05d0aaf239776e31a5eff390d5b7ba065ff64f98934b28d598e7052bd36aad772c45aeaa6cb378716f70da45149a0610c0f0510bc67394f81a9073e75911b0dff5a9f5974c697a056c3ba8139cc93e351ed7e2e9083b56e0823eeae8c9c17015a25f0e1876d24c8d65963dcfbb5a9f03c7ad85633f6890cc8d2e68f5d9f94e0451576b257c6c64048769922c515a5864823d9c162ab691c03a564f6163b59a05c1a3f71dbfe" + }, + { + "public": "34f992dec97ba5c086a16d327550aff41d1b1ea8a5c71811d3d9b1510d96bbafcc72d604102c6c835c362a5b3f45f421ab6408060f7bed572ae030f478b0d80e21bbafd651e704bcb49673574cd414ff24ff34e0ccb7bcc8cd9e5471c178831bf62b4fd168bb31d474c825c31091bd640c5c0df9ee8445a87859ab74d68290e4", + "secret": "c1a006e2e4b672018b9566914a35b8e4c4844290a9a068bde0b47097b1e5e3f0459f6bed016876de2b0738f8a8e06a562bc636cbe224b126a736b098d30182cccc72d604102c6c835c362a5b3f45f421ab6408060f7bed572ae030f478b0d80efb5fddb7e4f72ff702e6d4edf4fb1632c3364d6e6f337fd8f2babc4a243ba354a36145df05d8e9c4f347ea5eb3c71d1356f254439d08927b2e33f8106afd13acf62b4fd168bb31d474c825c31091bd640c5c0df9ee8445a87859ab74d68290e4" + }, + { + "public": "9a89ca8443fe5cc964813efcca21540ac2bfa2bf05f90b9d974b971df8dd3c78d067b9941beab729f708bae1c788a636a131d8afab523577b13d6803b93905a2669570df28426ba86c084d9ff5f87c6377691f7fb592be7f4cdb56817a64899f5fc16c97ead7e24651b918ec9950316de46eec19f278063c3c93640057619120", + "secret": "1faca721681785ccd2476e795c70ab6f8a9057528f274c282d350bf665206dbbe095c365df16a707d6c2605cbae5d6a67d89980284f422fe8949cca1bd1eb2bed067b9941beab729f708bae1c788a636a131d8afab523577b13d6803b93905a2c07b0ee64948d6f17811b5ec9e9eab918d9685466da6fa4ead59abc6f7da46768c3685b4dcc434a6fb0694537617955339dfae5ff0887cf7e6c3b23653a3dc765fc16c97ead7e24651b918ec9950316de46eec19f278063c3c93640057619120" + }, + { + "public": "43e46baace1998bcc38abd07d522bfa50fd7ae830165aa4956344dbb8aea5069287ffb8af928a130d95d7116809c80463de8c2e5132b2cb28089f7ec4812f5bd00129cbc4f5dfe40f976fbf254b95cf6a80bee1932327f85a31b5cca12f11627968cd93dcdf024ca2f4b1d034bcf4a0ac4796190b65d737a7d13702f1187277c", + "secret": "70c56e8989610520d3445e731e4f9ca50658cf1b9af0100d19de6da0466d7771b4b00588af2ad63a01b502cd8b52dbbd423dbca55c0599e5473a33ad4867cc07287ffb8af928a130d95d7116809c80463de8c2e5132b2cb28089f7ec4812f5bdaadf91ed78c4586d58e5e2ef01a511d3dbc821a66569ebc4fd7855d97eebeb92e986b0da3fcfebd201baea39bc583b8656a723576772c0c2b0fc37ebcd133ff5968cd93dcdf024ca2f4b1d034bcf4a0ac4796190b65d737a7d13702f1187277c" + }, + { + "public": "33668c864030570b4f01f1629dde40c65cc38c42b9769de9c4884b423c813ce76c05583bc172f54124d6958f94dd6039efee4d25d806809bc023ad13b7e28b68139db86d76504ef07efae9e2ca6d4238b6bd1dfbbee0bf1d25a7e27c376fce9574260c8c42e06b8ab533390cd5bdee3ac0c3f3fa414f4f4131fa52789708b836", + "secret": "1dd9ff8aba14992ad38d7e77d90c228075aeceeca061def1ae4b1e4b3b0bb1daa318341604089d1f6dd54eb132cb9f55bf6b064626b11425c8fcfce68bfef6fd6c05583bc172f54124d6958f94dd6039efee4d25d806809bc023ad13b7e28b68b068aa8bc7825d71854b7489400915e8222ca88d8f29ee92e5606b9555e118ca77b08b512ce4513f92024a7ab534c14a79a99dd7559704673969909d01f7819e74260c8c42e06b8ab533390cd5bdee3ac0c3f3fa414f4f4131fa52789708b836" + }, + { + "public": "876b03ae009828a2e45a455ae6ae2002bd8033eddb9a01d0b5339737e383ea2cd7bc6a077fc312fca6844afaf0a29a8eaa4dd71a33d75c1a92f5dbc2a42315dad39542f25c2a10c8082b58827efb7758a862d4098862b92c5ea4fac1ae35a27c8bd2f4ad7dcbd18d5801b86b93cfcb62eaf18a94017635aa38c633015a9103ce", + "secret": "8d19943f3b15168ba74448870e489894cdba81d8818006beb2bb3d490e335a0603983723d9514e56fed562ae42edc7fd180944cf6cadc7c0aeb1560d719cf7d2d7bc6a077fc312fca6844afaf0a29a8eaa4dd71a33d75c1a92f5dbc2a42315da54c615ca17618b76e534114115a87f05673eb8f51998da4937628273492a86556c452605f9d9e3d54f59df3ea44fbf1b8d3abc9b2e3928912217e66adeea4a7d8bd2f4ad7dcbd18d5801b86b93cfcb62eaf18a94017635aa38c633015a9103ce" + }, + { + "public": "0e33da85fa6d4a34cfbf1f60e5e6681fe2aa2f6089d8c08f3d6d0433a2e6720fdf01e633a44b0707fdb8fb63b630b5b9636a5d71c4b5646eaf00fb04caa0297a61f1626d07fe2b63a727d6137f10ffadb449e9395563e9b004605ba0e61ecc4677b8a7c489e5f1cd801ce8966f1110a9895f9a4f08805b37f1de374d38bd929c", + "secret": "35481e62db20172e955eb0ce3abd9c9dd8387323fd940aa683bbccac97eda727f22cd798d35a5245cc03d8547c5bdb0c8987cbeefead33fb6e42b7fe99374f58df01e633a44b0707fdb8fb63b630b5b9636a5d71c4b5646eaf00fb04caa0297a1b4411de0eb4bb067e57ae6197fc0f1d40607e89bcba4a7684b5285a8e8c0f3b0924fa53a02c4e45a5224ed551656fa7598a77fb1730960318ce826accc5eb9b77b8a7c489e5f1cd801ce8966f1110a9895f9a4f08805b37f1de374d38bd929c" + }, + { + "public": "8300f563b76a9d120014a0a46ed5ed48f50288ab149738550685c15dba56acf562bdb79171ac447ca50e0e560f2589d2b5eaae1147e88c2c84caa43aade72d78d876d360dd2266dcf5cb26fb6b3e752602075615edd02270b441236fb8c1397668859511c545232460bb92cfaf3335dc28ad883b664c04e4299b747c90ccf888", + "secret": "12b339a62c8d561cd367b270da0377c689168de6b9bc1b6df799021c76aa83a18e77a1f08433628eff0eda9fab205bbaeebb55a4e1c0966fbf5ab34576fd8cd362bdb79171ac447ca50e0e560f2589d2b5eaae1147e88c2c84caa43aade72d780f1f1b627679c58eecdf9a2b092dc25d300bce3668b146c81acd4108a521ce053a96a811505c8fb47a7dba4a45cca70d5ac5e99b1da45c9b6f2a9f4b7f2e1d8f68859511c545232460bb92cfaf3335dc28ad883b664c04e4299b747c90ccf888" + }, + { + "public": "4fa61f9a41190548ba33876d4f810fd7a9f708422091544f08ce07fb7896bce74d9d0056954088e94dcd80fc0bf447b473c69f0ded8ee56f89fed9aa438fe923c056e7a9d4081b98299c70a8b8fe67aad3184adab903d414668c1ea9d46ebcdf29e238ba11453a34dca8e6d16fa359869cd572535ebb67338ab228ba85739170", + "secret": "506e0d9edb20e4be9ae5000589b551420583970af89d2672cee56ba72ae0461b6595589d1d47de1a4294441ff073499ef7505c53fdd9ff54388220db72e91e764d9d0056954088e94dcd80fc0bf447b473c69f0ded8ee56f89fed9aa438fe923dea10bd6dc7ec2d3a15f83e46ce442c3c18bf870e78bfdf9533cc4ac28769660215c09156c8d5a4e81848a8a3cefb6f69bcd34b185aa72360d1b22cfd29c3f3729e238ba11453a34dca8e6d16fa359869cd572535ebb67338ab228ba85739170" + }, + { + "public": "c8c803700357ef35f94d09c9383f8bea365b841dcdd60dbeddc935e6aca8ae687df72a85b6c3b80726a5a86228dce1c6b0fc8c4ceaa6c5afa832018cbb173957b4f276fa2a464396a39349aa5435f05ccd7d54c5267a23b230638cf6933ed5e43a24be0f17a3dad257badb5e3d0f8e6eed90993a6703cd814c0aa2beca1272b8", + "secret": "a34e37d0aa73555256117a0f83fccff14bbdbdc248a22c5447abb7211a739793712612bd2bfe6e0e786e095b7c31c2ef6b58e5d716cfa1f09a631dac0eae39827df72a85b6c3b80726a5a86228dce1c6b0fc8c4ceaa6c5afa832018cbb17395762356ae14b2139748c340051fc47417943b0784ca57f886e72e4bbac493b21c8880942b2e6c1088ca3ac366fa7cad080a278ceb24cd7cac1d31a618c4debff3b3a24be0f17a3dad257badb5e3d0f8e6eed90993a6703cd814c0aa2beca1272b8" + }, + { + "public": "0dacd549e8a0bc93c36667c51c52f1a144ad62de4b09355dd58f24fae1c54329a7d2e64196680fbf71ba86e89b036d9e4b16e3b5bf51510fb7805300783234406ae95a9426c05ef99803daefa9d26edbe23b134c882a1792e514e100fbb1eeb5aea214efb3254618bc4a9941c42c8333a981b3580bbdff04fdd6fc866313f443", + "secret": "e1e2b32109869b0842979f076df173ee5e184bc44200f2159e18ba65ace00be45ee743214ebd78e7195975031a0fd08684d7897ed8a5a5c057a26aa19f4c0106a7d2e64196680fbf71ba86e89b036d9e4b16e3b5bf51510fb78053007832344056d48b89858db4388bd3cb3151078df237c8911afebe8c7ad53b4e35fa8904d8c91db503b500309349256aa6541701cf462973f08c77b63f15e1813128efdb47aea214efb3254618bc4a9941c42c8333a981b3580bbdff04fdd6fc866313f443" + }, + { + "public": "dc2b66b1c2567be633132d0ef7244005adea9e3bfe3a2e264b6a499697d2ed734afb54a9b40fe34a613685ff4277f9cb77594fdbeed8ce71938db5bfab9dadf14b5f7e2e9f23b8f25425f093314fec83a83bd80d8b558e8e2c7423db17638f0d83f9b440d2627f053f1f5db40b267ffbdb260ab783d4c8850bb6ce0d738b9ab9", + "secret": "30b3985edd9c25a2d52156f84f0caa4608c5779ebde0805ed4ccd7c0b868803f336e69eacfd5486ceb59d8c4b1d6ddd59e2ae5ecf96cc291418bcc08033dad9b4afb54a9b40fe34a613685ff4277f9cb77594fdbeed8ce71938db5bfab9dadf1124e5a4e1bb71ba419fc618080e5c3b63a4a772a12b53283c5bac2d4ed160c3f99ca269003a5b685f08b79229bb416b9dc1c8467c9b5c5dce0565d35846bc78883f9b440d2627f053f1f5db40b267ffbdb260ab783d4c8850bb6ce0d738b9ab9" + }, + { + "public": "89e13f8b99122558c00cfaf49c33fd062b8891062a07884a20b546ad3ecfcb683f51a339a89610feb3462db412f74f39db558065a8845f8509f8a0b7072df3dc6cb4409670122cab8597d18ba4764ecc4c771811bb939fd3b3f5c844be3f829c0030507bef10f045000968d4ad2380e1f4221577e1f76f171332acf286f6cbc8", + "secret": "d594b3a327fa3008c03438aa636ca5ca916c54f91e41cc29b5218b765ef3e27a3bd26f2a1777de20ab11736138f90af8dc5a0d3143dc9200431f982a57b499313f51a339a89610feb3462db412f74f39db558065a8845f8509f8a0b7072df3dc9ed60348c08db9807fb2665a2aaba4d13cd31de597ea9be6ab2935e815c5d07581e4f51acbff161d58a9c602fd3c0e170ea616cad42258cc5ff7da733ed22f8f0030507bef10f045000968d4ad2380e1f4221577e1f76f171332acf286f6cbc8" + }, + { + "public": "0152b50586783ae703b3b7db9737b8a620c8ee025a8440c3dd3aacd646377a0277c83b9a04aafcf32fc408a0bfc9b12b4d64cd91654b85fa58ff7712c338d49fdb740817151ab48595e5a44d6b2b15e54399ac85f5eafb695b3bc5aef156bfe6ca60c7b39177ab5e8aa479d0858a863ffc373bd1515d258805f214d02896f5c7", + "secret": "4b728ec12014e930c4a0a0e0a6cd9405b593f1dce81af3921c7ee36a9b58fe15975a6935521786cb8ded5c8464cb62ac313070a9de30eb52202aa6f42b29548f77c83b9a04aafcf32fc408a0bfc9b12b4d64cd91654b85fa58ff7712c338d49f6dad0750af26c9d9f598b239571b38d3ddcfd84cd9b572404b605771119b9e4a8ed8a0ba218c4cb6585a43725516a0fa50fa54d6bfc1dc6314437498993caaf4ca60c7b39177ab5e8aa479d0858a863ffc373bd1515d258805f214d02896f5c7" + }, + { + "public": "71586987c605e9f30354a5f7c3e928bfa0ade25679b9cd0f607a24987f578aad4c43d29f62a6d4bd3f9fc9bbf93e811a3f8fc2b57bc481656b1f3a9e72820a3af3ae5bb649ee797f610d2442cb3ffd6b285107331cd8b99d5d5127f66b077b60d95492cf3b8a0cb5bf47f329c18dcd42eb3959378f6c1214071b073cd33f01dc", + "secret": "5d7a5f599b23983710fe58441d47fdb2275721db37c606dd4fa27921d26fe8d9c4987df171f15c647d5cd17dfc179d0cde6f011f4dc89c277f2b3d347c33be314c43d29f62a6d4bd3f9fc9bbf93e811a3f8fc2b57bc481656b1f3a9e72820a3abdffff2c9cf2416b736a730d00590db483b034223a3bdd39954b9223a9aa6a7daf4fba0cccef1ce3734baf2bd6b443925e55d5828a7791b408d220dc059ec985d95492cf3b8a0cb5bf47f329c18dcd42eb3959378f6c1214071b073cd33f01dc" + }, + { + "public": "e5b7d0b33ca7982fcb3ee09686f4564e98d89577f01cfeb3cda6f2db066d89c38ae8ef7214cbb1b069462ed1fcf6b05bb6c4e88f21ec47a0b3832a6bceec6f31e1bd68a797c4618b315380c3b7c84033c9dd95fb6a5d46c1dfea9b586fa06ea2fe088ac967f649ac3062929911283106d0b6e24c49786f674f6fc521bd01b59f", + "secret": "d9bcc367ec68c27414b7eb6bf3104365ad421af0c2107f642c5405c4c733451092ec8b0cf3f7ee5d22feb627a99c5801659857a18c61b7a9630693e10bbaaa398ae8ef7214cbb1b069462ed1fcf6b05bb6c4e88f21ec47a0b3832a6bceec6f31531e520fb81c308f19585142cbc512a8bec625220d98db5cdb09b2d3fa928cc21e823376a5221644f92e9a77fa3987edde523ed727258a51dcff0cb6444572edfe088ac967f649ac3062929911283106d0b6e24c49786f674f6fc521bd01b59f" + }, + { + "public": "76f251d7caf61ccdad047c3a114d867fa0ea37a3ce09dc4d12542e348ee2ee20d5e6f822f2466efc0888a4682dbd07e3c448b6376afd86129db1bade0f604c95ecc7baa3003ec9a84934f06becc18296a50c54339ce1fb36b43b8ba92441d72bb61b0d7722646a3ad421111d759a98a2cf7774593130247a10fbf6f20e5010bb", + "secret": "f1c23c04bfe0d3984d32f1555010228fb60c1f330197c0608889eac10f34e785bc98bda6f542fbfbf52af23b6fd606ed3bf650782b4298ad2e1e3b4d0d97cd24d5e6f822f2466efc0888a4682dbd07e3c448b6376afd86129db1bade0f604c9529cd6cfae7b91cbf147dd58610faa9ac5ffbf21f8f36e0937b38ff98203178c9063184ab1905d90a78e17f790cd0731717f983ff191c02fb61460926e4d11508b61b0d7722646a3ad421111d759a98a2cf7774593130247a10fbf6f20e5010bb" + }, + { + "public": "cbcc77c24ef19ea2673deb8bbf6cbe261d6ca50487fd46c4c6d25f9daf4edc09b182424b2f3da7f6c2cb83421dfc10f95ad839bb8d80281c021ce7121c12d1b793a4c735c547b68e4845ccc61365dc779239465e58d287d1176cbc392c2b75f6790fefb9ba1403e7a9ef531f1539028bc78f787e3d2870d1d04ce06c07548932", + "secret": "0a309017dcfe9f648bc242696fb8f3af67df8ff5e852e575833b84799fe8aeb84d0aa145cf14e8453013ae155ee1313c9113d3b9113743940cb9be231ee4b241b182424b2f3da7f6c2cb83421dfc10f95ad839bb8d80281c021ce7121c12d1b7a53d74a70de20b69ca3993db4d3a0bf61954663264345ada1413d1fb6a90a1b50b862eb55148c80d6f29f55ecf002e10fc6afc85125aac55431553c9edbfef54790fefb9ba1403e7a9ef531f1539028bc78f787e3d2870d1d04ce06c07548932" + }, + { + "public": "9cb4ac1709a8525b98f2e36737c1cd0521eb0d1c3dc6f7e11edfaf5aa74fbaaed381fad65e26f14c94c0ae0bb7233d512cef44c590d71761fb4200ec7ebcdfea0b1a576c281ab780af11ea5d15b6b933ed0ced25a9fc5058e7b79da8f31fa0c363e3816066db090addafbf9b547bf3b4a45e2e1f7d2360388e3262cbab0f49a9", + "secret": "3badecfb2c9ac1b26a41f80175a4a2fac659b6e66434bf6afefcdf467e30f3e5d09ba3af9852a8ae0e311ce2483c6798405ff9a2d20c21fbb5c1cc8ac6c8380fd381fad65e26f14c94c0ae0bb7233d512cef44c590d71761fb4200ec7ebcdfea45a44483938ef7b50af76604bb92bb406e0f4d50c7c4d2c35254fb66fde74dd3235646e7b89cebc5cb8a834990919fac4a1bb40e6268c0f05097a3a20ed289bb63e3816066db090addafbf9b547bf3b4a45e2e1f7d2360388e3262cbab0f49a9" + }, + { + "public": "86d51864492c3c7083197464775f48806b72f7310f01e3b7f4ef42d8d1faafc0d361bca6a1d305c7e59d629aa93ad1040dc1cb8af7613a1ce0f99a2e2a7e74faab0ffc5d8a2c2236be36c0eed1b2d06ea0019b82689a6de76a7daba0e3606d876b37e7fcf8c10388e8da9fac10a118effe4b93a8ec8bc25237d683d82f5a0e78", + "secret": "6adde60865674b4c66019f67adde018dc4aa6c23b8c576eebbbfcd7f44c00cc6e3d24a6b2e31b76d8bbcaab024b76be8d0b2ca5e7896386711d068b2e438d1dcd361bca6a1d305c7e59d629aa93ad1040dc1cb8af7613a1ce0f99a2e2a7e74fa08ee8b57ecea13f16a90c6bffd8d5643fea5c6316342823292740272f6e11dfaec5a6e8ffdc6996461c0d1a3bf300d1b7cd47eec8d37990be28d293e379eef766b37e7fcf8c10388e8da9fac10a118effe4b93a8ec8bc25237d683d82f5a0e78" + }, + { + "public": "ddd07f999001833ab723ef8705b7d73e72d7779bbe835b27e8cd7db6a351ed80d59cfcca2bfed4a9684a9eb7d2dafeac7d2a025a64b0ff8c05d2684c513826e6b4a85482065d21312308442394fc5c28ae60db7a27581fec0c8a3d9330c2a78ecb0bb3460b252d8cf2035e4ac3cf1e58559afbb7f1fbc783f5aeaa5be65bfe50", + "secret": "0e20c71375664fe12b7dd056c6e4ead0e3f98e7148f763cfc080d7e349be65dfe575f4c7fefef5aa7ddb7dc91fa0841d41ad91e54c4369e33b3ba8fd5effc157d59cfcca2bfed4a9684a9eb7d2dafeac7d2a025a64b0ff8c05d2684c513826e6d2894ad20e71b643bb5fd98378e2299648c470220171e79bd94878660ada2013b169d660a000d16851d45fc55acca4c4ca445aa907bb87b70dbbe80ee06f3ea5cb0bb3460b252d8cf2035e4ac3cf1e58559afbb7f1fbc783f5aeaa5be65bfe50" + }, + { + "public": "6fc84eccd21f7dd94e3a25ed1085853e2f12b2bc7403e3c5c01af9e5de6f9345c2b075719e3d8b6b06a1ad216614ebcd19e6fe8123952bffaa57aff896537775dbeb23c03dfbfec2bf97da17088357619a8f5900101b3d50840d105ad03cd7703daed9d935cb2fdd5d95b8380ae52d092ecfa0a630c8ed01a9575dc3a587b328", + "secret": "aa51791e2a1cdf70550851f5682b04472b16247834a11289e180ae05f128e74cbe5f8ab1fb77fb07b3f06867a87c4d00fa74c009a1c2714c96a818201f296ebac2b075719e3d8b6b06a1ad216614ebcd19e6fe8123952bffaa57aff89653777509eaccc86d9f0e922f5aa00873541c627a36db6211ac3b5f3d0876ea0d908040118155899efef3e094928e6a1d4058a15b7b30d1a93b93aa734962f64921c6193daed9d935cb2fdd5d95b8380ae52d092ecfa0a630c8ed01a9575dc3a587b328" + }, + { + "public": "5aaddecba3c01ce3c03e18dea22c7443b76596201419a3f6265f82866ea760bcabaa5a651ef34121a5e566d7b06373dd509a7bc49ac346b7ab32dbf5c617e9423a1036b5ffaef22595f468e3d3705893c9efb9de86fb1300e1794d43f509292652a3cd8a46b87ecf77e61e49e4fae80116ec6d9849022142934301c7148a8a7d", + "secret": "0dcb2f7f4c55f4a7b51acaf7f0bc8f3e3830bc1446ed9adbb3ac28c519675d526c4fd185d1a8721be0e46008c1fb0962d59a0f994f6d52bfa64319bbf07fd9d3abaa5a651ef34121a5e566d7b06373dd509a7bc49ac346b7ab32dbf5c617e942bd31e8e982466c8650030a8d30b007c1aec8b6da632f8c9449e25109d94c08d44b0b297cc0c3dc9477c63c82cd2d9bc969de816e4c6b3fe15da1484fb0ef6a4652a3cd8a46b87ecf77e61e49e4fae80116ec6d9849022142934301c7148a8a7d" + }, + { + "public": "45772a0288dac09d3a61a09eb523277252c6b503cdfa2c7e00a9df9b34119f2e49a8deca0dd6cddc8679096fddb1b0e93b31b0e74faac2a9dd57e6f03698460aa737c47c9b24c738e33d43f4c26ff0e0fafcdec5977d78e530a95176a69f6a2e151d9f855ac01755765b18fdef1944883c5c14fd62da7125af74ef30a8ad687a", + "secret": "e75f8a00f0a91374f47c87a145c05745c1386671ad1120d72a1f0b34663909a6220aab197257d29c6863441a038a796a3b3e5ebc189227af827147ca9410c54d49a8deca0dd6cddc8679096fddb1b0e93b31b0e74faac2a9dd57e6f03698460aa8eb265daca58cba6b726eaf748d345885c7e41bef2af7d46d2dc7b08c21badf5a0bead6d46a8b3671b68d9535b3698c9dd13b98c0530a949d717689ffdbdf0b151d9f855ac01755765b18fdef1944883c5c14fd62da7125af74ef30a8ad687a" + }, + { + "public": "c759993fbeaaf2d46ae638201f12435b7a6afde750145a35725460481f47a8d11b7fb030f0f3fd08c98f0afb3f421904167562008cea3ee76821302afbba61c3372e85fcecf46f690a7b9372b9e970e1972139e489447a829072a1b6e94ff86c63a1f13b18747074d17713d7d44874124b57e74fd5ab13fd26262687bc2cbb87", + "secret": "cd1f34ff8c43493cdf870af655e3dde2d53234638820311bdb0906d27870195cedd1a576841ffa5bdb85e76be113b449b08c631fe9a5ebf4838a522d09a2dcd91b7fb030f0f3fd08c98f0afb3f421904167562008cea3ee76821302afbba61c3f9eacd0cece754a84738f081c6a80ae0c2612fdac9cede9068462a85955a51ef4e0bd147f98d516306c32ea5cd6d5787cd3f135c27b5cc9f28d2e6489303260263a1f13b18747074d17713d7d44874124b57e74fd5ab13fd26262687bc2cbb87" + }, + { + "public": "844939f9313918a4b883fd9dbb82ef8c6f3a32951d0cdd9750eda3ca7005f04b9fbe8e9584b215a215bd384bdf2ab3301920bc1ba84df11f94991ffb61ec859438af08fa798cd782fb3b249ebc6780a3bedbc9cd74b0ba4050be4c3cb7f4587c26cc1f878687ce05c8c2b9c1826f3599a944239c0929f35c311c624b562dd1c3", + "secret": "6d115a832c252f85f74525fbe3fbb423066ce0114ee8694f6b3e16bf2097bd50c2697c1ab668ec35338aade1f3944d50cf7bf77ab69c50fbec6d3d390d4182679fbe8e9584b215a215bd384bdf2ab3301920bc1ba84df11f94991ffb61ec85947666369bab70764327a5ac9b94d609867f0f5d43918779e88671340b5a78bba7f3a59d95e56b769793a669a23d87461c705a681b89db5dba4ee35e0783c2437a26cc1f878687ce05c8c2b9c1826f3599a944239c0929f35c311c624b562dd1c3" + }, + { + "public": "716ee445ddc1345e718ec696b315a6aa1e66d8363349341918dd30e75a86220a773f608868d1d5edfb2370fb55e18b67a18c0d299d838c5192d74a29b32f76177c0f840bbc51a89f15e29c479de8b0b7ce339ffe9bc71274bafa6cb8bb0b827c799ea74ef03eee8a8bc95c75fd0c814612365b4a874bfa72584bb909f24475e7", + "secret": "757cbcd3b4ce65cab1443b051f948f15477aa701bab5f0fe1f9ad4e2e3fcb0b7c56dcc71149743649e3f0ec9615c44ed130454e3021172273339e0cf8f6fdd63773f608868d1d5edfb2370fb55e18b67a18c0d299d838c5192d74a29b32f76178ea7c007dca16ad747c5f413bbc890477ca485a58b7d0f7a896b915a089733dde46cdf47dfdff34db89038b43b41a09c9f4ea9414a6a457350af0db5a048e6cf799ea74ef03eee8a8bc95c75fd0c814612365b4a874bfa72584bb909f24475e7" + }, + { + "public": "833fe26cbb49c8059432a66a8909bf2a2b318740266017a06132170022f487707a66c2b2b769502ddd6f4d8eca29878cbc600f0ec7170a4714b4d3a87c816e25ec1c6660a7471689da122f93ce57af2d3dd545c5b1ea969c225119ce5d15292d14eee3b849a6a3d0b14cb06e43c2a2190c3b334385d35a2ed39b2801905eb51c", + "secret": "0965a8f47d5780c573f0ca038d7ab75354b5cea6d88d4406418103fb9e56f638e639f6bb55293cb1badd9da118d67c5a2e957b6735324601e04ef28ce3c142087a66c2b2b769502ddd6f4d8eca29878cbc600f0ec7170a4714b4d3a87c816e25b77d0b264578f8729440f8b89a9b175d667fef1ea23dc5698cc98e367ba2126cfe8f989dfb1362e533de018e996098aecd60a18f62d40167516cd48acb5f47be14eee3b849a6a3d0b14cb06e43c2a2190c3b334385d35a2ed39b2801905eb51c" + }, + { + "public": "1af75a68f3a5799c16a10bb91c62d29642f3154e061296d3d2bd6625c6233af78e81f0f71251bacd60f60dd851db139e81e3f0fde34bdd4633c6168c20a8573cf37df5e5fa6f4739e878fef17a6cf99618a752e71f6a66a5a11dc6d5203a0bb41a0b419295d679c46c74d5d98c49b195dd792a830466d68edb016f8b57c3ccb0", + "secret": "e2dde1e54e8971164264e3ae7d1e7ebc177f2e76a09468ae7cfeb6025406e4c52399c86d4d1379dfc60751701a473073128073091d9ceda7613ae6de40fc7b858e81f0f71251bacd60f60dd851db139e81e3f0fde34bdd4633c6168c20a8573c2e27162406f53503e981dd57178e0344d8d9b0950c9508d5e9c915e29f9db78bdd57440680e673b8bdfa0c67685ea955f92204dab73033f491e7554f429ad75c1a0b419295d679c46c74d5d98c49b195dd792a830466d68edb016f8b57c3ccb0" + }, + { + "public": "9fb67edff2ca3a35984024d9c5d401e50a411c9c17b00c514341bff987e1af4da18c003514b9fd5fdcae9e763de35451c718fcbd34880d367826f02a3e57d66cca66d399d12d5df14f38ff559d16f14f6b372ca1bc5d19f62c8da11c99dd7b39cf6a3e67c3f2383649738dc8359df4b1133bbdb2a4c57e69111c06b0b584d5f2", + "secret": "5d389ddf703578a6c58985a841a12a868afc8bfe65803a755b027be8eca9fa61f12bd6c24d9501b36269783b19640fa81bd107455ba5cce9097783e962061dcaa18c003514b9fd5fdcae9e763de35451c718fcbd34880d367826f02a3e57d66c6f9db08891f4ab08b87c372f8d2e8c015d7d52ffb70c0c4ce1a92aa4dd5c6b973096e562b0bee0e7368dd1c22a75fa3b2045fa59688b610558302005c54aabcfcf6a3e67c3f2383649738dc8359df4b1133bbdb2a4c57e69111c06b0b584d5f2" + }, + { + "public": "24c06a8d48601668e8bfc58f88b297985f4c3b2f9c14a6e7e7c94e9154611638207bf05f8c19474752ee3017aff110d4af3a0fe4107bf055d9695f8562fcaf4b85ab0ec381d52d460dd5e3bc9f7dd0344e4ed996de995c401fc5f2ae6323e1b4ef9bf3d2d4847d58870d19b7e24da1ee9c5b9965938d5cb80f4f662c24cc5af4", + "secret": "cd84eaf61e184df2b9adf09579c8d23516cfe97a37c9833ce57faa77c74418b77629b11d0a60107bf283c48b29406575ba78628af0d4303270c47b0c59896b1c207bf05f8c19474752ee3017aff110d4af3a0fe4107bf055d9695f8562fcaf4b74e97dcdcd56991a953291712db9b303c9c52ec541b1dca9eaaae93775247f14a084ef393de710b432f44fabbf7cfde2d81fe67e3f508a997f22503f89f90f90ef9bf3d2d4847d58870d19b7e24da1ee9c5b9965938d5cb80f4f662c24cc5af4" + }, + { + "public": "cf8a812888f6922061ac869d3f03dce721d086ae4f6837f6c12ca0b656c9e6eb5af9d15354b07e811c95ac0ccc91c48928a2f103e6c74845b4aa24f40f09894ab5e1255658c105c3e18edddb9de88854ec6190d52b14abbbc01dcccfedd2999b01e001de7299b1f54f2e8cc2a7edbfa7940be1b73ce31136f14d4dabba198273", + "secret": "9f1f26b96721e6d165e8635b6bb61e200d635a588fa76f7892c05d306b40675199ac6119e5f6b0c87c26e81356005cc6202d3a939ae9264ff997f829d7950a395af9d15354b07e811c95ac0ccc91c48928a2f103e6c74845b4aa24f40f09894af99181387d1e06f73863805958bf1edd15152ae7888e6981db7c80d129006e843fbda4b00fae22ba08bbec56e847f3c63914aeb6f1b575b39a4e751fc4b3407201e001de7299b1f54f2e8cc2a7edbfa7940be1b73ce31136f14d4dabba198273" + }, + { + "public": "df20caa4575fd9f3a2f853aa0bcd95b6aeef7ea8537b755ab4a8f78b825435beb211a6b799c5676cba13f35aa3aed7e962814c29b240b2a78470e5fbf6c2afcd9417b6d78ffa36ecd7074a0b39fafd99d77014553566c53bc471a28bf3709e4e362108a8c9e9394ef255c9e6c202008d4d9e553ce4e494c8c25cc0d9c7854725", + "secret": "4f5888439a742314fdcbdb3bdc96833be8518400a3d4e4c6010c1ac68bcd5606014a884ed0fcc713c85e347b71ff352f39aeb9a3eefbd62b2df3621d02b6c927b211a6b799c5676cba13f35aa3aed7e962814c29b240b2a78470e5fbf6c2afcd6e5cebce031610396e4056e15131d774f20a4bcf3576a7a7f8679a65fc6f3d95345f8859aedbf96d2c740fdaf4e124bda56a85f695320fcb99d6ae0443b1f501362108a8c9e9394ef255c9e6c202008d4d9e553ce4e494c8c25cc0d9c7854725" + }, + { + "public": "e70e8759c46114fda94e9170a8224995ddf6c53651931f17f02154cac30973ca7ca728de96906bb121af975afc213b609b86edb25e7ef29281de7f78262c69fa90ed8df2f7db293c523c1a895a7e67a17cece1a727d52c510646118addaff6a457007ffe08546c16a626243ebd16ded0a504bcd7d12d35203f2c29ee0abf1d9d", + "secret": "1e5a457675f555091d200294bbb1a17b3737b060c762767e7d1059655fe787b6e55bd794675f43748e5339d982364db1d342c896319a56fa39dcf081b39cfffd7ca728de96906bb121af975afc213b609b86edb25e7ef29281de7f78262c69fa938372cc9e48eaddde6da8b7b62c109eefa96ea96f53bc3cbdc07d57a28ada6612000eee2fbecd2e67baeaf41a7d5f9a66dc7355b4ab54b379d9ed35614287b957007ffe08546c16a626243ebd16ded0a504bcd7d12d35203f2c29ee0abf1d9d" + }, + { + "public": "42bd119e805433a0fa4974cee2b9e10dc6f200f2f59479ae29bedb5ca60cd9b666f61cb4de4c64a0e2016617989398852b98a06a22a0da050c8c2764d13300a4174787cbaa34beb33d2da8c229eed203d9c539947bdc3c4b095f39f96ced7e28ff3ad9eceacfd5ce4e4a0a1c1fa2ca40f914c733138d04838e66c1bf2ffd0ae8", + "secret": "aeedf4bc4ae77e4e576f66dc5cd9fee5294c657e3b621b338d659c3d26a134802efdb9ab5646c881b67b5ce38e44359fdadee58f97c851ba4a40dac71c760f3966f61cb4de4c64a0e2016617989398852b98a06a22a0da050c8c2764d13300a4f69ff9f59fd862c869439b2f38009d6ccac8d61beebca85f8c2c708040e066366affe2103bf7bbefa6045523927a919d834518f2cc1cc8510a24f994e3c7b809ff3ad9eceacfd5ce4e4a0a1c1fa2ca40f914c733138d04838e66c1bf2ffd0ae8" + }, + { + "public": "4ce4df8eb2793872c83b82d154022f73e8425b2f26c44c4fcc2637f0a90e4109124690540c8fcd211698a21737fd51a529516e3f4c3d54a817a92909845499b123e3c163383b883728f5ed98d609a0f343f58848f95cd1f3821b2005078df68ab5f706c2cf5b5c41107ed2c5c2eb74cf5726c4b7a929377b16fbea29c5c0791a", + "secret": "e6e8196532e0165ec13f40b8b9f1c4aeca4ec2f89655710f70e32e6c889ee39bfac496118396e053c5cd52391776fd3c8a5a13798057e0af789a7a542c05c038124690540c8fcd211698a21737fd51a529516e3f4c3d54a817a92909845499b1ea2c71a4eea70662c8bf46f1cba2ebdbad1bc1459f2d4409f6552b891ce5ef82d058b67b9df823c10abceaa87c3544abf2818005f4fe9653eab6843bdc7a7805b5f706c2cf5b5c41107ed2c5c2eb74cf5726c4b7a929377b16fbea29c5c0791a" + }, + { + "public": "e91933f048e997d01ef67a3f8aafc7d5c9b3ad65b665a93300ab542a3e0a39b5db93642d6eaf48478549f67a4880ec86b63bfa4bf6deb9de18712676a5eb7513a3da87963bda01bad95c411be653c538af63e9ac9c8ff64a87366674d227efc3e51363709b20dcd26bf0c308f3da47b6f20a8b9961e41933bebe68877a8be600", + "secret": "8f15fed8f777391300125d6958650c3e759d0d85fce8aa930b927961bce0bb3e5f2a0c76b10a9ef908a73d6d5d78c42018842ad23652b6b92aea44b743d926bbdb93642d6eaf48478549f67a4880ec86b63bfa4bf6deb9de18712676a5eb75138eeee3220b3094d8f40b248c6c1bdf5f66e7d7820a205182f58a302fe70dc39be27fc3be89f44907e6194e3af7ab832c5743e9f248f0eab9c99533fd65c7b590e51363709b20dcd26bf0c308f3da47b6f20a8b9961e41933bebe68877a8be600" + }, + { + "public": "84b18def2a7be6966747e6aae29dca39df40fd053ebe8bd683637bd1e172892dc6b926eca62ebadd937c8ada89763b3d40d98ac657d67db33baa093de43ff95a9f7f1b5563d6f1813ddbbffe33617875bfc1a21a1bc24ee4cb75aa3682b2df0570811f63a24931a754528b131f6022d19769b8f18a622b6bc0a220169bbac90b", + "secret": "3e9e0d029514e6fe2df5d40171631fddfd490f22825b960780e8f3b2dea5ec3db9c36ff7301574c1fdc4b8b53b21595c5e6ce1fd6355274d7aaa26ca5bb22277c6b926eca62ebadd937c8ada89763b3d40d98ac657d67db33baa093de43ff95a8e1c9be2cb236b0f17301f97f6e0ff4d60b869848bbc1e57fc5ec987db8ff0a1f2c3e79ae4534425d9cb90960174a3471a31170797beb8b80be5a0093ac5ea0870811f63a24931a754528b131f6022d19769b8f18a622b6bc0a220169bbac90b" + }, + { + "public": "a6c980dacfd44deb811ac87788dea22217d211c81ce226b0912f73df22101718ec882db7e2cf67e02e426afbef4cab2917c83759924170027537a2d384228c94142f719c0722a5e245df79d562c302b7a13cf63ef348123a41ecf1eab2d77f6901f9950454ae73ba4b22774c53b9f7352127de1792fb0454994ffc953c0e357c", + "secret": "144599ceeaecca70d53b89b15d2855d82c93d980453727e49cb6c801ca228fbd6affa91de7236844d6080afffd61599d049a77022778d81eeab20625ad7bab4aec882db7e2cf67e02e426afbef4cab2917c83759924170027537a2d384228c9478ea98da91d9d145b378ba88effed2796c223c7868debce5c1d27586630e8f33dd8f9d431c3c12a75e153db14fefd1ece67dd9e8ec94844cd571f8739e034a2f01f9950454ae73ba4b22774c53b9f7352127de1792fb0454994ffc953c0e357c" + }, + { + "public": "8944af814dd87d1c32277571d7a46b822597269abc563bf62a5835b2f34658fc5a8c43708a4f53c27981a40b0040117fdcfd644e773d3a65db3f58618b25220fdf555d62cf6bb6cbb6dff78408026a67f0817f42c01f02116c2c10cab18b2e45d802a3b5d1354c090e5b20cfc80d8d40892268d277c5059e0d04e600bdbf37d0", + "secret": "5d053f7a876fbd2d0e13430bc4a88b13c2e04e9e6157952d36599e41c75d8d1122867a4d86d53ca21925f85fd062cc120165c934f032bb0dce550ebc722f52ae5a8c43708a4f53c27981a40b0040117fdcfd644e773d3a65db3f58618b25220f2ab3866a9a34bd7033f7e152d05c1d4be63f1344e5ea88a4ccdcda09c89e3a8a4c094df855a69af56f56e4c435c5708bdf328b8018b7178e309af07ac6125a1cd802a3b5d1354c090e5b20cfc80d8d40892268d277c5059e0d04e600bdbf37d0" + }, + { + "public": "f7247c6f34497e3d34cf77f251bb9ad14d636cc5ff4d577b72a6987834f4a85ae0357fd1f0ea8ca6c2c75ab662a74ec8771943106f7743c31ea0e28b4c973ea7c0d9d7f9bc2c1ddda5d0dced09fdd68859771168d06e21375b42140124a7d1d69a0d2c6f233cc86d9f9ed31c9bcac00936cadd62453ea65f083ed65ed0b52a9a", + "secret": "60f04b921aecdaf7aab738f068b519b3a9af0bc9f45328d7bbd781c3f5df77b540be4f61d5a128e34ec62626413c9fffe7aba415dbca5ea81b61727ef2407e98e0357fd1f0ea8ca6c2c75ab662a74ec8771943106f7743c31ea0e28b4c973ea7581fea29f04ac029a67522f421435ec0cf5c37fec088e00c2ba3742afff5294a61df3ad71459d2189acc33d21f703ff1b374e081ff792bef74c5bc481c7a82c89a0d2c6f233cc86d9f9ed31c9bcac00936cadd62453ea65f083ed65ed0b52a9a" + }, + { + "public": "5dd6d231db5c0da05012031015a6491656d2f23c3b8a0e5b4fb78af58e555f1a78d7b73010a4a1fbb33077d4c87cb8332365eb9ac3ce9f696f3adc42419a4c8807caeb56ae68e5c9703ec07132946741bf819029a8883a6917225db9e7847a246ca3f4027745a3e790f5cf5a21c1650a68be15d8d5a478114ea7082f69f67612", + "secret": "8ac7a957b3f2b0c44ef470c3f2cb56018be012ecf5a4e6fb94f0ea38c3cd695a9868cfab572d843ae3f886ce0f45b9150fce4ce13499ca42c8772821a24cef5878d7b73010a4a1fbb33077d4c87cb8332365eb9ac3ce9f696f3adc42419a4c88771dff551b57908959e10c8a90e7dcf7ad138cae6f0ea20d905648755e3218eaecd77c5c5033236fcafc00ccc7148c0f1d34812e0e68dc76cfcb8c5a6a8cc0c16ca3f4027745a3e790f5cf5a21c1650a68be15d8d5a478114ea7082f69f67612" + }, + { + "public": "ba3278fd51e1d771d0b352d6a94171111a745f6a4990ab571f00fb886cab9cbe8de4ef33a1f695c67999013101bebc5f2bcf3628d8530a410e758242b3ef3685b4cf627536f523ccebda770763dc005ea7fae7bea5e32d0d66e0e0c6dc6ec046c08762355260a8d8618697b5c846c05b7317ea68514b348bc739874ddf16e2fd", + "secret": "49b910776bc79dee73ba91fce8a06092c07d715c5c238cbdc50bc45118eb6d2f34c8bd5472fc1ccc8db955016757ed3098d1f3f3b2d98c80644f1210cc08dd178de4ef33a1f695c67999013101bebc5f2bcf3628d8530a410e758242b3ef368525cef43e317d2364d5d91a23af317b96411ad9c416c4af6b7d43125b77e7387dde35ba8883835a5ff8427c21a9403f6bf7deeeaaade70bd7a515633eb6f197eec08762355260a8d8618697b5c846c05b7317ea68514b348bc739874ddf16e2fd" + }, + { + "public": "77b9083cf6a3fb41c52f7fe684f8b88137236531117cac0775f88edf5111f8c4ab60d7b1357a66915fe85471f3b4cdef9189b30f0eba882395d5b239250597503a88654103c4d036549a22c0c22d5c419de2a100ceaf60f2ceceaf124dc4a05d34e01857a82f0ad230a2089e69132ce0efbefb68a6adac494fdc21fcb460527b", + "secret": "2b55dd46260788dd4a5b3b8688ae29841378fa46936cedcca5bd4449dea758d03dbff4d2a52bbfcab4da4437be2c34f9df9b7f2d2f7df05f4cc6a3fca889814eab60d7b1357a66915fe85471f3b4cdef9189b30f0eba882395d5b23925059750e87fd87f753c59edc4f608152130f5ef53ed86c9ccbd488f6fe886358bafa4ee297f8cfa9acab126542c2b9bfb2bcdd0735b74d307f4183aedbf274f4e8eecd834e01857a82f0ad230a2089e69132ce0efbefb68a6adac494fdc21fcb460527b" + }, + { + "public": "523ff71f40808cd2413a0f97e2253fa1cb1a81b732fe966aa9cf4547a1698657ef847e3388d9aa0daa63f380a2df0db7238c772c629fcce998babb7cfcf9a9300d5d02998890992043540ff00894731a75f6ca423400616b0ae37524ba0f8fb304b390cab0ab71a4cd0800d1883cae6cf685484f211175b2de1507a722e5ba42", + "secret": "248ecf80c53509120750eb550cba9741a75c4a59ca90a4a54ffe644c437a0298d8acfeab9a61a6f193cc0dba457deb9ac83fa74a7d1b83ec2ce89ac9b856cb67ef847e3388d9aa0daa63f380a2df0db7238c772c629fcce998babb7cfcf9a930cda7c6f26f842c94e78fc25683b41938da947d7c7376cbd8d2a47ed670b82138d6c324d1c457fcfcd37ffadeb51ea814cc005111692879a0ceb982b8da3e6fc904b390cab0ab71a4cd0800d1883cae6cf685484f211175b2de1507a722e5ba42" + }, + { + "public": "6b69538dcd7f0d860428809dcc236ca7e926955bb110b76a246028ea323f4bbe8305d737a2c7d6d31c15df9aca2ccf3318c441f3c38d031680f9e1e9f330fad0c34846af9392cb1273f14202c9b43350e509a8b3d93d0b100f7824beb49216e345f8bf447cb927f3aa71e647252b0be1caaf7ddbd53c55d6570781ab5b6b734b", + "secret": "a07786da330ce3af5277ed7df85b2ad2f0f0aeb572d967727bc0e4203c9ead6190b500b8657a619af5d7078fb1247ebf08a8af3a26d6ab31a67d262076753b308305d737a2c7d6d31c15df9aca2ccf3318c441f3c38d031680f9e1e9f330fad0bdd5bd834a29eeefeb6cd0710aa33f1c84c658e739a68c515b02d249ab621dc69725e2c88cf7ef5c44ef29a5064c526e2adc09a1f8c9519b74eee581b5f628f345f8bf447cb927f3aa71e647252b0be1caaf7ddbd53c55d6570781ab5b6b734b" + }, + { + "public": "ac7e1bbd2678499390661235348beccab2a727f1c13821b0ad9b017a97248f34255b93bded6516b30d8ee05b8f7d74bb28be2ea836602ee828d51455340fc5797df3f3bc7b1012e9b32aca86b632b02dc6d414bf24f11e11658c4ea00ec0b3a0cb36fa41a62039ddaf95f5f0834670d4aaf70054e9ef1b9f77ccfeae23f95ee2", + "secret": "cc5f877036f507111e99b59d05c837f6fb90afb3e9eb7461e7dd2ea3a8ce3bbd89a54bfe285957fc541e5d9b2477cebf9fa243c491276c5f733018f07eee12ed255b93bded6516b30d8ee05b8f7d74bb28be2ea836602ee828d51455340fc5798f3823fbed59128ec4e657c1045e5ee9716a2716694c4f5321b11eecd8ea214c381f3838d1cc8db9707d5287536b77212dd89b60bd26f9858f510647896b40bdcb36fa41a62039ddaf95f5f0834670d4aaf70054e9ef1b9f77ccfeae23f95ee2" + }, + { + "public": "f05ef3d5507f81804e7ec32ad1e8e2b050bd5421d1c0a9ea5465ae4ab6a1a2a9238aa256ad2f81439feb3dab55296bbfb164aae2888031b783fa30dea167b43e255a8b2bb7f51d6c0f6990ce2c60f16b7f50a82ffff064fda7d9a5a228fe8f1abc9b5dfdefb44188a32c892be615a0e469a7a9827d29dd9e8e5b466b90181c67", + "secret": "812f1bf6b1b59fd3a300b0e7a612fc2fdd3af69d95e6727b98903e23a9d194ad1d37f173933d6ac386f8b634015c589306da8579829a922ce17821191579b061238aa256ad2f81439feb3dab55296bbfb164aae2888031b783fa30dea167b43e347b6e8e6f9e11ae0cb4551d1c89f894e0afefd2fa229c51511d94eebbf7c76d25f87daf739c882f1dddff8688da7e7535075a747ffddb2994be0e621bb6d172bc9b5dfdefb44188a32c892be615a0e469a7a9827d29dd9e8e5b466b90181c67" + }, + { + "public": "4c8074c3d5d0dc11a0b04edcb77dc1d030e6d83e074ce553f98d835f0d37f40bf4b35ae379988de0bc718392f0c6a314fe087189bd5062deb92d98137ba3731b786d406c5216c220d26471bad59161316e4313376fe68e9ba3d3bde09de70cd6b19341419cf9173ab7f79030e67cb0a3e0723f4f7b21784c3a5ac790a838af73", + "secret": "c188fca3332811db818d001dfcf62b30bfdf071fed434650e9b7cd73b9bf5202ecd5087da2027830fcfffffc1b0418098337fa10877d436cb9032b793e289116f4b35ae379988de0bc718392f0c6a314fe087189bd5062deb92d98137ba3731b28d93797ed9a144421f7dac634ede58619090d96470e974c8f1756f69a4f24fc10de079756438d4652390f6ccbbd70a90b3981fc3b28ccdc4f14f648966c9b4cb19341419cf9173ab7f79030e67cb0a3e0723f4f7b21784c3a5ac790a838af73" + }, + { + "public": "e7aa2af5889fc0fddea1f432263bd4549e1c235778c6b89a4a31b365d3cacbc671b02788204345100d5a938fa0682849a215cce6641c7ad7cb1305586d628264f0c0e69984a3d8d89522b8da4bb8e23e0caf4ec329bc8bd1cffd4d64a5ac5dd084969231b810ab0866cecc94d93bb8cd4447d62dce3d28c98e0b9daf25f218e6", + "secret": "36b23e9801c047e2726c4b6662eb1e98a607b549b19cddb5fd03a812b822bf1e9432b8f8563707d6755d0af92db0cbf1b29f36226a04d57fa54b5a950544469371b02788204345100d5a938fa0682849a215cce6641c7ad7cb1305586d62826480df4ed63a130414545bfdd6175f72c5fd01187eb7c5cbe51e4a812cb438441def797400cb7b069b327a43a7dcc91fc9c960eea7670b983f4fc8ffc4836d41f084969231b810ab0866cecc94d93bb8cd4447d62dce3d28c98e0b9daf25f218e6" + }, + { + "public": "9ef075b540f0d912babb4bded94a263c02b28bf47123526ddb48d3df5cc782d0061f86228a0f0dfd61b7d675206c3b38f334d661362eb2b7097726c22645d12eae28465873d3ce8e231a79d5ad368af48f3524d236a2b962a706aaee2c192275f2c4d9e2d5272335d58226eabb70f1e14216c4fa860b6ebec76f3b2efde36b3e", + "secret": "cc6ac382946ea60932a2329d302bf853471e2278a2c29240dcbd76bdd45d3c59e7651b83138e1d8f4c820dbb7097d4e81ec92fcade7363114ac4e133bd0428f2061f86228a0f0dfd61b7d675206c3b38f334d661362eb2b7097726c22645d12e40cf7fbea891df8ba1e7222ba744d0f9782e234ef5f4e8b86ca139c6a2e256ed992b938ed098f1143f21977a5a053c3ab3230197dcc0868ad49b55bc30524d0af2c4d9e2d5272335d58226eabb70f1e14216c4fa860b6ebec76f3b2efde36b3e" + }, + { + "public": "c7a964c96b33151d0609713b2faaa436f3934d29bac1762c5e6af7b8e3fbcf508f335c1f54cbef0d14645c226cf1704130efd56f328f48d2287d49cf30d2965fec7de2204660a3ee726db0f907f2f3e31da3c6ba00388782e0ce5c48ba4a01f45ee0176bfee1b5b88ad7f9a4558f3166eacbc621a21dcc29be06ad8124814c16", + "secret": "eb15c4c1a3c2d51ac237a4be2babe70eb9a40de9322fbf2030851a748757558613f7a35d861889f176a4c3778cc35efa0dd52ed2190f26dada3e26e09054bf1e8f335c1f54cbef0d14645c226cf1704130efd56f328f48d2287d49cf30d2965f842de026a36c270edf74e43ffd0d3e02db140f88885aa32b56291f45e84ff2c5322b7a21aa42a8017dd08eb3b92da8980273c29a4cc121b069e3c8bfcc7256565ee0176bfee1b5b88ad7f9a4558f3166eacbc621a21dcc29be06ad8124814c16" + }, + { + "public": "4b85184148287c09cc8d48b6abbf171d9de09ee54d2fe6f104e7b9f2cdffbf9329a2243fd3562c20ffb970a22fcd7a5e29bcfdf82e71d0b38dee8aff2a3d4d743c33ec177013ef495cd554a58b1d3f2ad0382ca6eb13052601f8e8c422ee3ca6cf86cd3311992188e39b782db36efd274de4b910e0a128dbd128aa4c416fc38e", + "secret": "4ee307bcd31d1d031032b1f37559c3acc3d8c329dca872087b1792f58fed9633f2a446da53fa3c8294bcd9c4ac068215e48ff9fcf711543f26a60381772cc38429a2243fd3562c20ffb970a22fcd7a5e29bcfdf82e71d0b38dee8aff2a3d4d74c05356711d6bb940cc25c56eca778fda4cb708df35654ef32fbdad17ee2d74ae7b3dc3653f843c090e5bfa0fab1cee2d57cabba2cbadd11133c14c3cbf4f0eabcf86cd3311992188e39b782db36efd274de4b910e0a128dbd128aa4c416fc38e" + }, + { + "public": "86c06e6060003aca1b8fe81b0f9a422b0c6260482500b4094ee543eb5afcac48fbbb4f7d1aa6601f52c2d3ba9d7a2abff7dc922461ad0882eae91c6bff8e61ffc4155546cffd160de27a135f249b9c367490eee952104855e3899979e7faeca1172b3aa674a8377722eb7a4e991705f4f1d230553d65aaca70b02058ffdd8420", + "secret": "a76072d7236285e82b5bd8b786291c0c2eb1c130f0f37d1512e12afac0923e1f64609e98a11823eaa7cf230fc8753a48c456fa131d20996cd888447ec2e3a8a2fbbb4f7d1aa6601f52c2d3ba9d7a2abff7dc922461ad0882eae91c6bff8e61ff86f821558d043ab3704e84598b64d03cc0aecf8ea3882a40f0614a234fed874cf712a99896f83154ce08a54fc8f51a7854e3da111a691ccdf4c392135e05fcef172b3aa674a8377722eb7a4e991705f4f1d230553d65aaca70b02058ffdd8420" + }, + { + "public": "26aee49cb88931c50b389b909a89dfca975b0280ede9cb1a1132ea65ca254ea3a0d3b2f4daf929484c80b84de9f9255960ba10998602da4e13cbd23f3496944c0ad132297bb846fd6bb0055860d1f9bc21722f88b1202b1b507d47b1461190f86cf0831bac2db241bcaeea386882e4364a331a1a5c8a797ccb1d6f6a4db1490b", + "secret": "4b2aa172f2c2c844420211810de735cec4cd6095ed48af8ac9bc1b2b129357212489f158b62c0979a50df9845be7ed556a0fc857a27cd72534aa461e203649baa0d3b2f4daf929484c80b84de9f9255960ba10998602da4e13cbd23f3496944c07f1a933b2a2e87585a02a773abb03eeef3b192bd61a5dc1bdb9efc3035d10330d3e5f9608bf2c28e882f909039762bb3b1ba9fd3e927275ff5e40bee09ae62c6cf0831bac2db241bcaeea386882e4364a331a1a5c8a797ccb1d6f6a4db1490b" + }, + { + "public": "4bc504fefdaadbd36afae10174c0b6e94159be8092e192acfdd9fc16c27876d069bcadf330b9c9b12b80917901b9f88aa8a56ab9d2f98d503ef1a611d69b70cc264964d9c9b9347e1f33c21d44d6e7a89b24097428ae50fd050c0b3b33013bfcd2e1071b08066377e80131451d43a8ac51c6f8be36fc3e8860e1ad51d84ae6c8", + "secret": "f5b9b85167a8cd792aa9ae13ae2a9018fb924e2fb2496ac0ab28d816ab0302eb78158885ac730b7e22557e30e4462d842980484de828873822b25afd966c5ce669bcadf330b9c9b12b80917901b9f88aa8a56ab9d2f98d503ef1a611d69b70cc8ddbb0ba0580a3a9cb619311c9e7700c456ad93b9ce02313c6d6958c48433cd15def0df490ce2513e25fdea62ee440e28933e804c5e5be0529a3cc7789ec70cdd2e1071b08066377e80131451d43a8ac51c6f8be36fc3e8860e1ad51d84ae6c8" + }, + { + "public": "f661a138de27463fe67d1b95553f8ed6a877415c3a57ff0090bc24c7df3b28faff96286c28ceb8c6b87922eec834e8f1e49c8664dabc67f6528bf0ee8c2f606149ca75a91357a139b5ef11d4d20299ce197b995cff83cd24f77fe860c2233009f2243a2620f6a355a5af68cc91b9a1acf3af635fb25df657307bb12cc7071a94", + "secret": "0f46238754b9583c94bed293e246b8496cb89f4e095fa9161ede96430ec5a613c7893fbbb9cd8927d2944a6e6d15dae8c93e2811cf6fafedf8f685d1e9b47d7cff96286c28ceb8c6b87922eec834e8f1e49c8664dabc67f6528bf0ee8c2f6061bab23efd3036a73bd20bba1feb79fd1acecbe15345820e29245932466fd6b1aafe534265758d516a35c5471b9ce1b1e5162f4ec731d8e16d8e8ede2df5046372f2243a2620f6a355a5af68cc91b9a1acf3af635fb25df657307bb12cc7071a94" + }, + { + "public": "29f3c8617d323994e3d443dfd9caf1fea6f5301171ed9dee287655b0ad8aee9b5c548f546fac50899585f56a54d67979c4eae060023d8386bf54e477a09acb82c2b82c41bf306e1cd88ee594d7f10d5a151b97e7d90d6069ed3923581bcf4c5378aa567abfede46a4334afde30691433a83f563cebb467f3183213fa7b542116", + "secret": "8c36b44447412b8363d059e205864b1ba68e085a19f4dbf492c4c4e59910bcc63e9c59d792964ccd2eaddaa34683c779ae78c70d02ec9169286462d953f0f2405c548f546fac50899585f56a54d67979c4eae060023d8386bf54e477a09acb82f0e08b693b466cf1ccc90c6631def39dbb9b7236762f1dafaca9ff816f6f66518646d5685fd4e4bdbca4c7931159edce6b44c54833b39982945846c57981cc8b78aa567abfede46a4334afde30691433a83f563cebb467f3183213fa7b542116" + }, + { + "public": "8e5ec702f58f9d78a9c456d30c7c7bda7c9b4a5a6e2888b83236e911c0854b0c1127896cc9797d3daec8f327cec7f3d27fbb51e7ce3555bff02515dcdd85691740bb1d1871990d2771aa263472770b8f3ee10d59a57a1aabd26f722c4c11268f98f7c3174403053e13c114c35b315d86c41a1446d19ad7caebff3797cfcbe35d", + "secret": "5fb4c3c5fc9bbe1160beb7a7ffdb6b63631f9ad60109160291b2d52cbb6f8ee82dd2737e376bc82025dbb6214eead631e8729ff2a2478d90290a0c44f12a56b81127896cc9797d3daec8f327cec7f3d27fbb51e7ce3555bff02515dcdd856917e6e4362db3f15c552d0292218fb4b3d55ebc7f782307fa9fecd40d014b3e7c29b6cd0b0dcd48e3dbff906aea534e967af541a88c38b2e162d6cc0186c0201a4898f7c3174403053e13c114c35b315d86c41a1446d19ad7caebff3797cfcbe35d" + }, + { + "public": "d19c0685d0b4246f31850fa59efbeb885d3eb52899cd3ea5ae1f3fd3bd9e702090837d3d1926e0b78d45d0910903902ca6c3b0408b202f95369d1762f873f0442ffe7d8e637c76fd47de02d5abdf877db0ec82ca93c51e547ea54ff54ba46b6c948bbbabbb53644c6ea9441dbe6371c3944cadc1667f42859b65e25bee99db92", + "secret": "66254a021a4769c94cac8360aea14478c99138409a78f8a380e6dfcd8fcd943eba1c262a16db53e9537ac24fb75e4c06b9c905d14616f123a8d33503aa59af8290837d3d1926e0b78d45d0910903902ca6c3b0408b202f95369d1762f873f0449264f41cde7e8ddad2d73fd778246d42696654c2bcc9fdbe8dd5ccd9652805e8de7e7701bd634dcfa3fca7dce53f1746be20e4abd73bd356e6cbed90c4c52df6948bbbabbb53644c6ea9441dbe6371c3944cadc1667f42859b65e25bee99db92" + }, + { + "public": "4ec9c509f58aa0167af3a8bdb04ff7d726b97784fe0b434b627535541924ee30b718586fe70a2debac5e45075a63373be4a6964dcfaa98969821d328eed900e6a7fb7c8bc48080d185cafff196dea7fbf7f917a435018a9965e077d1af92c08cf4171795d446c90e02eeaddd1d7f739a3e729c6bde2c726bf385190a7bfb4d7c", + "secret": "78410adcc07764622d28c6452f1ee60ff714754b39062aaded169cbd9bd386b7c564fa1636898f498cafa184dfdea522c9aba987c039836c5c86e1cd206b3cf3b718586fe70a2debac5e45075a63373be4a6964dcfaa98969821d328eed900e6c139ef93280781c39d603c534926c42239c16d2bb36ac630b1e23bd9b76e05fa66b903f35fcf98b7d8081eb5588549c68a4eb84e988503caf69c2f357ddf5c6bf4171795d446c90e02eeaddd1d7f739a3e729c6bde2c726bf385190a7bfb4d7c" + }, + { + "public": "3a262bc3e9fcba4451b0d9d81844d74412ec144bea9aa1e040235d188f00c217c9cfb9763b20b61a4c7fca4944dadcc12e20d48c8a6c42cca6c73754d078e17cf00914b18a35b5ab8bc0b0c6f19e6e7c3f4bf63503692e5838ab42c515431b87d40f6d0ca575d58a5d1008a2c7e2a901fa9a9464a3edd0fafd6cbdc435771a1f", + "secret": "960f297b9b1623c65f26d8c094324eff8287a5df6122548472590cafda3a7695d9a99cbed932f1a17aadd5a39aa5378883fdf0ef4ea6a5c0cea99a193676b384c9cfb9763b20b61a4c7fca4944dadcc12e20d48c8a6c42cca6c73754d078e17cb55ebdc568d0853997752d6ef17ecf4b2560f8400dbd22002ac633a535a08b746efd9b92632e29d19c6814c2a589b06b011a2615ca3c2a75f81f31bff4fa79afd40f6d0ca575d58a5d1008a2c7e2a901fa9a9464a3edd0fafd6cbdc435771a1f" + }, + { + "public": "bbf0aa614d53f3ad1bdab20f2039624bf5d71c6b9f774ff997cb3e13d17ba9a79d10509f00daa9b3ff79d7f92c7c216843dbb45e5a606862076242c0daf2a9586745c246ecdef5398d64719e00d865b7616fd63478e8cee23d90d5e1d2ed1077ed6ff4e025a7e799e16b98410dad14172041694d18fc644af4cf6565ae3d64c8", + "secret": "60b721700d2ca2e6f412d8f560b6dbf76fdcdf8bafa0ae55edf530916d4133dbecf19d2c1c86a630e1c6dd9cfc5c83786d9b67412a669351caac17c8c49732fa9d10509f00daa9b3ff79d7f92c7c216843dbb45e5a606862076242c0daf2a958aa0a747b0b55875ec8c9ab569205e34c923456aa324c81d4460192b53e2401ab867314b754e59d00e7458271fe38a97d667f4f268fd287b5e0d2cafb091346d2ed6ff4e025a7e799e16b98410dad14172041694d18fc644af4cf6565ae3d64c8" + }, + { + "public": "8a4f2d30cdb336deeb111fb0e5f100263dc90efd91428fab100f411e31443c5849026d76057fd019f03b5998dc96c20b640da6890478937ef7e1ce9d4e2040c3cd0e1fa0211120cae00622742e6a320ee545dade4ade77347bd6d634296fab2091eba53a8b6b90f177ec5fa0086099b29a45e9f02c0a67a223e3c1f3917b5a42", + "secret": "30adebd82a1ec6d962612bebe86360d548bed40a7d87a34bd375ad6e61ca479f638eb1990af5266e10b6bd336b815cada9ecc6f145a6d9b394ac14cddfde089049026d76057fd019f03b5998dc96c20b640da6890478937ef7e1ce9d4e2040c3eaab22486b499cd9146a73960d43489e7e8d7e05a633d891d1fe5eef9da3b687e1133ecadec31ab84f6e86a321da67a6b83667dabdb1da2fa94430a8c9c401ac91eba53a8b6b90f177ec5fa0086099b29a45e9f02c0a67a223e3c1f3917b5a42" + }, + { + "public": "566cd08a043ce4c8897dc79db3764724aad562b7fe4130f6663e0d3c08dc756facb38b98bdbc5812c30c3cf8c16ae123d9132b86769608694ff6969422b119e063a02676f959b0bb81618487072658ff050b37a2cd96de10e2f075a3ae821d0e00ccc0ad2554f5694b6ba8a5e79e0a5f05750bf8e6f47d5bcef5f8fb808afa14", + "secret": "d9dd7a8908f62a835520ad83b591606fedaf5920cadc7ab954922172d0610553f17073e28642b78e8bfc984c29ed8e6f4c77ac6ac9293b992c65a8c66ad6bc9facb38b98bdbc5812c30c3cf8c16ae123d9132b86769608694ff6969422b119e0f6b90264bc10fce3e3c118c4d3b6eacf6c387056943f30cad947045186ef0c8b5f1ac1f757c94868b61d994b9b892052078d9620c05a6df6ed5b5c77ad2fc93100ccc0ad2554f5694b6ba8a5e79e0a5f05750bf8e6f47d5bcef5f8fb808afa14" + }, + { + "public": "2fed74e5ce1c333f60f058fb0968fa2e87f682b1879aba3313a9784a157682974f38ad38164e1c79fcc1916add4c72f9adda7d0cb20041d9adb65f49c0e35b8d96d085a008d8f4dbfda0f8387bbe3209132db087b06c4dd911ffc8c9e9d44feb0feda9bb5b0e0ffef2921820eb0242aa554f6ceeff73b53b62cb4ebbe974354b", + "secret": "78ca3c59c756439e370c09aba83e1e31d359a6fe628f7ebb4da348c00450977d148db47cd5513b7970b3953df1a2cdd4be547bf67e853b6ad2c2f6e03fe8ce364f38ad38164e1c79fcc1916add4c72f9adda7d0cb20041d9adb65f49c0e35b8d2862b0c7c3ababf84cce11eecd73d2aa5ab07e28b77a4b193354a45d98b2f4fba4888fa8dc579fd6f90900610d2ec5cd0a8250492214a3a668c035837f7d5b640feda9bb5b0e0ffef2921820eb0242aa554f6ceeff73b53b62cb4ebbe974354b" + }, + { + "public": "829322c0c62fddeb6a424d971888b7a579ef5d11244ec956b80c207b9724ad54f73e481fc544bebaeae2c3f64f333ca6fe4d270f4566c3048fc5cfb89bb1b60cd3e4697006b2fa125bd2c2515e38b6d03b2d0a92b1da158cf46d40eda3a34362f71e168f2f33d0c2903b675f69191f9968b6303d8a46d640b8dcfbb21a0342b0", + "secret": "eb59bc510de70099b9e5d801eecc4101110372c365049c0b025a34fcedeb0bc749c27bd71c91951ab294234405fdb77b1cc5eae36f42426da8ba0f0a748dbd0cf73e481fc544bebaeae2c3f64f333ca6fe4d270f4566c3048fc5cfb89bb1b60c3e847a7469289d05f1cc04e4445937d30003f6e7fb4fc289f895b3dfccad831aab809cac3cf521dfe81ea48d9422513062a66aeeb24cf19b528c3f304843ee9ef71e168f2f33d0c2903b675f69191f9968b6303d8a46d640b8dcfbb21a0342b0" + }, + { + "public": "14d30173dad995eb8bd5d3a984000b42efd07651402389e2c1afeecd3d25cc74cf9b2c1da1affed4560b2ddd4e80cbf93aebf2a1cf6f385bc96707a2a3111f79b0ef061d1608719cd62aab6b6deeb4460a8dee13d144741f07db07f6a34eb3d087dbc6e72f0eb051ea1025d6af34703bbc572f60e11b0330ebaac332a83f7146", + "secret": "8df084da995473b3de4f0d19a3c316d7f59ef23fe901ae1810447c9655d10b77d0a65924bc3feeb001189cafba5927adcbc5588f0bd1661d81c23ad1602a368acf9b2c1da1affed4560b2ddd4e80cbf93aebf2a1cf6f385bc96707a2a3111f79c0f4e9def1ccff686ac2e5d45870bee324d8c44ffc8c26e8618ae1fced0567dbcb1186a3220c4958042b15da7871eae23d953754355c68aa733c4fde072566e487dbc6e72f0eb051ea1025d6af34703bbc572f60e11b0330ebaac332a83f7146" + }, + { + "public": "7943e5deb0263465fe1dc61f5a123bff24ab3a405e5b0977836689a67273a25514d4863d69f19ae9f4c79630e1c2d95c3bb847d82355c3ed8b3d50df20d77c4fb0f1d9b53ee14f97c16b9bd5c7df625b6917622c29a4bfc67a5302e8e2055aedcc2d5b1fb383cd9947cf331ead3abe9988e29bf0e179bb8a94cb25f03bc9b1a8", + "secret": "f514c599df5056afb8dbe328bf30400723da39ae60275921b1bb923a48b0a9359effc8807a24ab3209e566538ced7a9a87e59b96dc554c7e9713b7e27d7f549f14d4863d69f19ae9f4c79630e1c2d95c3bb847d82355c3ed8b3d50df20d77c4f6a5fbf41ea81cc56a9a0aba16534d69aad99d588a9d4c4fc0cacdb0a9d2420b3725c3cfdea6bde99b84e50739b9ece36d7b9ce2e10fac128a7af81fd118d2c6ccc2d5b1fb383cd9947cf331ead3abe9988e29bf0e179bb8a94cb25f03bc9b1a8" + }, + { + "public": "065224ab7929093aec3669eaf5015a5339ce16af39ccb9cee8b935d431abba11cd4d6d3dfd2f652fdda516f599cd2d4b383881fcd89699b9b71ab8b259b3c46051b90ccbaefadb5bcff887e2f31f3458117d91bb041131351ba950b5cc98f5848070cf20da4dcdb347b7ac67962324f90ce128b14ba4f2da47bfc1fa36850d41", + "secret": "12535cd7278db63c43a0874e58cc7e30f886294c939f445822e52d27f965f833babf6f194554fe4881a4c3d0dac8eed8fc703acb1bf4ac8748285a0b340acab3cd4d6d3dfd2f652fdda516f599cd2d4b383881fcd89699b9b71ab8b259b3c460177795a8f7b609b4fc919885b2766456e26aa56a3c19d680e9d9ec10c6ec169432ef9e3c7cac4bdabd536d2d312d3fc13be03ac4b39b92c22637d86fc459b3ed8070cf20da4dcdb347b7ac67962324f90ce128b14ba4f2da47bfc1fa36850d41" + }, + { + "public": "5f1a27207074c50eb1e608f676434de8a950e2fbc14dceae2a0499a17ce6b89fa10a9df4f7eb8656f1e3a247612a4381b221622773faccf457049c06c5724f16454793b8198767a56ae0378c592ea76369064df631b9eb388263b77374d6523f33d1f4c24b3e86e5663b3ffba8abb5cb703ab9a8df90d7d3e89b704648c4923d", + "secret": "6fe91dead72d626e1ad311e57f4c20fc606d4307eb5c067be22a888ab81336936a82cb76af3d889d0afef6bf4b1939d318fe69797fb81c1724674901b6b9dacea10a9df4f7eb8656f1e3a247612a4381b221622773faccf457049c06c5724f1684e5fa289eb5903efa5d4d04fe4a0ac1e8437e57c06d17d01be6228fe8080ea6be34c040ff346e73d873e3a1c7cddddad0565b3c997b63cc9a861bcc8965a87c33d1f4c24b3e86e5663b3ffba8abb5cb703ab9a8df90d7d3e89b704648c4923d" + }, + { + "public": "c151854a462d783ed03d11ad7f6db9098ec74cd02ba671b65af0608c454f9e0c60a0a566146e9d6409fd46237cd3fbec2618772fcb30f6ec39d7769278a7fcf4106e87535fe388254f707af13430c848d0aeaf7fbb045b6fdf2051ba906d98e57417c96119f111e41aaaa3db44c8d2668e852eabfecdb16a5e459f1f1cfafb75", + "secret": "1adfacb54f2b7308483a59ed52cd3aa4b438a83a18c1de2d7387efec90978807d5096065989f7af65dec3868c193bb0431e990c1f9493b0ee4051394f09376ba60a0a566146e9d6409fd46237cd3fbec2618772fcb30f6ec39d7769278a7fcf4419cbaeeb8182d0e5a60df190eefcf3e4e5031e361bfea81aca57bcd0d2b7f6e242f6ad597d58627d20df89bacbddf270fc8f24c71bbbd30b5779fbdb1518bc37417c96119f111e41aaaa3db44c8d2668e852eabfecdb16a5e459f1f1cfafb75" + }, + { + "public": "480d642a20dc4ea3e374244f7d47bcc57dabf773c102224bd5611928276007fda184dcf49fb3a3c88faef615f85b97f58233f3b770541e3bc5a9a15f3a98368da66acebb61e1dd461e3653570691e9e82621cbe97b116d7c8f6cfc689d5622eade862261fd2b589141b4eae2c9ece6f4fc88f9a9e4aab654b94929c5b33b3ac2", + "secret": "456217fa6aa171be2ff0d8dcab5b26a36756b92f8f062592a7a9caa72103cf2814d749ab652323ca95b0ae5675ac9061723b8ad24c5f7144e75a37fc9add756ca184dcf49fb3a3c88faef615f85b97f58233f3b770541e3bc5a9a15f3a98368d82661a659cd7bd4bce28b86eb180624927f0b24215779a82487c0be9ed68342d0ea9ab2619b02d2047895338fc649138ad2a94f0b1d7dafee4caad5090485cbede862261fd2b589141b4eae2c9ece6f4fc88f9a9e4aab654b94929c5b33b3ac2" + }, + { + "public": "ffbbe4955b210304fd24b47d83ca69970b60320aeafded7226ae331cc25ac66ff4e19bab73bd86745214a5fc85340b467002e7fd325b6e86659f9f70bd29c40598f754b32b5a5f4278ce1d40a8db9dd6f608e7ce13373be579dab7fa778d4e3326baf75705cd0c81f450bebe8098e226c66e9a8981ef8d0ecba32920b1039a93", + "secret": "573d45238ef650fad842bbe766e89faf1e50169e0ef342fc375700414f87c930a791c9d85c957d4f0169a2a42c7f59ea88c5bb4fa36caf0e08b405eeaa1e7970f4e19bab73bd86745214a5fc85340b467002e7fd325b6e86659f9f70bd29c405ae089a5ea8633bcfb289e50caad2efc15f00095f03c0b92f54015018dcfe8c43747444ca0444b405d2b8502cae26081618e2e4c146ad19eadd1fb956f0fb610926baf75705cd0c81f450bebe8098e226c66e9a8981ef8d0ecba32920b1039a93" + }, + { + "public": "297196060a7ddc7d1ad95b124786b9b4256ae821e2cd825209a3c732766f64a019a1db3beafbe03c4601f4252ed3d5f621735952b635bb9b78c9c48324b08678ba130a3636cf9752b581b637e7f14c360f4119bb27375ee8882fef02983028c59ff019be897359c248c1e063ab66bf68392cd04469ee07b6c6fe2b74fafa5928", + "secret": "4793803767635bac4fa141ef726ca5e0a2bd7364ab419929e5b2eb922316d9da48f0dba96b8d133e3890acedd507c29f1cc23cca0f6360fe301aa422a1b169a319a1db3beafbe03c4601f4252ed3d5f621735952b635bb9b78c9c48324b0867887df33c3c62b7a13b65657babd4567e3f0ba27654d9c86142e1684ead0d0f5de03347ec4c871475b1714b84040a57053ef97f0ce620588f330cf7026e0edfa009ff019be897359c248c1e063ab66bf68392cd04469ee07b6c6fe2b74fafa5928" + }, + { + "public": "8958a2712b3ca206e6607d1d58a4c75ccf35ef3ba0c5bd5e87efa1c5138fa6344e82a6053f1ff760a0241460326c1e52efde3861a058344d6ff8646eb305b2a9fb7fcbd87ed2da40955a30b334e02f6c08b9147765d2de08a769dde9f11c5eb7730d2e74532b25c923f331b6be268ba8f950cadd9d7db156d61be1271cf722cd", + "secret": "6f0438acab59b29c5b3c93adb62fc94b851703347041073726da3ac8d2d0e9390a12c321c43c95da99468938050db8d36e3c07d57e689ee3531fc844d4bf87dc4e82a6053f1ff760a0241460326c1e52efde3861a058344d6ff8646eb305b2a9c7ca44e8bbe2131d053bb8f7e41f1b38e1a23d26d430c5cfc771f6e5437fef43001bbecff5c73ed63e682753ddb7c26d3e7e503a2c8fca999d8d90f4293125e5730d2e74532b25c923f331b6be268ba8f950cadd9d7db156d61be1271cf722cd" + }, + { + "public": "f1e352804b5b8f4039f22f881bd1aff4ef920a1e0f92659be941d5ce55dc787d3b9a3c873b54866e3f1673f5007d7967ce0b2909d3950647ae2bb055fa519969aadc14c353d2876c3c3130f96506018abcf721778715f0ca81423c87b1aa981c89ba42565c499b3ef1c3e1c402fa2b66ddaa131475884e0a897a22b73532312f", + "secret": "b9501c12f7db45d6c981b764f181a9a09a0ae961c3a405c21577cb59920b94f66ecdae92875a0228d317aa99c19e293a953eecd69dc135e364176dbb3bcb63043b9a3c873b54866e3f1673f5007d7967ce0b2909d3950647ae2bb055fa519969faa2af703e9f50dc6f6853729ae25939e0c3165e805fb8f6d9711abb7e74ae75ff5f91c16931802a2b48594c5aff663eabd6e758556ee8b30a747bc769389cdd89ba42565c499b3ef1c3e1c402fa2b66ddaa131475884e0a897a22b73532312f" + }, + { + "public": "5e80b12257e845a7f508fcf162bbc65b42295a73883d5b30797845f147b3dc42f0c8fefa4401f6d3e214b66b19db559842686f528945cb2527d3d35e051c72bb4d33b603eb7e12c27dff6e8cf7dc468ab2e65e0b88d7302b79e0d19c7a3454565f0c1ad7e81c870dac50827a04ea65711e3c5e09fb8b9446fe4a92b71ae0bb6f", + "secret": "e01dd3d01f5b2faec7c03e951406b2f47192762f9979b553770822885b676e75420383ef3a1e4e9ec6e943426b43afca5953470f4ef70fdba1a6e88471da1b86f0c8fefa4401f6d3e214b66b19db559842686f528945cb2527d3d35e051c72bba03de3e574c9e6557c0498058dc907fc613ff6f6ef555fc539f42f163bd670cfcf691a41563de766c2550056ae4b07c4f53e6b6df1887174f6e03162487998c45f0c1ad7e81c870dac50827a04ea65711e3c5e09fb8b9446fe4a92b71ae0bb6f" + }, + { + "public": "e7a6a93c87f74ef915d4402c333686c2f61b7316f5f2b1074655e85a736c53458c47fa8ba26e9d7b3822ad4796ea2a33d5894cf58c5fe8f2e18ad69609a011ee9bfb32c1bab65524b0110bece3e87733b3c84b3c840adaefe73fc58390637fedc6689ccc0327e0add9aa4862e1ff13d3f596cc6f287924b56c2fff826c41da5c", + "secret": "ef46b48fbbc241cc189b3690e8cabfdb6e0da5350c78cd4a8d012927ebf69e82f8cc4d439b9768abb2f6042c18550a70bc791d0d843c9e570e0c223d79e20a9c8c47fa8ba26e9d7b3822ad4796ea2a33d5894cf58c5fe8f2e18ad69609a011eee0091d923ec3e9c9663cb862c9f54aa034e8682ba5b86b3d9614c57da8d16d19ee82fca0b067d92ea8eb8269e4b8948bb1d5972fd9db398675441ac9328fd43ec6689ccc0327e0add9aa4862e1ff13d3f596cc6f287924b56c2fff826c41da5c" + }, + { + "public": "6e03c8b96a510a28d14262ad90eed7b6d1e374af241256f93a7de4c53302850438c59a5589c26d57559f0a72c7c77856a0f1e1e39b5c32993c4a572631c7d574d0929d8be76f663cf4c153946da34f98f639d8a38e8fbfa123a5f61b7ad6c8323da732ceab8de40f1f071cb190a6dbdec80ce8d4c800d28edef86fc6a00d17e1", + "secret": "5be665a24f74118e38ebc4ccc9d35be265c533fa5b259fab7875b15905e1635cd7dfa3a8077c71113a96c0bcbca839fd370ff10b1985822302cd4ee1071e3ed738c59a5589c26d57559f0a72c7c77856a0f1e1e39b5c32993c4a572631c7d574449ac28cd5db7cf451e79c2dbcee2063cde69b1e8fe621c7e77c6f4587078be3d37b677e30f7f013f033862fadb60c1211ec032622b8051d2620bd42db2bb1283da732ceab8de40f1f071cb190a6dbdec80ce8d4c800d28edef86fc6a00d17e1" + }, + { + "public": "8ea403c059acbf5cdcff7f642f2df3ba651983aabeff2de9e89147a6a635ea69267ffe9d1e067c142b73a68036bcbd3062d69c6e24ad6a862d96707218ca642a254b26c85b2aae4142d692a12f530b9ac3be54c4d9149aa92f7ee71c6cc4e3ca29cc98ab985ab4b79dfa25b4358d2417cc5504feba5ac41828df47b98099cd3d", + "secret": "35799d0716378c874faf40675b60ffbe4024faaf7d3c86d5cfe46c0e08d3ae490b4ef3d70572dd55b91bbf873a2562fb5ced02ddf30af02eb72fa64b63093a91267ffe9d1e067c142b73a68036bcbd3062d69c6e24ad6a862d96707218ca642a552949c8ab35c628cce99f81e571ca1689391cd3a4386a7b59949a71c8482d244487e4a1f474f8448f777580566a349e1dec293d71471801cf0ce698485c059629cc98ab985ab4b79dfa25b4358d2417cc5504feba5ac41828df47b98099cd3d" + }, + { + "public": "cc93bdaff6dbb90a99ef238b41dd87d13138afb21c8764189309e2c8ec532d5dc3bae8d0e8b77e8291c2ed6b1292d56ac10fa03238bd5db329ab3bad95a7388d71adad294a0493a0d42424dc4e6b46a8261309e5babcaa991d0bd435127e28a07c6ee7b5292932a297cc29fb5b98eb0723553413d1cd82cec8f89bae1c123898", + "secret": "a587ab3c620b54cad516fd7b2c0baaacfc5288a2fd17e0db99450a0c4a3cb558bd9817169fad191b9e68287640ba0bf6759f1ccd9419199ece18b08436a05d45c3bae8d0e8b77e8291c2ed6b1292d56ac10fa03238bd5db329ab3bad95a7388d8a594a9901570de9e00f9c034fbc52bc323acc79803f334c1402f4f5cb3e37b227c57ed7abf9540c0a799c65fe149b77936fae34b9d234356192912010dac0f97c6ee7b5292932a297cc29fb5b98eb0723553413d1cd82cec8f89bae1c123898" + }, + { + "public": "0e1f90960c251f27af8da1bb929be656de67f0e627fda305fafa5d47b63f656b5567a4d663db11e0a893f83c03e4decd011f545b4598211995681f90b1954c21a0e79e2b3fd0a6727575c1729461567bdc025019dd7ca2e1ca7d91b924b2781fdbb5a2a72ae1f2ec37bd2334e906d4d5a934cf908075e11a80138f28b9d868ad", + "secret": "73d6d49cb70e25476b3f418fcb2c08b622b50b701952656638c70b339240a6a4d81c39e69b103220718200739e009016f4fe34bcf4362d7bbdc949005eb5f2cd5567a4d663db11e0a893f83c03e4decd011f545b4598211995681f90b1954c21d3e97f86a7900459de8d6d88886b394f8eeb80f3adf26b7efa1bf8e6889d148e30096f3d26cd8cdc3ab84ab89a13f39589d7c2960dd4ec33bb638c9450bb17e2dbb5a2a72ae1f2ec37bd2334e906d4d5a934cf908075e11a80138f28b9d868ad" + }, + { + "public": "0d1f263975489dfc839c8eb30a6fa3a7a49924723e266ee1e17fd1d96882162e89b81b72a6cd55725ad386ad09cb64e077471c168c3153827fe90197c47520ed6aedd84d1a5486600311916ce85b3eaf46c2f74cbf4412cc541c7759d17510673f0d7fa35561592f57cde87cec8536649c4b1450736b831072cc4e75bd5319bd", + "secret": "352cf38075e1020926adf1aacf93181fe30623f109cf8c6599894f358d4d0eafd664beb950579c7990450bc838e77122692f047f71974b74080935d8e52ed59089b81b72a6cd55725ad386ad09cb64e077471c168c3153827fe90197c47520ed89ab920f3cdb64368feb27fbb2e6da5d38c1b61b278091d644ff453793ecaf9da5ba437a1df8190bc5b687118471f939532007ac8c2673a7b1b9219b1d2fccd03f0d7fa35561592f57cde87cec8536649c4b1450736b831072cc4e75bd5319bd" + }, + { + "public": "df34f779b69e8abad64eabe40ae7e31e43e63e5ef1fa0f26fb9159543b5a0883364b339b9055643d32591647a292bbc09bed144b0def97ab309dd1d00c18fdd1d72e1623b9fd87d7af12732fbf9f44540b3c3846f6f73f1c6b0c0e574f6a14970556c50c9fdad127831b1233f216031f372800bdc207eddf7fb5fdd270c5f204", + "secret": "24d89a780758a26633cc5cf21ddaa1e7c23f3d40861857d8ac978719842a523bf1ec8681da3dcae0ce9f77cf427cbde49ad107b5fe77bdac1824e7c036fc332c364b339b9055643d32591647a292bbc09bed144b0def97ab309dd1d00c18fdd19b7692c00f0f01d9d3801b1a29c5fc9130764fdb3f353a281ee1ef2d4f3a8b7e41acd7a2bf7415e501fbdfb4b8b58f777297ebc9c3db18dc53625a44fda904200556c50c9fdad127831b1233f216031f372800bdc207eddf7fb5fdd270c5f204" + }, + { + "public": "d810d6437b09819e3aa090f6045ff3a561d9ef48bc124d5d457b7f679ab39a85e2af0b8c391ed3488b23802c033c0fd9bccb223d6552f27d5e856c21c6a1e3893e4fb9e801e61f0e89908e5d60506b32cb73bfc0f02d8499276c6fbc0817c560414637016278a40be2d5c50e7ebb0d7e11480984cf0154fcfd0ada3dddf49015", + "secret": "59b70127eace25ff873c6e00ef7be234c5a6f0daead34a2610e78371e9669817b9d63c7d4d89d1b37ab97a69d559038b31f1d4eca53b10202e30e5157a902677e2af0b8c391ed3488b23802c033c0fd9bccb223d6552f27d5e856c21c6a1e3890d15fc071fbb6ac13699cadc1bee5253cf537988f09aff6261d8f996da12656ceca71e50de313a1042069844f660fe193942f58cdf3f81144f3b94362bcaf49a414637016278a40be2d5c50e7ebb0d7e11480984cf0154fcfd0ada3dddf49015" + }, + { + "public": "3b139b49782403e1c52a4d2fb7ab67495107b91d4384f981aed40b9c77c01713ab427ca71f13e991293df2dd05622be7488f6206e42f0fb31c7a018488323df8c0d4acb691d40bec49c55b1b0ccc5fe1bb052f80d631848694971f91109bf51b574e12846e67d6f348652246098634c3e31a7097c7d28a1d81a421d1b0dc4d82", + "secret": "7c01e2a6afdbc54f14b085032f0c9ccd91c27772177442edba314f740fe17c41b5416f1d7cee585081ffc9cdfade54072f71c15b5d4e1bfef93e7c9e90e77971ab427ca71f13e991293df2dd05622be7488f6206e42f0fb31c7a018488323df857f527c3f9471374047952612f73a07cafb359242f972f8e43d1b3fe2c781b171cce0caffab855025d529e1d56dffc5984aa7450eb5c332ef741de15de7097f0574e12846e67d6f348652246098634c3e31a7097c7d28a1d81a421d1b0dc4d82" + }, + { + "public": "8f4741f76f2e7d6a03a50f86a7ea47fa38bc86bad5f7f030ad1f20d5dd8e067d48ab066c1ce96a4e74473ce90b21d589404cda89f39ecbc6ee4ffb0b351d6223b01110da649161bf84a5a96ef5235b2b909f148337944eef52771c82380bec084e54c6b1500a6e6077de9876ef650f5dbebbaaa72f9a69533f603a0b8e13d942", + "secret": "6c14f1f9680bf3d548fc0a2b07d326bba758ccfa88138e2a07aee5c1c3c7727f4bd16c498f596be880be18fc04dd61771a171ca4f97e1e7b2b5f904da1e4269048ab066c1ce96a4e74473ce90b21d589404cda89f39ecbc6ee4ffb0b351d6223b5bd25352f3adfc95653706cf25cd4428780dfeff4a2a9043360c5fa789adc6d3d0887decdb047362b10adeea436f42fc25e71fd9718e4ef604cd985d2f79eed4e54c6b1500a6e6077de9876ef650f5dbebbaaa72f9a69533f603a0b8e13d942" + }, + { + "public": "1672d07ed8122bf4c63c0ac07b4877f800bec13b239f5c2b1bdd015244315e78490007c64b7ba972a9aeb4aa80da266988951fdab59e073e05facf13c3d2df1d380db560b95ae3e4566845d7bd9c78b567157b988ca670b67c5bd7d0e05c7c41927c0fa774715a3e86df637f8a53ffba9f0453d2e0a4bb67b9575b678903f042", + "secret": "148cc037ad297cd5c315da80b8a1bed7ad9d2989e46cafd72bd6608e8050e7fc6df4dc12a635d105f6c6ed42cf7df15890754a4489d95f500ebb198dc7d56ef9490007c64b7ba972a9aeb4aa80da266988951fdab59e073e05facf13c3d2df1df1d21a0c2ea0b7557b41981599eeb0b74053b1635d071255cf32d6844aba6800921d979e2de47bd06d2518b48cde7d9fc74dc8027732baa64c37d2bef2849f50927c0fa774715a3e86df637f8a53ffba9f0453d2e0a4bb67b9575b678903f042" + }, + { + "public": "089272ab716c2594f34302012b115d9bb378f6c203447f0ea66208ff2ca1900796e4ac4d85d7e1a3aa342aceb20d9ad6b01fa8c49bb23a451e95db45261ae9c09130ec80700075f4a629901680f5ff60c0ae1ead68c5e081df9517acd7e6c35413c4b5f274ff1c12a63d4ce2cde8f5662d3f9c957b4d8a07a2232cecacf47375", + "secret": "24408c464f69ff165d2d46f153a60338d7a25febf324b26a27574d632477a3425ad12038f32406a458d10326e6a8d707b54fbc839d70b09ba8169ed4906dadaf96e4ac4d85d7e1a3aa342aceb20d9ad6b01fa8c49bb23a451e95db45261ae9c04006c276b412f3f59d195cb4175e36a18db22b0d5a1708a40f84027f90242e3f6b741339e56fde6d2f39b26800fddbb268773c80398c0af6423ea449588130c713c4b5f274ff1c12a63d4ce2cde8f5662d3f9c957b4d8a07a2232cecacf47375" + }, + { + "public": "74d290f1d706a4b6a573762d25c76c4e2495cdb3823bd7ad4928fc860f217e81f2fc6c9b2827d93b6c3c662ef6a3519b0617782b1091053d564cc47153794636a0e1f2b280319af1ea10d55ad395badf4fd41b5d38e2a2cc82348c762e5e2d1380663062341186b30e64b63cc588a7274af998c897242f425db969b4e8df7c10", + "secret": "3f38e0a527505fe3f4317337cf2acb9358616def1ffc578e22708ff177abdb7850122eb7cc0bef16b9996aa2c7206fc4ffd304c64f29b1dce0930540b86b0d2cf2fc6c9b2827d93b6c3c662ef6a3519b0617782b1091053d564cc47153794636b1894880ee121df872a537eb8de2bf4731238eb6bb6fa6ff1bd0c023b65c641b11a67f36c799209db4f241b15c82454702049f2c2fbc1b4975ac6cadad524d0480663062341186b30e64b63cc588a7274af998c897242f425db969b4e8df7c10" + }, + { + "public": "7c89c10eccde82ce5deface00351da81bab67c4982b432e6c44073acd5ec27fb6061208c76805bbc754deae73e3b1560a02b28b2d3095108ac05fb3097b3cb7c04cdef811eebf5f275a69d0727c4b0c83ec88d8cd47907758ad6dbb122db0145f9222793fbfc3d7c06082329ac78358188cae6999a1fe7a50e53d06c0905a05c", + "secret": "97475619d0d612b8e4e8d5109aca3b45301a48943d7401453327635e94603a59439dc3c942eb2c8b8354fccce8135e5993ebb41e120c80c96864a84565d65a3d6061208c76805bbc754deae73e3b1560a02b28b2d3095108ac05fb3097b3cb7cab1a29cbd7247ddd7dc8400194798e84d7e5dff654078772f78810e81e5db025eb38a2ecbc175433eee7f168212099be0389c76ca19bc5135405d3802099b050f9222793fbfc3d7c06082329ac78358188cae6999a1fe7a50e53d06c0905a05c" + }, + { + "public": "5c78b782aca1cd37eba37e558db643d743b2fbd21e6bfa217f0d6c8e78a7c0d9e5b6c04e06014f6229b151f129e03c5cf8df617f4e770a7eb6cca0eb9a4314f740080834638edc2c0d8913c6654db0c403a5d1330c5a11c428f541e34c45ba393b52bd83af421f9015c61d48dba033ef657569e9e8d937fca5e9c9bad5bd3c72", + "secret": "755abaa49173dce1f0fcb986f77933ade2debd0d83fbc86b0ff46b4531c4d7499141af257595202d0d8102b453072eec1d1f38deac86aa1a440ed0649c19f793e5b6c04e06014f6229b151f129e03c5cf8df617f4e770a7eb6cca0eb9a4314f71c381f78d7ff4e9b2f609a1377eb1c6cbd1f7234ad8e7172e79acedcb457ada1658ae46497b76792813b4a084aa53628e263d394473e1d647841a2bba2679ee43b52bd83af421f9015c61d48dba033ef657569e9e8d937fca5e9c9bad5bd3c72" + }, + { + "public": "8f70c89ecc64f183b6797c0f89afb2d16367ab9a2ef2869e076bd13feaf5cf0e668d252080a822415bf14c09d6a55a33c66dfa9ebef83d42a0a845ea62aaf4eb3492f39ef253cffa85a9b0ba3c452f1e7538573c5bc7d3ed001210934b3abfa1e7c94f39ca77c30023a0354d57900c0ae1eab90ce158bfbed1e7bb7fa0a5e32f", + "secret": "4437a3cce3935fcfa59cea346f7fc20905789e3a34f79eb61fccc86f34f0bb4895e282e51e9198160b48277ceff7a79f9323452e1b9499e3c41b5da9c5cff450668d252080a822415bf14c09d6a55a33c66dfa9ebef83d42a0a845ea62aaf4eb3810fbac6bffa7f16428bc28daf7ba4863a322c4dc296afa9d57821eb3972a9fdcc1f86fd23088f04180a08270af11e7eeb099e4d5459d06f85623694a0a08ade7c94f39ca77c30023a0354d57900c0ae1eab90ce158bfbed1e7bb7fa0a5e32f" + }, + { + "public": "1ae288f857ebbbfa10cc172a966f8dfee5b0e222b0c7f233e6e5d0439d259b6adf2e2b2ea523d6c1c234aef6fd6c5556021ec3f64a183e0c55d12d6e33475a4907d8f0310501cd54a7023417f9f7e7b4824a116493c07d432bdec7bfc09ac94f4235770a3eccdbcbca825198e2d2672122643e20f5ec0900f54cb69df97a14da", + "secret": "1bbb812cad8b113c4b2fdf9683844d6b103606b0e26420db1b9624c71af05671ab79537254468812500b0540c75212a7636928c311608e0a1f5076cabb7581fbdf2e2b2ea523d6c1c234aef6fd6c5556021ec3f64a183e0c55d12d6e33475a498fc9ce95bf84ed6d6f5a147baf7f2f7265006bf5908372e0f8965b8f4cf18019dd3196745222c912c4cade05a539e7828b022ca5df42afa7e2d9afaf8a0cfa4a4235770a3eccdbcbca825198e2d2672122643e20f5ec0900f54cb69df97a14da" + }, + { + "public": "35ec2d97ee6a83f96f432d0fea70cdaf1d4270897fb8001398928d720007c01bf5e81337ff24c796d6f8e25de608488d95e0e7a118227c04fd3187c5f14626b2a19dfb5067a39889499488d0d3d9367f574e3113694803067d2499aaf2b0309c567c810743ea2bd08618c076e3bfc9d96610263ccb0ee29253515f1bc5ee051a", + "secret": "fa4a3f2d18706911e64c7a465ecec77d10b2f0938ac236eabaf5d4eae50f8724c2ed93a8acab67135db87ee558d34a507220fe64ef982ea9d8c27d79d7c9d075f5e81337ff24c796d6f8e25de608488d95e0e7a118227c04fd3187c5f14626b23b69c530927b6861b25e0972aa1f01d3a8870735496d157125a6ed331c2889a75dfec7286db28837eb7041131a1935dd9d6dfe31c995511ac80b95955c1ba770567c810743ea2bd08618c076e3bfc9d96610263ccb0ee29253515f1bc5ee051a" + }, + { + "public": "db0dab62d99b95501a0932c421f9c2a40d9345fb6236f7f033ada9cb79c43a112e5c6ba89c6ebb86ac7531a68b0b48db14224ea9c32f59d927a5603a84d590461d1585c5bc64dfd566f5be6d225f5f968b00aa385ed2909cbcb0ada29bc1c51f803bc3663424eea34702aa6d381bc749564372f4d1e6eced32f7fceb7972518f", + "secret": "38f33e1acfd3b456f962ab3458dad40aff7c4641215049d40b7c5227c7d72a46f7b96cc2702b8625c852dd368488f88a5f392f2d1455a9cd7b35d0c0cea0d0b32e5c6ba89c6ebb86ac7531a68b0b48db14224ea9c32f59d927a5603a84d590465aa3a4d3e0c60c45ac763d24a4a9e9458a2e14bd3b1cf80efc1bbcdc878750363a20480813597ea4175042f27ea6804fa9bb390a1bf5ef5234a4ff75bf8bc6c9803bc3663424eea34702aa6d381bc749564372f4d1e6eced32f7fceb7972518f" + }, + { + "public": "982a0aaccd04e0e70a5a21cecfcd3d238aba03469458915c8ba2ee47f40c03d9a94c793834ba5eeaa0c26f15b15e034baa4b5757ec8c055fa94d9fa6bb67cf15cf66459ffe769aafe5d060e805fd372b19f38205626633cc71ca5fe8a0ac9110aa3c49e760c2986f61e041b7ba079598cc07ef501b78f34c448db29b9386e926", + "secret": "3c0ed26cf557278843925ecff046d9ef535bfe8f8e61b8139596dca0c25563adbd994b298d84ea544d3bb42b5d7400c37eb17907b2c2c533a66a697c0bc90591a94c793834ba5eeaa0c26f15b15e034baa4b5757ec8c055fa94d9fa6bb67cf15abf32059511cfeafbe9033d8d464d71b83b9bb1823f3bd40f447e676d06f724005105e4389a9499f1273fe09fba1e05a155c0b9d1b3e584f47adced450bac5a3aa3c49e760c2986f61e041b7ba079598cc07ef501b78f34c448db29b9386e926" + }, + { + "public": "0d1547838648cf63035fb81a5b49995e5869dad2e2e4c5a283d45a382012e20a532798e1042499014452e985832e64edf226d8b8972c64c94737a67782f4bffd6815816d33512ab230ecf95d47aa3a0b1a4f4a22e237d1567f9cd7a5ce097977b0dbb8ffb269e397c1ef205a58480f480064cf3e07b2bad8f9fec403af75fc9f", + "secret": "90301a2c456a5d0ca03c2432bd3b656ef25ed283d7da86a9fd6960d76dc1c09ec4c84c3446c76ec3908254e7b6cb4480c7166c07f0f101e53d9dc546229b39b8532798e1042499014452e985832e64edf226d8b8972c64c94737a67782f4bffd5fda846d3592e3832c653d33b836acb5488fb1c0d5728b7840aeec0e20f504e4fa62d5c0302403fe1dda9b3ecbb33a8e7b5ee21c8f63d1f30d00e3beee30ce11b0dbb8ffb269e397c1ef205a58480f480064cf3e07b2bad8f9fec403af75fc9f" + }, + { + "public": "a2b38ca189065db3df09005131ebf0a432c42dd31e5a4a5d67180edada1a446a246117f4c7eaa0adbcc26c179a99157c7b88a4d630eb171575021c58ea30f6e7c53964cbf62eefa50649ad6d346751b245a77b89cde4fb94e119cc682a17a988465ef7c99903cd4fd263a8ab295fd3c20aac7c2d9d8a7a39dec9aafa7f1fce3f", + "secret": "8b4a8671612845f54a19a98fdd08878da75d267358b923bdfc17b8a1ae0a831d75d5e19b0f61b9e53d3eae5309564d33a4a019985bc7e2492da276df941cde1a246117f4c7eaa0adbcc26c179a99157c7b88a4d630eb171575021c58ea30f6e7ebe7a30eca10003db8546a3dc1c585d37d5e67a6dc82ae073bd6818d00b098fb0b28617736e2fd69b1c60c07dc9eb7d91c3b1643c1bba7380bd9fa9d75f7172e465ef7c99903cd4fd263a8ab295fd3c20aac7c2d9d8a7a39dec9aafa7f1fce3f" + }, + { + "public": "7fd6d4fb525b693840c00e1e6751f8194a0b00565b2f8ef94d437685105ec2445492154032db7653dd4cea002ba924799869847a6e060f4018b6e487596a5e4c69622d3fb82bcb07dc161ad8dcc29878672b668d97b0539611f7846f05eca7ee19640ad6031ef7ee5f42a7bb09bdb41b10df77ddb40bc93fe784ffaa7692844d", + "secret": "9438013148e3fe4eaab4e76d18af6cdc08e144e540f131b14c5038d6eff1887501448811254de149c424de48a9cfd5ef15c7b05bc43d08c3a2fec859549335ff5492154032db7653dd4cea002ba924799869847a6e060f4018b6e487596a5e4c3b99caf2bee397f5e715ed9e966c72424bc679a650230117b0f35cd4e8d0ff592bd6eb4b9b894c13affd8f8cd52c78ff1e184c97ee6e36683e872ec12af1971919640ad6031ef7ee5f42a7bb09bdb41b10df77ddb40bc93fe784ffaa7692844d" + }, + { + "public": "7271034dd55c74592ae7c74b75a40ccbafb4474cc78bb272ff34152310e1bda2970f627fce9d09ea55ce23d9a44e9e4c7b3e9c7e7c932b5fe776f27d7b603e6b0ba0d0217f16606bf6765976b0ff2f67f39db6fa04d412ab6184cb690bc263b71515e9cb5769ed60b40b67111609bc1a2065f4686e2ee3b81812e5ccaa2b8e80", + "secret": "87f8b88653f93b75d46869c0ef5a731253dc9547fd16ce0b67615f750483323e31371b9ad5f2b7ba2c10b5ddd5936b15fa859ea25a47384ac3f546b3b543143b970f627fce9d09ea55ce23d9a44e9e4c7b3e9c7e7c932b5fe776f27d7b603e6b8dedf28f19c90a3d9634ad2b8baba08c83cba1838860a4d600ad8a2b60664ce1e89d80c2ce992152587483604242811fdcfdc808ef945f085af9f0e3b6f7ed4a1515e9cb5769ed60b40b67111609bc1a2065f4686e2ee3b81812e5ccaa2b8e80" + }, + { + "public": "ed3ce8e79eca83a7136071573295dc1043e67fd3299f6a448b4d4f5a8771273223e5b4665ff8f2342ee39725fd20817bbf386f1e9ecdf08596fc987966365decfcada6e022620ae4950a9d4d02eee28272eedefba132e92e386fbda96c82a5956684cb77dcba18cba516f77e2f5bfecba1c3e9d84521877205209a3baaffe6e4", + "secret": "c64c75b1107bdbf1345322acb6050fcabe65d8aa8402fb1b427ae63cf4e56016af84d21f0438a7415b0d3b11b694c2278a911def7d0809beb0d9cc69bc4629c323e5b4665ff8f2342ee39725fd20817bbf386f1e9ecdf08596fc987966365dec9dfa07509b69c6eb8d2f66ce7a8d1dde8ba2e7dc12d4651d3fb666e2c476e7279422163fc1fb87722154975cfa60c6086078d9d57cb27d9c6f23bdf926345a906684cb77dcba18cba516f77e2f5bfecba1c3e9d84521877205209a3baaffe6e4" + }, + { + "public": "fcf2ea652b0ca060bcd5b9d90730e3af389361faa5489f7ce38c90708098804a7fb360fc140ba1e788de2e283d937460efaa973c3bc49981ffa1911159046c7888d0f27a93ab9b81dd52702096ff8006ccf116e50162691ec7986263097986f48dbbc1eaf4aeb7a554a4e2dd72162d59bc8ae4af951166b8bd6d7bc9609386a5", + "secret": "38c98ec46704bea118eea8e0f7d46525f07732e6afb6233926e8764c4de7b302bfbc85aaeee0026694632a6d6fc5c00f6ae1cd87a5519c7b4e51e6b7995be1ba7fb360fc140ba1e788de2e283d937460efaa973c3bc49981ffa1911159046c78362f2d5f9e0eff1c5320df8f3c01fc604ebdf93206e8723bf5e7881d4139a0509cbc16a2d887af5bdce2156f8b5a30eb7d02bc1c6fee1a2ac7e79b8cd33b2b3c8dbbc1eaf4aeb7a554a4e2dd72162d59bc8ae4af951166b8bd6d7bc9609386a5" + }, + { + "public": "65558f45fc9873e7ccdb4baf0dfb6d5e7c486f6f5a596389241aa0d47ab198e5e3c1a2581ef3bd6a75d2bdc34f4f782bcf52a2d8248203ae9559fba2304f9799b446872ffcfc6eac2a163984017c0a57988086191c70df3ba5d5e5452cf7e28fccaa9a6d385789cd02177f298501885ede6d40de51eae07ca803c17fae3be52f", + "secret": "70ddbf3e5c0ae2180739e62ddd61bc086bc8138ef07c987e8389e24d5342874e23b0ca26389faae9ba5a53e14bf6a4348adebce0c3f41e8816b98e9543503df2e3c1a2581ef3bd6a75d2bdc34f4f782bcf52a2d8248203ae9559fba2304f97999565b08ec46b78cb69ec6b4f484a394becbf6dd61ee3aa15404bbd81ddd43ab3a870e1f9b374583684f7edc1dc3f93883479eb3724e6191e8173ba6a1dc4e25eccaa9a6d385789cd02177f298501885ede6d40de51eae07ca803c17fae3be52f" + }, + { + "public": "b5a7fa3f3a22ab4638551708ccc3615e9a76b205ed93c09463f9aae446e68a3ad210a8b95f11d19942d1b4b4f164cc022e3d0b886917cf4f546e9a6456ee465eee7b52edfdd84279e7ebcf078d80b20b6cc185a297abfc341307dbb6fe0106068024b63eeeb75b4ff165d5a83b4d75ddd2b9ff0c3fda3b9b8a7d90dee0d6a541", + "secret": "3221b0a65bbdf9253b6c2ee818170a242978ece8ee0f9eb6bc85b01f9986826edc02ce795b7f948e741e49500c2f4b56477506f4c72ac808b1cccd57c6eb013fd210a8b95f11d19942d1b4b4f164cc022e3d0b886917cf4f546e9a6456ee465ed4bca8da5e2c0b14d29dd606dd800e76b94fbaf00ffd35f94e93ce18a6b598b51c530b25e5101bb6927a2701a28e61e587945cd7e2ba4dc68245191ee4e4f2b58024b63eeeb75b4ff165d5a83b4d75ddd2b9ff0c3fda3b9b8a7d90dee0d6a541" + }, + { + "public": "b92335716d74a3ee8b4c064deff017b23d2c0a00bc9a31d088150ea336cfadbfab278be0b714b0fe3de4ff8b7a011ec5cdd0bc7fa132a8af9d9cb6726efe0a2aa3b0e42aa553e0df914698481c5528fb1ef33a2eef9fc06564376756ee643b7eec1a500bb664a350d9658b1239be853a9dabccc01a439f0b94fb2566f4e229ef", + "secret": "563419d8ba2150edef50dca70c80c90ac2c1a33f9ce23ff787d757be0f03475460dfd7c1976546d24fb8c5cdb1755709d73859e5a0f50d637f6c1a8cd992858dab278be0b714b0fe3de4ff8b7a011ec5cdd0bc7fa132a8af9d9cb6726efe0a2afa6c32690962869600d0c1b68bc98781f32b8f1a09cd6db6f66339838f571b2a3ef53afa203d28739176baeaeeeb051107f179d3334db465b0761269b55ad2b9ec1a500bb664a350d9658b1239be853a9dabccc01a439f0b94fb2566f4e229ef" + }, + { + "public": "136c83b716d89459457f36ab1e597325fbde68c529abf3b8c7b483ac8f50ab798e3dd3ba1fda544c6e3044b18707c8f163f183b7fa9efc76aa0a65210b6892debdf8cd139fb45f7bd4c49ad37f1ca60ba4cc09a50d9fef2ff86dd3efb449a82fb1c1a31b08043cb590a514b1d749237c6b3bd77a1d329b5af815bf93b8041b27", + "secret": "8d8689be437ec45e1645498e1823fcd9e55f9392add882bee8d0ddd1bf21c6a9d44f90caec6dd9db3e507e15b1a4a2a6affc46d80a75195ef7edd94777ccb2858e3dd3ba1fda544c6e3044b18707c8f163f183b7fa9efc76aa0a65210b6892de0b328e16937c4226fb23645df9bd776aca632bff7358b4f865949e2776a3d7ec4243717dd83eca8ee67a7ec9255ce7e4bc958e590bd21f12cd53e70159028598b1c1a31b08043cb590a514b1d749237c6b3bd77a1d329b5af815bf93b8041b27" + }, + { + "public": "e9443f4998975797f4c73e3cf3a0fe69279c9834ca935312ed066092d79b95a5a7de9629972b74e1744cd171903f32a3da4e3fecb93108541cc5ed065f0cb705dc6decc1183c68ace9572bba18561158e1fff9029ab2af20609f1106fffd435feae8dfb39e9e1ab89da11bebf5cac6cb68ce31b2723c33fbae7797d37dc0f377", + "secret": "3702e0fa4e9b1b7bb341aa2b07390c171394c2a033a64972186987c68f0854826f1ccce7e63c014393673f11e1522541875a9b9ae0f40253d9c9a0b07627bd5fa7de9629972b74e1744cd171903f32a3da4e3fecb93108541cc5ed065f0cb705d826619deb97aa7fed308121d0ee2bf235af2efb0a6a103576e8ca72f945e084f46162c8e15f0db8b8aa2ef5d7f08ea66d074d6e22c17d0b92dc7ed0d0dea5e2eae8dfb39e9e1ab89da11bebf5cac6cb68ce31b2723c33fbae7797d37dc0f377" + }, + { + "public": "fd106a25c827d23ab65064047bdb790cbc70e9617607791aca3bf6c503d1c660b99f429ba3591336c03f57b757a71f70a7401958c8670012aa33e3c45af684030a9da3a68c9eb7bf2b209504dda8e7ec2be9732a425bcb7aab0a12604b340f69d46366382a344f21f22d3d114d3eca0c17f168415c2767fa6fbbf821d63d4a0a", + "secret": "2bc0ed2468f5b9be25e31000957fa05830ee3e8f857c2d093ccac962ffe9da96f8035e1eb7c4bb02eec4b5703a87f9087919a0d12537b2bd2f8aa5719b8012fbb99f429ba3591336c03f57b757a71f70a7401958c8670012aa33e3c45af68403bfbe4adb45e499ff1b7a669d1ecbe4f0346fd23f5cad44039a714b10846afbbcab692a67c7bb2719c66cee7d3d826d766cb7220c968ad70c6a97ef4061e830c8d46366382a344f21f22d3d114d3eca0c17f168415c2767fa6fbbf821d63d4a0a" + }, + { + "public": "ede3857db65380e127be94b9dd35a667e109162693d9dc121a1de24b187ac870942a09479a5d60c9dda37a646a37029fef6a7b0c8d32094bd49b9379afc820ac115ab2a16e9936756f7b9a8fc8b7cda65dcd15e3099efee4d5d6c38978188ad900009cb0ccd0d1658dfa7386262a976c6d2b72710df7f35ff1aef9ade8243764", + "secret": "fb577908a1bd270e8d7408c7ab9d008ed30340fbb63a745cd841255342526879af646578af4f97b9ca6f14dffc50b0ed15e5330607db11f8e9e3f8b646b1aad6942a09479a5d60c9dda37a646a37029fef6a7b0c8d32094bd49b9379afc820ac5639aa0c173d044ef3e2e5e37e77833cfe85ed444a65f72c1978afb93a9be39467b435db3dfe2beca60e0618e274916180e7f56dd7ec363f8afd6698eca46def00009cb0ccd0d1658dfa7386262a976c6d2b72710df7f35ff1aef9ade8243764" + }, + { + "public": "6ab9d4371d311b94956254736b53a375be9d4ffac080a340545b82f16dd0620ede624ad18f37b0ed46e69e05407940abcd773bb4541d7d03fd825c85094b2487c54445c4e1105827c34c728320a61522f78f297c9ddc78eefda599f11b41486769bf166536c38935a22b942c5d92baf8c213fc02e82cb523d9ce9f594af2e895", + "secret": "bd0741af08334e65be027cc29f6642ab5ed576b4dd6a3535876573502a697b63b409026981066095eb1b10305ffa8755807b5c1b7f84d71b78c3a13c19bf5275de624ad18f37b0ed46e69e05407940abcd773bb4541d7d03fd825c85094b248758235b5417359fac81656b24ad19cf8021db069a4d96edd2d8bd554e9fcf6379b6bca93920df09a555a6510f2a9c49fae7f607b2888e7040c82443ef4f73d3c769bf166536c38935a22b942c5d92baf8c213fc02e82cb523d9ce9f594af2e895" + }, + { + "public": "069f46766f3beaf40af6a9a8c831a2e96f2c212710d5644c4ec19fbd378262dd39073d7f98d56d6c189a2d6fec1946ecb822249c6b9751d01d179f8ee4aa3b0341276799425665e14c4f0c04518e76929274e9b5cc9e23f3f231bd12ac4075484e4d6349ba11755e139d2ab77620891ac4e360067865603b36bc1c13a331052c", + "secret": "5821835fe9d07ac9e3ac5b534c2e51ea149d8bb83972469045a891ae659bcc36d6c03b194a82025f231b7f824d5e85c239623d1b1c7081a6d949916d8421e9f939073d7f98d56d6c189a2d6fec1946ecb822249c6b9751d01d179f8ee4aa3b039c00087d5d01160e57bcc358df3ba02f8ce1d8c37246aac4d233d876c51f195611f16a223f57557fde950eaa8ccecca6b8d536acaf9dcb08bb96dfdf252be4424e4d6349ba11755e139d2ab77620891ac4e360067865603b36bc1c13a331052c" + }, + { + "public": "d5eb784778f6c02b5825c872fed952a2652577fb1d8cdab93623a5b9d7b658c82f5a500a0ae0184d08d853320467f220b4f154d57844f3b0733634161894a21d4edacdcf4f79ebb1e57f48e67e6b61abe233aec1a6b0761277787612e392669a96378535bc65c55e36bdf23221bcc2d6f83cfb607b4fcb2eb57054033129e623", + "secret": "d8acfbbe668b95391230a2a890194dc5cef4187c3e835b5249e7408778059baa83f38d0d1022ab8ebd9af9439b100ffd25f3313023a5c525b1cb580e9bd2e9f02f5a500a0ae0184d08d853320467f220b4f154d57844f3b0733634161894a21d737edf0af8efaa6e9aa9ba88ea0bd8d417ce1822a137b96efd68350a51936f7edf0a5dac67cc38eebb3ac81b1727760ae110c2a9c8b99232d9822b7c67b6a46996378535bc65c55e36bdf23221bcc2d6f83cfb607b4fcb2eb57054033129e623" + }, + { + "public": "721358935122c0458054181624e2bed00bfa67e73717d04d8381de70c6aa80e54592e08854db7fd640ecef6498438c2827c72c75e2232ad6c0019fc660a0c5813cb8079585e9031ca095b2cc7e3faec3c7dd980fcb87b057b672d0c2271cdbac81a2596f9fecd34402ee9755c926447978719f99fa20fbd419280da3c68abff3", + "secret": "95c630d5ea29517c78daf0615006e04dcafef52e85e4f58b07d2db4b78f923fb77eefa8f7ca0f67e06f37e3d5962a6d68ae36e80c1f87effc34d83de22e851a04592e08854db7fd640ecef6498438c2827c72c75e2232ad6c0019fc660a0c5815749c9b5710f39ff9326ff54e8c1785ab00f7285f2f1eccf268c24a16cb055973c37de28b135f136609a0d8a1fe2ac288f50e0ee76ca5d6c41d86ef713026b5881a2596f9fecd34402ee9755c926447978719f99fa20fbd419280da3c68abff3" + }, + { + "public": "d7b6652e15e872689f14628c494186eeb8ad04f4e86fdef5e667a7f6a1d0b0f534a79774b55ef20c6bc5252d051b7350bccb73460b2e40112d4ff8697e14e8b9bfdc82f7c6c23d53c2774a5e8f62a2945127e85e00269e6d802a5e2fdcbcada9f7b65abdb441493932b25a79f97218a917d619e6529fa9b4bfd79206add388ee", + "secret": "b9a053a7abd68ab3194f31eaa5f774d8361d5ba55e47f39b913eb0e304ad5e15935523e90efee7b6ce0d49a0f4cc48a6e96a41293ccf2b55fc2e4e3472d202e534a79774b55ef20c6bc5252d051b7350bccb73460b2e40112d4ff8697e14e8b9c2daee98bd0165bf3939666e7c890426c87468e03e3f2f50e4973299efc96f399d49a924ef93eaf35731ea31b32c866fb1cf93c3a3328d6eff88a7308679f887f7b65abdb441493932b25a79f97218a917d619e6529fa9b4bfd79206add388ee" + }, + { + "public": "da84aadabe2b618478b951fee663cb237566a8f4b05221421541785761b1d4689a845aa8caa0104ac13506a73f492d4139b32b8abeeb93ff1b287980c2c80417b2c29324ccdbd9e394b9f01b964e19b3d9f32702a2a98486f23410a1ca73881a66479a32efdfef25e80f740195184a0c30c5b7a3b7c0fe291ffc2bf5a8889f56", + "secret": "7f36176eec835dad7592438deab0435ea2a9fe19b614ce4bae5a21b5cbef742781ae662c92b3ecd17531c42e8ac70c8ba637e718e40ea869ee115483eb8a04029a845aa8caa0104ac13506a73f492d4139b32b8abeeb93ff1b287980c2c804174ae9983c8a6e41fc0620f42a1fc17ed1fd3a17349f9fe44825a8500e1b6bae20db7e5aac55441e0d1468303209937c6f1f57a5ae5e89c426d3a23094aaee0e6666479a32efdfef25e80f740195184a0c30c5b7a3b7c0fe291ffc2bf5a8889f56" + }, + { + "public": "44d565cadecc14ea7ed8aab12d3e654f174d22a353c5ce3c365ae379cf7fdc9b79aa5981c12945a2359493ab9ac777c63496b25f6e3e3094cbcb7701102ef639c959c4345855ff442349f2d7e137132dcb72ca575ee4135b19fa73c55d8e92a76daa232236e19f9c0c958d4faff801ec78ff040148d3b77001795dfaaabc20a0", + "secret": "bff5af8e8460470f62fdbbc56ea41bf0465e20df0ba1832e014124dd2ce0f31207984e1df60dcc9830606e3ab2f391271be97ce0202255d4a5e35f50944d639b79aa5981c12945a2359493ab9ac777c63496b25f6e3e3094cbcb7701102ef639993e7130700f31551ecc87da814e8e6e9db67bbe6f7cf9ed0d98b1144c7ee21ce5ed8fd8da99a39482b5e68b327cb9dceffeb8835f98e88d5b857668be500db96daa232236e19f9c0c958d4faff801ec78ff040148d3b77001795dfaaabc20a0" + }, + { + "public": "c26d72b2f6089805ef9e6de6b900f01f39a261dae437fe454d193065315d513e9c754861cbcdb884c975d219730bb50835879e129e3ad4292c5c655c426339398ebfa6677d91113b65c60e5b6b35a55164ab614c050a83ea214186d97be11f68146e07262e947fa52a9c3388e55023ab5c98099c4d7528046b51aa34a983e824", + "secret": "040a41b954905abaa5407a95284b445a194f26708e02e8188dde7b1e89d4d83a9e052b02ee4ed8de412126bb41b4426a45c93ef5ae97a8d8706866516448bee69c754861cbcdb884c975d219730bb50835879e129e3ad4292c5c655c4263393933830369b33fca8b93b8748ae660412ed9802ec0adfec7218bc8089de0aff319208a715315cc8db3a552c6cbbeea0ee006b6a81ad8291f223282242c9ce5911e146e07262e947fa52a9c3388e55023ab5c98099c4d7528046b51aa34a983e824" + }, + { + "public": "669c814fcad9df4fdaeab8061c020e9fa08c5355521189ed1574472194a823ea567519bd10400aa689ddc674d1ccae8867335f73554d607298f8e521dcfd6ede3b2c6c0b103341accc2669b85ed647673dd76ddf877f60967a8367bfa10332d0173206dff2f83839485fff509c07f5d72bd8809d230cb9e83f61b67c6b37513b", + "secret": "094fac2711772ff795a4947c3c0c34cb98d2941bdbb9332389b3b3a18b930a6f63928a624cd99efb8d31ae26acb06eac277da61048624dd4b55892f4697d2bca567519bd10400aa689ddc674d1ccae8867335f73554d607298f8e521dcfd6ede7478047e4fae649048f59b5abde86298362951df49bd82f0b2746354ac4e89b1cbede591721e1bd7f7e6502f1962d5c923258d2888dc99575616a2d7720118d5173206dff2f83839485fff509c07f5d72bd8809d230cb9e83f61b67c6b37513b" + }, + { + "public": "2a007942f85c95fefd9c60703a26260105391f1d5c195de4b413e2d385425c3ff21377ba73a9c87af8f283e7ac4fe957274902c637fa5258a0637f7e0340ad12ddd9fe5c76da41f6ef2beac2a606415d8eb828c22ca31bf4fa89bfd27d3fd592827588e95c7253f815a624244913be16472ee4129b99382b96baaa6c80e4458b", + "secret": "424b587b90e9178992e540af2857f156aef039c746893f8025a67cf8d0db98d26203dfd12b298aa8734f54b6a17e2eabc23b9a89bcc0f83b19daa6bc58c87cdff21377ba73a9c87af8f283e7ac4fe957274902c637fa5258a0637f7e0340ad128c2a5bcada918fdd0430549c7c4228c0459732c66827d21bcb7c82d87a6398660d9e40f0d2a88c871f18efe8a582ba6bcad630252a6028c19eb2ed94ecc3829d827588e95c7253f815a624244913be16472ee4129b99382b96baaa6c80e4458b" + }, + { + "public": "f41e555cb15c05b61db6ded22811d5aa48185f7e6c31b38f777b427962ea1bce8d64b3a6b0f0121ae3a4e9610e2e354e674d2ee88c64f83045469e14155b764d3618b7bd0362ea7aacfcee42c22a89cecc090a6e34e07425762b4db021b46c8a9a39535ded2acb3825945430e2b01ac8bf7d5f976150e1fe4d7fe13023114042", + "secret": "a6eac09cfa8b6803b382bd38d59460802b1d88b26081f2fcf2e72593d691df88d87ff344275744c4218da633e2e5ffeb77f31d15706546048fdfd21fff9184248d64b3a6b0f0121ae3a4e9610e2e354e674d2ee88c64f83045469e14155b764d2d598255f8d90f6fbad3fd6d894645dbaa336057feca970980dcae1dc1f5d25ed94ac747f590de431296172a57b8c079bbed371af306a5a09425c8161ec152e19a39535ded2acb3825945430e2b01ac8bf7d5f976150e1fe4d7fe13023114042" + }, + { + "public": "15a2abcace85cee23f212447d1830b485817a4dd94808792cd6a2f9379ec5386fe34b01d392ebaaaf2097d32694a9012a3986f9a906c32d2e934b5faf0adb8d399cb27db74bb4833a770e6faa0856a43c19d02861a0701620fb57896e330597181aba18b42a7395f74cedd78525b52d65590060b318e32035818bf5cde3bb61b", + "secret": "73ef2cb937bd9b7abf36d457e39cd9695ba5a4ff68c6eb7f26f8c43b410786daeb495170fc5a57dce70036cb9ad35b41eb7279700733da1c39478797e6ae4a5bfe34b01d392ebaaaf2097d32694a9012a3986f9a906c32d2e934b5faf0adb8d37c73a5d64615d2d5edfb0817f1a4ca64553706498c8edfc6a8f7eeaa8d4b73c6bdd9a89c0198c30d6264fff378c0b700b9f9cc65b601005068cb4b78e150249181aba18b42a7395f74cedd78525b52d65590060b318e32035818bf5cde3bb61b" + }, + { + "public": "41ef40d94d5263890a8e3cafa7b674b6a3f170de806059fc44f61ab934a9b518f3a0f899cd310763bba683c1464a19f30e0c63a924bc4138d68393c94046e7fdd9051d76ea58a74d48fe7910df3257722c3abce464dbb2cf22818438cc623279f0b53f124b036e484b62ad8cbe98508b647849297b3a13d6c67d5b04897f1154", + "secret": "7f7cab090d5b1b6465a5e5212ad13f991a49693ab2b61e5e3a1fd3e7a56b5a039b403604bfa5c88a3d1567c799186f1805ca72f89d0acabcef02bff081697efff3a0f899cd310763bba683c1464a19f30e0c63a924bc4138d68393c94046e7fd35b175a3580d599308766e356c9acc927304ec327b20b43147265cb6438d002ae874d6330820b158613d77ecd20eac1d18a7fbad314dc3de0b0f3b04f07c6ce7f0b53f124b036e484b62ad8cbe98508b647849297b3a13d6c67d5b04897f1154" + }, + { + "public": "cadf01cf3811469fb79fd98de289bce4ad6b6ab8ba503b62060460991bb746271a98042d8e7fb14809967d2c91f147ab7baa067c8b361b41d965408414fc17590282c2f3808f55fc9082cc9adc2a9d9e3263920feb8ee9b286c716e87ff3a9bfe428491d805844c45c501e83b867f7750a169cec3177b65e709b1704fdbbc93f", + "secret": "79ab3c62b6b9de5c2d9f263adb734831ae5522d1539c9c5e85b9a24cfddda149cf29567b65c3e66d640f1d40d7c1b15b5f05490f57588c50f287fb558f3251e01a98042d8e7fb14809967d2c91f147ab7baa067c8b361b41d965408414fc17598d5ee1d44e305c24c1c70fcc4ac70bd2218469d3ed6ed0df2754490f7df7bbe0472e3619da34fea946d41d3f2e0e19974ce0d4c67042d22345cc308267224e38e428491d805844c45c501e83b867f7750a169cec3177b65e709b1704fdbbc93f" + }, + { + "public": "e299c7d11155bbdd9e826aa15624f0621675ab69bb9243b90fb4c1770b35a19cfbc8b12899b5a463c82fd828f36b697c86518e1706997521d0a2677906b10091a48a2d92a7943a3a20001aae3f5fbf70f5d9d51f4707305bab3f58f9b08939a685d6eb6a25921c744ec573ac7dad97cff9d003bf30b29db9152fc5936723dc31", + "secret": "f6e7a9286cd200dda82cefba1e30040b9a07596074dd217be147c35e5bc53492cc5b76bc27196c57c1bce7bd59f410a7794d8b4c56be16ae18d79f887bfc014dfbc8b12899b5a463c82fd828f36b697c86518e1706997521d0a2677906b10091857f112242719de708e0fa2e13b003489fe802633af2aadb9ca2411439b4209f517d9178382ffdb8cc58f6b75c1c9b50da4ee833650bc96031bca910eaacd38d85d6eb6a25921c744ec573ac7dad97cff9d003bf30b29db9152fc5936723dc31" + }, + { + "public": "a0ccb0e10022cc8b688ccda14d8524dfbeb7a0c1d45e0b81b9de55950c55614680fce3b4abb8d412107a4508fd3b3e93293ff02792118c2472ced6e9c9d07dbcf241b053b924664d84e8a8d03a16da861057a54e9c7503bb492bb878faef7250afbb6b4d4c0b1f252ace2d2785cd3240345f30cf67dd628cc800d78a468709a9", + "secret": "0d0e5b949b35e4da15c694083eb3ba9228dd13432831742169aad15cdf8ef241a7b2096d8e700ced1e9631e4c79414551d1590f288fa40d4445831315b39b1b580fce3b4abb8d412107a4508fd3b3e93293ff02792118c2472ced6e9c9d07dbc338d327c0bee683ac16311918bd2e53d37e1a41f8fb9e63d7134d9e57298f36472ed44f5fd105db7a052e30e3f9bea2834de4c5220b4b7e54bd9546a4ff52f81afbb6b4d4c0b1f252ace2d2785cd3240345f30cf67dd628cc800d78a468709a9" + }, + { + "public": "22eb3ca1ac8b010670823db79af589331641677908c6b8c990866344686a5f7e3eaff0058faab2cf170a3aaea520273d917f96208c87743584d6b9577bd739c4d8b44d2c40efcc517f16b8304e55f75038bda56f6152f2c21527c5fa3d432b5bdf414120c19b0d512573a9cb880608ed1297cb415e686d34c8829edbedbb4e92", + "secret": "55b2ec69ec8e277953d6e8f22c252c51e00e3b20b1fc9b48e5888cd29f93fd86fa1392294fc31d5ece44f9f114dbe4413e0aaedeabfdd9a47af8e2aa19e660f43eaff0058faab2cf170a3aaea520273d917f96208c87743584d6b9577bd739c4c72ec327edcb0398d7265e8c73a514a3c9216487344e79a3ec7ab9160d91cda6f1009273e709f65ebe033a8bdd2040d9a571490b47c3d665fe666f336a5cacf7df414120c19b0d512573a9cb880608ed1297cb415e686d34c8829edbedbb4e92" + }, + { + "public": "e9896842ac49a7fb3b09d73356700668c302a8b9e3f7c1737238629bb74570b6237f46b5adc797ed3878efcb39bb182f52cbe1f949cca06e90723255d37b0b6cea251c95bbbd2b2dce376f746829c26f90771a497e0e6fe728c1cfd5849b7e95a273ad1d7f10e523e127a0dc3ac108e8f5c14aab6ebbf529c114af4dcd976cda", + "secret": "b55501b10623792e6795255e5e4c5a5eb5f04c15e56f6b26563efff2066d0e4c2c3240fb1f0a1f28a9d7e358749a06b13e4ff101fb9fd31a7a1d18023743eb58237f46b5adc797ed3878efcb39bb182f52cbe1f949cca06e90723255d37b0b6c8ff76cca1e1dd23d67b8899ebddbc44362c4a166153e5fefc15c28c3246ee2d62b704ba76803753b9122aae25130bfd8246274f8fb4bf953f890ebbd44f18429a273ad1d7f10e523e127a0dc3ac108e8f5c14aab6ebbf529c114af4dcd976cda" + }, + { + "public": "21b1499889f888280574c98037defbf4803b28a5b40db65ad6fed0cfcccd43f82505fcb39796b29e1435b3110a0f2c708155055432625db2b5e843420dc7e558b840bb1fffe2e361fcbd1aa1b1eb80fb7cf772a6e9e3e432a8e8115a931dd98bab1498984f1219549151bbde43e7780af9cc5ab6d5ff75cbf9d3df501c138fed", + "secret": "19a16fedc8c9fcf9b828276262c81ef17620b37227dd38d0cd295d0a14008488728a61b7f40c65e62ccf202aa4b4e76d1651b2ea885bcb456dcdbb4768d0cfa72505fcb39796b29e1435b3110a0f2c708155055432625db2b5e843420dc7e558d24570373b84ce398a79643370d31cf44b15169086a1d69568e9c0ea19d6fbaca2456e4857f0f66cb7b2148c7e1fb122325dde4e0d41bab9d5ff8b289e3ca2bdab1498984f1219549151bbde43e7780af9cc5ab6d5ff75cbf9d3df501c138fed" + }, + { + "public": "c59d85cc8b33477ffd28e3e7f6d9f6ef066f74e2879cc634a9edc8c8284e6f0a04411b5c03bdf6e9f15aa3d76c1689326eb99952adf72e03acba5bb1cac7b38ebedc4a380d054fc79ee5e10ceaa85cc3ce3a543ae7794f29363cec51c7423eba795f20adca50ab80adb47874670a78cc8fe1b63506d42f9848b35689b3779105", + "secret": "23a9ce00603b1187f7ce6057c95b4d219b9b2b7713d9e83acd257a17a5fab1d3d5185fe12ba1836e0bee44c3f6c773b302cdb51fd24208ef3844a18354b8ec5b04411b5c03bdf6e9f15aa3d76c1689326eb99952adf72e03acba5bb1cac7b38e28bc8ea17e45aab8d82d9dff86ad21ddc61ed9de032aac930e5f17cad7e705ddeeacdad3f20a6119c0287172bd900a8f8f6ade24cbf8913ff83497bb7b0f7650795f20adca50ab80adb47874670a78cc8fe1b63506d42f9848b35689b3779105" + }, + { + "public": "2e484ee3653ffdbb22046a413d086c665030f95cb01b76949b2712b3b3a1e2a641db90d4d381d4f3d524d80fa6ad926632b5b8f981c76700fc4be7dba8d1980c3be23373e4fac84b838af02b752b91cc491226ec841fc0f32bbb051442b3a1b5eac1c9b47f4e3f7df3d9ae40cb142902defea9455c13fca953276cc7d2bc256c", + "secret": "b086e0fc2eb879239d9107c1a5fa1cd59690206b057f53eacd37e0f24714983f2a2d91f55e3128bf9c633743b7c26833c812424e644f531690302fffe81b061e41db90d4d381d4f3d524d80fa6ad926632b5b8f981c76700fc4be7dba8d1980ce2ab696ce667415fc9b0b16372fc5da597b950f13b4ad03567e8ee616fdf6c382d6c969d03c9369a70f8074a7577d5392c6dd6b93a60983e2200ff93eb6c77eeeac1c9b47f4e3f7df3d9ae40cb142902defea9455c13fca953276cc7d2bc256c" + }, + { + "public": "e51f7d75d531f26644d3e81b0735541bd843737c7ee0fadc133926a76b5482ef0c65977a8486fa7e58b2bf05d038e99580ace4f00930f902a3488b803c2063629a055f820374997a01d6c9a6910843189c51a3047e1358f964953c9489cb629146b92d7315096cc9c86c47f126f4aadac5df2c9a17d23980a9fa2453e83e6741", + "secret": "f5d9c0280080c4ed2f3172b5980333773266b9f7da098b2402590a422513e27a6a2cad650207eeec34f590693a086cb8a1413176fe2ec54bf8a03816bd9bdc160c65977a8486fa7e58b2bf05d038e99580ace4f00930f902a3488b803c2063628f4342ac36a6e9ecc521f0b0526c26314e3f49b1a78e5c8147e0c0d80d91b44a9abca50287666f4aa069e2c9cc78250b456f5007238d688eb938d44062756a4046b92d7315096cc9c86c47f126f4aadac5df2c9a17d23980a9fa2453e83e6741" + }, + { + "public": "dab2de48e36c6e4ff3e43eca3b0d4f88a92efe8c74fc9a917faddbf5a3de40a82c3a073df21376296d67980680ce3ac6f922d1a9710b1e133f53e97306118b371418e86741ac7768940cc6233a59d009e2a31b713a59a530091a7a1204bf83865ebbfcc832e448acf346d3de496d26086dd50d88191d596ca6d1cb524ac1a9a5", + "secret": "290d0953ef6b61bd75bd7426f8a6e1e9b3dbb13b852f14774cf140aeca8bef3da58a0f589b39eada14c93aa4f9116f9c78c1f7d06c4e37491b80aed26585c49a2c3a073df21376296d67980680ce3ac6f922d1a9710b1e133f53e97306118b37447dd8804eda50dbcacbe249ffeb8b151093194fdadae1c0e0a369359cb958d3feca5174711c1c6552ec881efb4a1d7078e000d7a2efff4a8f378779f827f6995ebbfcc832e448acf346d3de496d26086dd50d88191d596ca6d1cb524ac1a9a5" + }, + { + "public": "8f04c1b6507836daca8ce4d16160f8f823749a7359856c1b81a82cb2bfbbcc62659afdb20f241be33762d2a09a0a7768e670ec0d540a7261138e7a5eee89a92422a69442e757d5f96bddaba97325a7e6085d88e82b2193931aa7ffef5cda214d2919dcf116f04d87352f8c215dc7090011b94a4432fc302caf9f9d6684989a36", + "secret": "ea5b6b6d46046b71b7727aec4e37d35529939087baa6d5eb4cf826c00b814dbffad287b63d6778ce5611922e66d106a08b817c70406723a29ec36b9cb8698d66659afdb20f241be33762d2a09a0a7768e670ec0d540a7261138e7a5eee89a92483f916e83b4e78d448f69d25c42e7671bb73c84ecf7bd142c10b4605473cd7a18f4b479c0106c6b5624ba57d27682eb2e143662e0e1464b60b161b7fb90fb5252919dcf116f04d87352f8c215dc7090011b94a4432fc302caf9f9d6684989a36" + }, + { + "public": "1f800fcc0abd541de71ace9d633ebace431f833134f36b41e3d8960e5da959469bc6543c65b9e56b24d638b950e96404f1130ab1c98bb2236f98bb0b35d1766d472ae5ffb19ff36c070e1091626d0ddb4efb7a81cbf56384c744a25df8fe02ff3d4acc0338c2ae8f9423db968259e0c680622ea4d07b6da158a4db5af49ff580", + "secret": "fbabf4d76f9b4fc84e20aea674afe473a535880b38db0d36c175182a224110e91b7895ec4f5222a9890403525e4a1e8b61d0fc669550b2619c70b1b9bf03f4889bc6543c65b9e56b24d638b950e96404f1130ab1c98bb2236f98bb0b35d1766d99b8fae56a3dc36443f0eb856ac95e990fb955ce7ad5156e58f92127190e90c41f82477aec896b7356fbcea5df189141fb6a9b17eab3bbf1300a68aa7744d2123d4acc0338c2ae8f9423db968259e0c680622ea4d07b6da158a4db5af49ff580" + }, + { + "public": "e8018473e9126fd565736a8afed9ab79bd7a4c047678163b8321cb2ecdf337f428ffe26d8d4914ce7f86f521ac9bd07bbd34e639fc5332e833b17559f4fb3f7293d3e52a0496622f7e1889007ddc268b456b4ff35b094f1ea4e4b5ceb3b60a11a2becf35382815d0f9318d4e41f59bf0895ff2ba8b04b20f413bb5fa2d2245d5", + "secret": "68cea38bc86bd21ee964488de7ba51171dde98b716eee062fbc103c178c50faa229381acb2b3938720a94c1c4574067563891c467d0493c4b4286dc136f4581d28ffe26d8d4914ce7f86f521ac9bd07bbd34e639fc5332e833b17559f4fb3f725bd591c1f95e7551f09c830df058021c2605ffede85a0605da62e31c762638b8251c1ad65c2b91720da31f9c8d507cf411aa4d23062653450ffca276e9f7c00aa2becf35382815d0f9318d4e41f59bf0895ff2ba8b04b20f413bb5fa2d2245d5" + }, + { + "public": "fc0e3afa2aa6bb1d6c5851b91a6a5a704ecb603635f757658cffc8ad44ddb7f153b3add44ee2de1b12a316b974c8bae5720a9c5e5d7f34941eb1d2d6ddb21cc9a9459eb200791c5c7b288649954d2f1fab48eb7a1e89f50cdf18bd36de7aaf0e3f0c1cc805565094e7052e0b5bb3f0a1301c9107fd126a4e51babfd0e45a277b", + "secret": "336e1525de0606ce8db78137389159a835919b14ca2a5ab5bd887b7e7182bf5aca717abaa55e81b893d5ab8033f0a124d8190ce5952c005590449d0ed0a364d053b3add44ee2de1b12a316b974c8bae5720a9c5e5d7f34941eb1d2d6ddb21cc9a5740258f663a7e59fc0edd248b5a8af5ca634a40d75f1d2ca0c41bd4969b68fad742fa5c9f2e125a3c9df5d20e593066a59a11108d5ec3fd52ed20c05040a353f0c1cc805565094e7052e0b5bb3f0a1301c9107fd126a4e51babfd0e45a277b" + }, + { + "public": "f211bc943104008e7ccd9a53d823b78e00572936665114e12293450f196f59e2621c5e27803d78f38f4a7f1e5ef6e2f0150b6574cb141b91aa9710b739d63b2683eb52bc1bce1734e52193acc84bad854f52619779cbcd6088c29efb74e41539b3a31d5dabde3c22921a9bf05c64cc6020d97b906c78541d59214527fe2a8e24", + "secret": "e7a4f7169bb2575a44cd76431f32b8d31238dea0c98add1ee1119e3797c34a8541add019aad2aba789b39be42e01c6b0e79f6a5eb0b5e2b0887398d864de7ee2621c5e27803d78f38f4a7f1e5ef6e2f0150b6574cb141b91aa9710b739d63b260531f22961c1decd5ce42888f3f34f7b20f18b47626cfd8bc91211adddd53be60591ba512616a71e8d83957ca31910d93250644eeff137710652cbd7afa02a5ab3a31d5dabde3c22921a9bf05c64cc6020d97b906c78541d59214527fe2a8e24" + }, + { + "public": "c3ef1f2f5679ce64632a9a9e95f32d699193896cb04dd5a3d7766dc7a09d0923622ca30258a5fdf4db825f82b3d6e2b916e5e18dc51a057695f61c727b4a99b501b52aa531c1b92abfe0acae28eb8b9dc77f00069cd194d071b7b9bf1bd7c73979ba6fa22ce68e6e415e6aae3b59455aa6bcbb0ec4e2c8598f46ef73f99757ba", + "secret": "d629b3e590d205702ab350439cc82c48ff5b972627a2b4fc13c1dd63bbdd22e49ff7b5f480db49485d5eecac52ebc6089a1f44f0ac1cac0b15a5e993f1893b8a622ca30258a5fdf4db825f82b3d6e2b916e5e18dc51a057695f61c727b4a99b5b1d5cb1f27d2d1311d7b9a7d9d39dc0ef57afd83f42f8e50608d9a2b99aa449926b5f086f74acb995e9c76cbafe47a4a5d93b75fed6f813d7b7f1bf35615ff6c79ba6fa22ce68e6e415e6aae3b59455aa6bcbb0ec4e2c8598f46ef73f99757ba" + }, + { + "public": "9ab68eb0a9e0fc9e61d6507b8fe2e35a76961185c97391c8efe459c1fa7d66cb5f60c691d8d0cfac418b5806cd16a61994c74c69a473bfc4ce7e5ebf61373750600ddc3c206a95481086d156f76311de9e5a876e9be5864782657446f5db4b7b81e702be8a9565f8cf2715bafa3a3a67c4d2a352ac1f80a8194c49debd0593b4", + "secret": "065339fd6a0f2cac0df79e0c56be6de62707bc41435a5e839c26f9a22ce255276417177e0bcaa47a1c9782906582d4c04bc78f039bd2d3967323c60a64b9267c5f60c691d8d0cfac418b5806cd16a61994c74c69a473bfc4ce7e5ebf61373750053210201a185fc3cb07d7498c7805c93346684f552d06d4d22f6df41e645200ddd75337558752bf7418f04a7411af34c0cd71ce84b6658c274eee355e498b5781e702be8a9565f8cf2715bafa3a3a67c4d2a352ac1f80a8194c49debd0593b4" + }, + { + "public": "c8c5d99719d970fbe60f61062931f561b029f4366e54b6561ea7d16741162649f7b4a39c2b4f3ebe3509a68be6ca52b5052e3be4b66cb0d7cd9087818401774a668d8d3143ed730c8a28bb13c2c6366b34ea5b54f56acf7437696537fd7a13015953d6de2ca3378b2fa160213c0d18fbe4eed01bb445a1b0a75108c0968efb55", + "secret": "5a5bf991da7b02c035edb3c3122185ecdc28c5b06235f4b93468a5062a836b0d54d505d0e777d2ab073e9ad5c8dd8e39b68169340d35704faa86e7b7e5ffa09ff7b4a39c2b4f3ebe3509a68be6ca52b5052e3be4b66cb0d7cd9087818401774a75a3f12e2af7b35c02a6860c8b6c5e4dd842f19b9938145ed18daf1bb78655e4f3f1b53d306175582f8f31a4aec8d74614e60328b6ff1ec072d0094be9092a3a5953d6de2ca3378b2fa160213c0d18fbe4eed01bb445a1b0a75108c0968efb55" + }, + { + "public": "7c59d5fe60e581cf6383377ac8541368947adf258685631af86f434d8c4b6b7fa0dee5df3c895f363e626620d5c916fcccfcb06aa63e778e954d7dc416e65900f54552b27c3d0f14db587e55ff3391615e1f78f71e30ccc125f894be1c2236757362e4d07258a08a6d1d3d14e826ddab9b5eb1317cb004911f747fb9369bb01e", + "secret": "c0f77d41af4ec2a0322e3f2914c20ae32fc5145aab75c223744213341a82e6f3c7ef4c8aef36cd5a5abc3bc6b1009c9291a04f779d5e805a2ff46534a74f5aaba0dee5df3c895f363e626620d5c916fcccfcb06aa63e778e954d7dc416e65900f252e8fcb2ed5f2bb13d737d738d6ca9f106b4cd7035c55c19d40f1851baef58af8f5306702b772b9f478b3345e62e95684b32dd64ea0f4820ee18c80861de047362e4d07258a08a6d1d3d14e826ddab9b5eb1317cb004911f747fb9369bb01e" + }, + { + "public": "2b5fe0c5dbb5574668d29f8dd33e73f554a37b5680c8773e1ee5340660bcc8e27fafa7fcc75451113520859595e6776bc0623fd8bb865a4f4c8d025e3c78dbff5174d7f1512932743c6e1d89292c3aeebf70a0405de9a12e3305575284891b4b84738c57cb4d120eb5a0732e6e84675e687519d62853d7dc8a143908ff5cd732", + "secret": "62bcab6afe90063b9e9594f4a934dbfcb96deaf3f5861ca0f16844d9767271f8a07fd2d4fa273ba4c628b59d23de60663614522d2f02ea0b0392765642cad1227fafa7fcc75451113520859595e6776bc0623fd8bb865a4f4c8d025e3c78dbff3c7a13785266e8b0ed318a2228fe95c286d1039cf96aae106a0fee6c8df51eb67ed6adc8b4f721951dc205412684780a61c60aafa063f8b30a068a34d438e4a684738c57cb4d120eb5a0732e6e84675e687519d62853d7dc8a143908ff5cd732" + }, + { + "public": "53a70f7d9715525cc2296d052c27cfe2aecd68999ea21c7d65b2766b375ca6118311bce34e0b0a1eab6dd274a6fdb7131d221ff995236876bbf9f13ba43a792184f55270a59e7a7dfb9afb056ca2a226fc0c37be30fd3e6bdbd7a9ad66b0240a8963ec99cae6dd7d82d3a5be8ab4eae4e2eeec1422ccaf32dadb673e12cf4b43", + "secret": "3be970f40b1f9f80c7687af5bf3fd339ac70ebca8c58ff620a5a6c266b6e08b8d855f09cd695211ca0a6c51452948a82bd448e847669021094f09cc67a7a185a8311bce34e0b0a1eab6dd274a6fdb7131d221ff995236876bbf9f13ba43a7921e028e67d5b83456e50b73a583eda3691de1044e4c31f3f2d8f442408e8950ee3ea83ca20dd969881ad37b9ebd16efded05839249df3c6858259882b6eac413cc8963ec99cae6dd7d82d3a5be8ab4eae4e2eeec1422ccaf32dadb673e12cf4b43" + }, + { + "public": "286bc9494fbbc90df10edc1f950ffa2db24bd0b32de9ead53d366d51925a1d7f4e1f7d3803adf73a194e730ceab6c18e4d91887a680a0ae3803e6510e80d9ab54d2b4ddc848e6c8386254afe00780c392a37328949a6160bfccab1643c89c337257bc3b3074a94b9d94bf70254af684410a9442f611b2757efddf186bfa4b02a", + "secret": "91d57986b2a76904a7673ec12c22ec0dcf37a9faef6d78740fabe09f829ba577e1c1cc0dba9dc363f151853f9747afc168604c037a203957853fa5bd5ea65b614e1f7d3803adf73a194e730ceab6c18e4d91887a680a0ae3803e6510e80d9ab5404f5dea7957fc3c1b6cc901c4a3865fcc32f1b191f64906edced7b4f67d61df6db71ec351bf53b36dbf109c53bce4c5ae741b609dac8174f32b8fe98b3ded2c257bc3b3074a94b9d94bf70254af684410a9442f611b2757efddf186bfa4b02a" + }, + { + "public": "b7c8c6a772b1d3359e38ef5ff2fe58c2521ecfd57f736f606111f5d2746d2d7273320c4468c7c93b4c4adc0daa51c74200b0cabb18404f9f7b05a546018ba2203c19a47065cebe74c897bdc1456c26323c905acabf88add63f13eec25223bed08e77feb1fa394427642de7d4b690c3b2a203889e2598801577ecf5b285150fdd", + "secret": "e2a869ce26400f1a3d8a00b821c52954279a87b3d9400562820b12a6caa34071cfb48b724c7194578c0e039b00792a58114b71c4344cadc393a2d5d0315abb5873320c4468c7c93b4c4adc0daa51c74200b0cabb18404f9f7b05a546018ba22039ba0057882b737a8eeb8787bef796f63f840f471a5c3543a289d70cebb9b53ccaa3e275b824838d4bcd82f1e049f39b194fddc4888b3dfe6fe2f09942267ab58e77feb1fa394427642de7d4b690c3b2a203889e2598801577ecf5b285150fdd" + }, + { + "public": "7603fafad91c45ee3bf08f5b42bdb316064c33f2a6cc3f609d006d55887b69ea4ddeb736381dd4219216e2cdf3030836cb5adb59fdbbe97ad14c9565b44a62801097efefe12b4b37cd752e327c77869c5c959626af658b477650424a7732e066a9f3ee126386f606e1cf03a086e68b604268bc6c7904aad6e4a0bb914f5f1194", + "secret": "0ded4716a0fbce297475ea1b86f8cf7cb11b6cc0073ee2fd7da01599d4a3e0d1b546c0aef5397724b9a4bfd9795aa6bd52f486f502bf2c179bdf081a76900fe94ddeb736381dd4219216e2cdf3030836cb5adb59fdbbe97ad14c9565b44a62807988bc49926ded177bd28854e40e4261fe8d3949d205c414e2a000beee594736013773c1bb868c7e21dde7680d83ca18e2ca94059f33eea89c6292754f190fcba9f3ee126386f606e1cf03a086e68b604268bc6c7904aad6e4a0bb914f5f1194" + }, + { + "public": "7b4ed462b30df059c3dce823adb376f156f03f43a3ff79eb02f2b62f880e7808f87119f0e27e45a3d53ed4ffabcbcfff6c6e0dad60f391758fee22f2cec1e0579af4a280de2ed52c6ed2c9f944e7c0261e4350191a7b6f3dd5ea201f4c84372c2f84ae498cb8f989bcf3180bbe8d3a086732b78e197edb0e00194f6769496862", + "secret": "83dfe76bca17140608b03d31f985e3a57dc33ae20cf81610dd97bdc42f20af0bb3af884661e577c50c3f869d91251673c76a5aa5ab6de65d84a1a668104ea719f87119f0e27e45a3d53ed4ffabcbcfff6c6e0dad60f391758fee22f2cec1e057842f77a47ef2b31bbc3127fe20a7db423c4533621a6128a62e6e87f5af79c8ec7b13966eca54656fde46a488ba04eefd8bdcaa4784abf60079bca07545bd6f952f84ae498cb8f989bcf3180bbe8d3a086732b78e197edb0e00194f6769496862" + }, + { + "public": "6c2deb001c19bf959b95652cc677b6505eb1a7d311ba8555557baed4feb0d2ffc909259183c7f6130280c15cac1a54165ea2712d95830b28b3a3b68954606982ef156fc6d5d16cf0cb3bc40c0c215709b6cb4d7f4c86e3a98f93573c7c1280792ec9fd4dc070ded8af194ad63de40e5f796d95c6e526242b05c2b9f82eeb437a", + "secret": "2f874496b529a6165467a884b9dc24c525f36956c2145f84187d9d18bc1b7bf4c2f8a93878281d4449f84d92346c3f7f70dc0abdd3ff65cb79f4bf9b7773f33cc909259183c7f6130280c15cac1a54165ea2712d95830b28b3a3b68954606982a8f45705f5052ba54424496a3813825c686614c3a43904086c3c8074c6afa06a7385535812f48add3b96d6debe14e447b76fb43e16f6a42d052fc5e2cdd840f62ec9fd4dc070ded8af194ad63de40e5f796d95c6e526242b05c2b9f82eeb437a" + }, + { + "public": "5d90bae1d9bd99283007bc0e34638a377c8ac59931b37671d2fa34b783c6a6e3773d8218c09a43b30e35ff88b6f05c8045137bf7579b4391a6f41a0a15b80d5b5de0463c2e3a35bb14ebce2b81306cde6ae82f954371bf7938368baac8d275c2c8c3b1abcec1eaf5f4fad3fef59c394f26beffbfb178c79e71a88c0fdc2b125d", + "secret": "1bef09b4f98525872acb5f3026b83f9982b225fd5489014cc39f4ba89e91e017d5ddc026772c51582934648194f464ac0290e5d761a0499ba7666d9072422de3773d8218c09a43b30e35ff88b6f05c8045137bf7579b4391a6f41a0a15b80d5b7f9a60b1ec85b7b9f03e1e27822d98bd8170511d06bfedecb67ea5731249ce6080a364d0625f5646dba270c51011275d17f73b5d4cc447e27b987c375cee7871c8c3b1abcec1eaf5f4fad3fef59c394f26beffbfb178c79e71a88c0fdc2b125d" + }, + { + "public": "4f3e908bf64a2451e80c4800f61b7bfb83ce4f0a3a4607d2d282f6099bf8becfbf443799e96893ace6b86d8f5a339f01a5d4f078bf9eced6091c29cadb5aee0ec79e93aa84287d531ec8e3f269693a8c60c365eba4b590fedf3a9e3cc99fe99371e835e5203eaf33e6837888363f2553d1d7437903415a86e9f834ee9a96a7c3", + "secret": "1d38b5073325498bd8abba8c13f84d8b741eafa6a135fef5fc036d9e6eeaf835b752815a1051d5fa99c08a8708ea63f32eb5dcddaec99875169da3b8753736ddbf443799e96893ace6b86d8f5a339f01a5d4f078bf9eced6091c29cadb5aee0e5421be61556e6a58b02ca11181c03c4189f059b0b0cb00358a36ba244f944598dfa6ca184bd8a70fd685396441ca4a0a7812a0fd63ae66628bbb49e55345fb1571e835e5203eaf33e6837888363f2553d1d7437903415a86e9f834ee9a96a7c3" + }, + { + "public": "911d09acd06a9c6de80f9587a7fea740694cf2f840202d14166dfd3b9ab04d659804c3e7f665cebd710be265a07af0dddb5a747c05720b3639b850f7790e53a5421e2be6c1acb5e145be33d94e7a523e2020f5d8866d0052bef0e9a0081d50cf377de14bb40e9552508e0120ee6d9f9aa6c3aed07266d1b879a22c098189655d", + "secret": "af19885de6c4481931d95b678e96c614a5533d14eff33ce589835dade35c66cdd3f3c126a4c3295488b09ae3b4b06766868caa8246a18e4bb11f68e343810efc9804c3e7f665cebd710be265a07af0dddb5a747c05720b3639b850f7790e53a56dae4bc3864c803a5d5ae47e5c8688dd263cae8f58add8a29c64fb2c120dbdb9e2d117fcb8b942d80b57696ec8131ca6682227ed74a44d68edac9cb227b69e77377de14bb40e9552508e0120ee6d9f9aa6c3aed07266d1b879a22c098189655d" + }, + { + "public": "d62b6adaae2f520071380d52c74a69af7e60ec92a22ec3bb8521beb7770b2e56a090c8b6b839f3e83bd205dc605579c9d47d2c4d0210d3602b66ada62b52b3ede783a22fb3fcef035eab2643abf14855446c914d9044295a28ede9bc05874242128a5cde6ff181e62741559c82c21f226786e9bc15e7445eeaf6ad3dfef6f594", + "secret": "c6f5b841199dddb0851db132e87cc19412b4ee48a651e92f956713620f26383e2bf06b7c2afa7584e3ac87e6eeeb557a3716d420dd57b5f4fec6ead67c5f58b8a090c8b6b839f3e83bd205dc605579c9d47d2c4d0210d3602b66ada62b52b3ed1f8ecf735a599369bc587799d448899e0bf6056176a027dc06aca4eb2be9e60810c3ead1b26c89136c9c82434699bddad02908024c959c55b9f9d5112882b777128a5cde6ff181e62741559c82c21f226786e9bc15e7445eeaf6ad3dfef6f594" + }, + { + "public": "844a0b77f7ed8e801713eb8247d436ded3662879852cbe352118c92509e1733ea2739ae684005634cfddee99868d98f394cc82a3e0812603ebb405820a3def9a88844c479dad4df327b65bf8eaf4a1daf44e2421fedd6cad66cb6853fef99e3e7a958e7aea3a4839dab88b81a58069dd13add31f4de0f4fced5b7d89972f3cef", + "secret": "c1ffa1c31d9acacebf6d3b88ae282e31f1fa0b89a30bfa306cc906a4da5093fb4e17180dc83eb8b890b01f1cd9acbadf7c4b12cd8f9153face0aec17b42b5813a2739ae684005634cfddee99868d98f394cc82a3e0812603ebb405820a3def9a40ec5ba532185fbe30b73a7e5bf0ad13c9e6d174c89316ddbf300f0e837f0e100dd5878870eb28c411dfca06e6500344bf35cb7239423b1c69e192748fd78b9d7a958e7aea3a4839dab88b81a58069dd13add31f4de0f4fced5b7d89972f3cef" + }, + { + "public": "4ca57fea7817c6f7d55cb42784658c980d5966036b3f56020b03ad95190835b699933b8db3c660fd89289c36eadff693e44308b5b80093a914600addf7afd05473d2aedc002bb9a325df91e98863e42aad8861919b563148da773e78a23b15c0cf4cbe5f32de8b9a086c6db4af6edbe9a29e1ba5bf6fd6f8f8bb6258533efc3d", + "secret": "6699954cd479b5f69063becc78adcd5d8d12bf07f8b5e69b22a66f3df1667618433c0b52a1bde8248638b945cdf96ebbd2f946b10a759b2d520331167aab195099933b8db3c660fd89289c36eadff693e44308b5b80093a914600addf7afd0542ced0ce3930515132c8f6e077c1f83cde18ea93d33d05e278bb25704c1e5f5ae0eaa4ba180009ba425512578f9d4d3d3a24e5856e905c847fba3b083ee2a9a12cf4cbe5f32de8b9a086c6db4af6edbe9a29e1ba5bf6fd6f8f8bb6258533efc3d" + }, + { + "public": "537e63e1de8d3146410c0fc1df71fc126f69e0b3f780b341300d791c18054f519a854462623db0babc9d8ad36f89bd3d6b25fda6d3f203c9ded913a32feaf53fadd803c1abc8f66396f26568ea70b021228099860c9d1759aa19f78160eb514bfcfd362b6728454e8a5b5cc292a619484c109a7597545872bff227384b96acf1", + "secret": "a89cebd4eb36342b8d77a354f529aa5531a7a0785292cc7e2b26506ca6faea48f9b574265aed1550496c3d81a31e13cfb8f0e748eaa0155c52c4ff8831d2b9629a854462623db0babc9d8ad36f89bd3d6b25fda6d3f203c9ded913a32feaf53ff3c39dd65dc636b9da8028c5e11628bf220494720135406d3978329bc0ade349a9299f456c84da3f5bd9dbcf2e8c7ca76fa7ad44ab585f8807ee635fb9133981fcfd362b6728454e8a5b5cc292a619484c109a7597545872bff227384b96acf1" + }, + { + "public": "63daa600623b0727f8c0fab5dbc21e0328840d4a2fe15a7d3db8606c4db1bb5668510e3a47ed1f0660cb7387be7e07b53291901ec7f23df2d4d55a93eb0f2a11182de6cfc2bbe587ea5a6a658c1a501ee5273930e1a7f92e20d8c3b57193abb4e40acd8b0d78f212b0317ea459193e43b41f3400743b2f0a023415a3b5e977cc", + "secret": "aa43e19d4887e19a1f798c50cb2f665344a95d2a735f4a241d2bf0aa0c20ad27a558bcb60c45071d8a3178787f1158685bb5362ea3e2481699a896891a4441d368510e3a47ed1f0660cb7387be7e07b53291901ec7f23df2d4d55a93eb0f2a11cb49db5f25369787a02234c61ab63beae29d086dad6080e2d63ff06f3293c6323eebe9adaa98f1210e88fdc0b8f56b81a487074aeff0771663f4006ba3e2792ee40acd8b0d78f212b0317ea459193e43b41f3400743b2f0a023415a3b5e977cc" + }, + { + "public": "22360c769ec9f20a0ad9499db5ebabd5dc91ba7894bbe92775228524ade01d8fe1647cefe92990a4215aebb9e39d3c04b87a8d60e1e87be4f7d247cf2395ecfe61043acdc9cf7b46080699a12c9115f0185e56ed5bc33e08ba970f17965621cf87c5d611181a231e952d6992ae5f5d18051b869467d3a964b278bae363b29f51", + "secret": "9b38bc7e846f6df6fa5b237d15d558a000e43a44962c4ef49a0e017ab71c1d3fb9121e8e222f35ed2691e24dad324369aa0fdb5bb0dc10f82240086b1dcdce46e1647cefe92990a4215aebb9e39d3c04b87a8d60e1e87be4f7d247cf2395ecfe51450cf0e94c14d62e5f5c2630a17f42391a0087f624e3a11faee4e4a9bbde74503d136ecf1e04dd18d8efc2627eb46b1084a27f142add9d37f260b8526caaed87c5d611181a231e952d6992ae5f5d18051b869467d3a964b278bae363b29f51" + }, + { + "public": "9fc0f5c385512f82b31fe2bfbb7491b52a178d82998f976a7a178b455cdf73289d217d402e2596626d5be566a020f948ef398785c44315a587861fd1c6e8b4c6339eb69e08a5ced05be1a41005ed2c6604719f3269156bac28b84805331d76d2a29139bbdbfec664a451250440b7f04c39f58c0d5bc4338658a1f0bb16fafafe", + "secret": "ae323b1e2b2fd9e5fd2ec21daac1e017271e8c76c1430163d757e31016bb819e8b4c55cdfddfbf15bfda610876c148dfb96e2b1f45f311a66f6988e6ef5348fa9d217d402e2596626d5be566a020f948ef398785c44315a587861fd1c6e8b4c6a7c56f1de8b2852b1c9d55e19571d345cf803c1ac4324807fee55281adeb73bcbc43dd5714518e7f49e000ea6587353a3da8c93e53c5534b8c550fcd6141dce9a29139bbdbfec664a451250440b7f04c39f58c0d5bc4338658a1f0bb16fafafe" + }, + { + "public": "fefd03f762f4cd277966caac9263465742dbc7d3556c1221bc3156596a45ac5673f71e0adf0233ea00470aaa2e72e0fce6c487e7bdf0b0983c7b0e8f6fe6ef95a1f2e160c81a4c7afdbe7827d4ec63e30b2ac93f68b377127599b9c961e3a46fba1e5a11e2775d591e9e2b4f4d2df1400986b8d42075acf4da34ca441ad37fd0", + "secret": "ba860558170072240966b9c0013ab9a2dc155700de3a84a8832bd5e358be59af275eead531cc900109c1de0916adb0f97d22ff545f0e6b709ff9462259de55d273f71e0adf0233ea00470aaa2e72e0fce6c487e7bdf0b0983c7b0e8f6fe6ef95d2c8dd675c8cf14cf5a69fce5ad29501c8180a5f77d19da0fb8c5ff6a176d0474ce2573a34344db0dd778049a794797da0ff681b60a8e3b5e4e369dbbc790976ba1e5a11e2775d591e9e2b4f4d2df1400986b8d42075acf4da34ca441ad37fd0" + }, + { + "public": "87483f2d2f35e6c7357e6f884b7aedc405f696f0f23e7e76b1582ba51f826a5e83ea4088c26ee3d0a3489d7650bfe7cd2d378f50639e97f074681194daca8dbf7e1774fea005a4ee96e48e4af14d51e96d22980867d14993b2283b1e6b20e5ec899dc49cc1f8d005e2c98735a81eaad42b98c36566b21c958e032c1d4935c780", + "secret": "41acb1a61bd3daa1b71bcdc31bd5dd8891146d2c59288f27acaf0180a02d048568375d57f27880e0796365d04bde37dcb517863cdd7eac1b563ccc85c2bac32583ea4088c26ee3d0a3489d7650bfe7cd2d378f50639e97f074681194daca8dbf4055453c760e9463b6c30ddf9717c54fbb9e2ad0d782da168f82c51f8ef32010dcec669c12cb66567e79df33f273b8100bf771a2593a88afc15ee230dd608f3f899dc49cc1f8d005e2c98735a81eaad42b98c36566b21c958e032c1d4935c780" + }, + { + "public": "6f9f420f372875df0ddfd848533bff68bc6fa3f2b7505172d691a74571004c32b439957e8fc56b17bb22e3a7d8780d913a59de8c339c1464d28ee5875f1018ef297ca59663fb1dde398f12eac2bbc732d683f54819df266c22e7e74404ff8006fdae1f55381b1991c37dbe7043476a1aa8c96ad79f6f2c5afae3f1e2a57c142f", + "secret": "d616ff9f65791e065da0a018287ffff6ade0b5b0ecc1f9e088190ff9bc4cf89314c8f613dc66a84d2a7c476f1c14439ae5cb7f46e36ca8ae956f65bfb4e8fe07b439957e8fc56b17bb22e3a7d8780d913a59de8c339c1464d28ee5875f1018ef298907936bd459273d6b6335d32c5202f9c8c32c6d3282ee530d9ea93d2c694500a6903283aca7323f8bb932c8f846efdca1b5b74ae06dc7d0cadff18e1fb8c5fdae1f55381b1991c37dbe7043476a1aa8c96ad79f6f2c5afae3f1e2a57c142f" + }, + { + "public": "cb426f6c082b1fd44032516286c4db2054a317a4a21fe1230d7ea2ea28e054ab4258825b67f36a4f6929fa2c765fd466a597d1273e64ef7fb233c6024babffe91218c8c42f70fadb4814af10c7f45dbde51985ab7c83ed366fad130ef87c043c6fa2bbfaff849a497329374f639d09c2a76a132ffa171464b820867cc79ac892", + "secret": "c96c68bfff0fc23f4e9351cd1fa8de2fee855a16eb1cb2980f742baba4ffc2a9d6ee4a817b2722c087ed85b030bb30a13deb6398c86772bd70904ef2dbb03e3d4258825b67f36a4f6929fa2c765fd466a597d1273e64ef7fb233c6024babffe9e92274f4c357ad6988a4cc845c3e61cdce8241249b665b0237545fdebd3f04e9ea6522023c609413d9d9853a53d2053c88cfc786b5d6816a975d27457477e4e36fa2bbfaff849a497329374f639d09c2a76a132ffa171464b820867cc79ac892" + }, + { + "public": "dfe4ce416fea53dfc26d2a420c667afe00e7e85c6667fd8b0618a7d7f344408cbd2025a87f06efac6ae4bb44d0d9af6e6f35ba3d202150b56acc962fd24de3211fb5070befbf0c663aa5e4f561bbbbea65ee87324b56f3f72fcc646e282c3a7a0fb5b6ad56c9b6c9de80a15c85e3ee4a3b0e43c16ac3c317b9829c3ad126a6bd", + "secret": "ecccf4385139845eff546d7e4dc123114eb6f482cada90aa9f6d7dd4e5f6a34d764217e16a50519e109eb74a55937df9e83c41c1449fd19bb0ee6bdf61658c61bd2025a87f06efac6ae4bb44d0d9af6e6f35ba3d202150b56acc962fd24de321ee3e4af91bc789b255070e6f0637b3fb8a4d39c5d422057034f2783e2806f04d3288998a2b2019ec40e3ca63759001c598d4761d04e0c1d8dd7a3d606a75f1470fb5b6ad56c9b6c9de80a15c85e3ee4a3b0e43c16ac3c317b9829c3ad126a6bd" + }, + { + "public": "ace7cf700ea401dd9d741d1ee91b7cee5c666904c5aaf0de4e2fe840087aa19b6ad114caa13e2867827f041483a3e4a1dad91a2b4a61c46b796855f6f8a20cb6b8ee06cc2cb062067cc4411b1e04c017acd314e6f06217e04482a1010c6741a41c12fdd3384e45a329a9665de4f075d8b203342e2d3aa459bfe4cb9e309b0435", + "secret": "d823b0a60074a1aa97c1bd65845c15b7efc9910215f170907d536ac096d3f8207d1d7fa94f960f71f9cac7a8a2001d5f3ce95476363ec5fb77366359fe5f74d46ad114caa13e2867827f041483a3e4a1dad91a2b4a61c46b796855f6f8a20cb6f76b8f9733410a806b9f292d4966474e73fc20d10f0f5079610367a79b6d41ade9171d9f01b9447b27e4d2becca07ab61917e1d9384394b058900f35f250f6cf1c12fdd3384e45a329a9665de4f075d8b203342e2d3aa459bfe4cb9e309b0435" + }, + { + "public": "5d18da3a745dd9c8cb7a91d6790ba04e4c7ec316ccb3931a19e45cbcf543edfca9b08e34f2d2aadcb01af6e2480947de527fce8375a4936fdb3dd8456314d786a0bd6c34cef38c66b864e4dcfc50d1ffb196dda878a63cfcb57bf0c349baab4a3996f1855256bdf3354395eab69f45a80b701c0f53831ad3a4ee0d1c9559c225", + "secret": "a967845e90cd22fb8315c82d85f33abd93f2a60f49c4c1cf90c3821f336b8ef044e4e94b6ac7fdda09de43919b32bde53c95b5b112f010762674b8f64e28d23aa9b08e34f2d2aadcb01af6e2480947de527fce8375a4936fdb3dd8456314d78621ef89d976bfbe7144bf8df36642df90dc9bb5feb0bde9d58fe7259e4e66a13e092c30a7f5b935b93ffccdabe60331f4861bf8b4859b76f21635852cb54bf3943996f1855256bdf3354395eab69f45a80b701c0f53831ad3a4ee0d1c9559c225" + }, + { + "public": "c64ed7875dbf453c824bfe0b0ec817ca3963d3c3fa8b1ac42e7db863d3a9d4dfc1660e7e731ce8be17a9ba1b3818e1390c352da215a828fa717d30f9826b1859717630ed3ac9d29dd44d7f800c7e173eba427968eeae617d16d61400728c91edee387124b2b66eae5abeced5e1c97b44ceb966b0026cdc5bca8645af4da1d55e", + "secret": "686c58284b069a20cb104bbb6454ee48f380a6200a8ad63f8cf8662e60c1115fe102b3f305f28b773e4de908a0fbce771de4434bd632239e49b1f7c4cc302f50c1660e7e731ce8be17a9ba1b3818e1390c352da215a828fa717d30f9826b185921c75e52b8fd11062fb933b451624926c3b8fa9769e2d14fdd74f528166730156df6ab1164418e8812777703ce4c1f4be2f667b04e2d30e0f5ba33ac8d8e5d6fee387124b2b66eae5abeced5e1c97b44ceb966b0026cdc5bca8645af4da1d55e" + }, + { + "public": "ef02f3cc08510f77fbd11c3025192f03135f15039d1963d24a8ead7f38525f0eee90e2353cc9a2ca2d8c0da955d326d030703aad88ad0b60c5eca04671cdf4b693d0b402c501b0a62a4c7abc32bb0dd3240456c3ab1563c33dbd763865d2b58b6102f6bfd97c9f2d105235bb56481aa83ef377b4c4368b2059eab2f42581f10f", + "secret": "8894bb0aed1615186c3486054072f83599922d5667398deee46de42ce2c1c3acb63cd87237fdd68581c8eb4871b4329583264f9374598f49ba0550c9051a8984ee90e2353cc9a2ca2d8c0da955d326d030703aad88ad0b60c5eca04671cdf4b6043d299e12bda6c9ecde6c206129640811b378de32d11f8c6be78c1225e426958a517cbff169f9a8dde5d7ee0d9887147934898e678e23fe6f14c929edf8ed706102f6bfd97c9f2d105235bb56481aa83ef377b4c4368b2059eab2f42581f10f" + }, + { + "public": "67e6bfcbe52c0fb830d4e252a5e834ba95a98b7cb67b5bedb010a0f7c8d30d73a4d4e52420b04136864a51182ed220a577e490d198af66b4dbddb3c4e10012da8eeefa1120caa3d41ee0f2d1af9b4f49643cf188beabd6fef852a12d318224e57902fb3085f3b835068fbbd266279a79e3480ac97dc7f127c9fc90b7af10fb33", + "secret": "ec75c28f37c1eb692364a6c478e0c664c2bece0cc1a9ef04b1013c5bf0fe482c85b1c21ecb6f1448f9c3b9964293ec14e021813dad750537421e108adb4ed115a4d4e52420b04136864a51182ed220a577e490d198af66b4dbddb3c4e10012dab077033aadf77f3236a2a7da2b20ef710c91fe82fb1175fadde0f42e223c1d689e4ccf509e1016ee2013ad1f1bf806c599c468c55cc088faf5b2896b758b77d47902fb3085f3b835068fbbd266279a79e3480ac97dc7f127c9fc90b7af10fb33" + }, + { + "public": "e8e619dc9b470817a2fcb3032f61a01cf35610ffa247b3cfbbe32aa0dcf6478ac739f4311853f5ab9a60021bf3d6f4d7ccf0ad36e21ecdb0ed4c9d4fc2bb5ba1216e0a3919c8705c3c7f6312e9ba13296d81a36408f8af08283095438411d5a70aac7ac4a553a7e00d9aa1050419f500e3c1fbe84171a67c91b9eabb66823139", + "secret": "c25f429c2bc8d40449665dafa6e167950eda3bad5d9354e4d5054d7ec22dec65dfd0e18fcc561d6f3ebb60cd8de81e9924d6b209674f97355df184738e9d7312c739f4311853f5ab9a60021bf3d6f4d7ccf0ad36e21ecdb0ed4c9d4fc2bb5ba15853f12bbf3cd6a934ca08db17295ada6d7ba697ba69324c0eee6e2b562f139edb23c7fabff2a6c7bcbab408856ee0110902c0fabaf7eab586f0353ddc0bd7e90aac7ac4a553a7e00d9aa1050419f500e3c1fbe84171a67c91b9eabb66823139" + }, + { + "public": "5699e7d166a6a1703489867f59c6dd31ab83a311e1ff0cfed2f1972ec9b419f7ee78e9ef2c80ed693089db294f2d72c1c278d1b483dd469ac8f04a9b64ab566ae4b15c2f2353a8729fb052a4c80b73711045a27edb34d448681a3e66b6e3cbc8f0de60c7caa783ef6468ab4873c7b3145fda558757ab7d6871414e83a7fb5f44", + "secret": "86957af85cd8da06eb67eda66a0565a2792cb403bb8957eb4476f221a11aaff0e726876beb796d585e1e0bea2182a8001a0c855cecde03eeeb9a53e4e56ff789ee78e9ef2c80ed693089db294f2d72c1c278d1b483dd469ac8f04a9b64ab566ad52c52ba0912e8bd2ef6de169a201c31f9f905193234441a8ed07e8cfdb60d461385a4f386b2342cec10455cd2771baf8004227fafac7302ad1c967d762d01f0f0de60c7caa783ef6468ab4873c7b3145fda558757ab7d6871414e83a7fb5f44" + }, + { + "public": "af8539775b2bd982a7d14a6be3b3269101235be6ab87cd10491112455b1db661b749340025f32b9b23499137c51cd8c59194f6b606bee80aafdc1c73cacf10a3b92ddcfde05042cbfa3ca009f205babb96d8f11a308373b8d0f87c63790019e703744505886d68edbaaa7b00f8378b85f8456a947f3a990dee104249eeb66191", + "secret": "3fdabb9eb716ce2a59e090460e62fa67d36c30f4207c3824d2beb9109b1c9561c9fd13868a9b832093c71f955f30dffb6f52c619b6f8ebfe0679d77948db287fb749340025f32b9b23499137c51cd8c59194f6b606bee80aafdc1c73cacf10a36976be78ef9577d98a8434d4dba9a3f8b8508344319b7fae78eec430a05b405d36c65beb3119bddb9783b5ab3b72a988e0dd252d08537f152a96efa6d9b449d903744505886d68edbaaa7b00f8378b85f8456a947f3a990dee104249eeb66191" + }, + { + "public": "22b2b8f5ce84c4f10f3a2b69fb4c512831f66eb942c865acf8a7a8e6419921c21bdc5174bffd5f09c6451441df2a6487ec2a013a498d9751f752af6805f1f85d4683a23a21b21c15aa3791299631231e0d270f6f6be33ad243be9686f7febface62eba6a78a7e32317f8d31623053e846745c26f86f6e95afe002a63affb5d05", + "secret": "db498d334e5ec236e7c7b85d7250c46f86b7ad53cb02d030bcbf6cfd2788c5fbed8631b15d1b0118808d8adf65979acd469556c9edf1e8aa3946bb70e50455511bdc5174bffd5f09c6451441df2a6487ec2a013a498d9751f752af6805f1f85dbc805ffc87ba7bbb9a7e12ac432182fa3e7dfcf97391552b2dcfd40c3b50f9f2840cd8c4d317cc3df3a8a0ec70de333ea0022ccaf7ba39bd2c98a90622176d3be62eba6a78a7e32317f8d31623053e846745c26f86f6e95afe002a63affb5d05" + }, + { + "public": "7de9fad0f50314ff05aec33f4563158b84df74fe1a1c68a0e613122c493a19e97c8668c81c23efe230143e1a32b7a1cfb11453d4e3ee77af9181278c09ee2d9fa947a8ed68774603c72cf0ba54cb4f7684c62c24845f343a9a7adb0126b041e850de685dcadcb4ef764449764025eef4e74cc988522cbd24bfa506ece7ea0b57", + "secret": "acb6c9f1c3ab411af1fe5f6e77fda427e53d92f0c4f6c2867842592ffca236562a17b9308044f2f844a056d25ef705344ae290ee57e41345bbc7411044476ff97c8668c81c23efe230143e1a32b7a1cfb11453d4e3ee77af9181278c09ee2d9fca59de7c380316b04932ed92dd1090ba57881dba788d5a4f3a6c91c2cfe65a836a6e16f81b7ed74e346ae6d50164bcb191d2b4e4951fdd134a2b01ca22cc007c50de685dcadcb4ef764449764025eef4e74cc988522cbd24bfa506ece7ea0b57" + }, + { + "public": "6570fc392082d8e30faa5de8b90e86e1b64069294f94fb11d9b7765ac7022360f2a3e7b94fc638461e7c0cdb199faa4ca161eb70ec5bf22d363d2fe6cbec519d1f84a53efe630799874a1507ffbd1b2ffebc8aedbb758b4230c6783b34a17e1d7e063dde9817a57df052c715de041f92f56e4996bc3087d8c2bf514488b444fa", + "secret": "24507eb7fc4862a685d1f5f7a403137b5efd63a39eed84b08580182283f2b8237a0e78cbaec20dc75519eea55b371721152e10b71a9100e4005a574b4c55e355f2a3e7b94fc638461e7c0cdb199faa4ca161eb70ec5bf22d363d2fe6cbec519d294aa991744df1d0f585fe955cb4aa025582e832476131cca8b6179711c53ea3589ce0309d756a734d3c7f2188ca9b978cebe71c41f249ea45fb2dddde5f35ab7e063dde9817a57df052c715de041f92f56e4996bc3087d8c2bf514488b444fa" + }, + { + "public": "1ba8c12b02fc2b33a7317eda88637ffcf0467778e06ba970cec9644365839d0b91a4aee5601d989e06e7c026ac3ef84f3cdbf666da42e1564c53c7163035eef0a63e011e2fc98da0d6b2e5aae3bff2552f4803863cb5c44f5becb7410b22b639f48f51839113ebf8bf162c31f62755851e3e6e68f01858373d2e13c7b5c96c48", + "secret": "a04dc93246245c9bd47139702636e64795af8639965c4bc2d76d7362064229058fc951af6277c8518d562f3bdeb88cbbbe6c5ae8cd6ffc290aa1076a29c32cfe91a4aee5601d989e06e7c026ac3ef84f3cdbf666da42e1564c53c7163035eef05a968b3fe366da47d2dae8ee418b9fe3c91eb5a9a49e88b47b88b6c814676ef7b85eccbd7f2da7ac064f908eafd2588686b4010d60973f426780181dc0055ae2f48f51839113ebf8bf162c31f62755851e3e6e68f01858373d2e13c7b5c96c48" + }, + { + "public": "2127f4ded67b981d6e58ccc0cb1190d5354e9505d0b06034a818c76cbca4d776779a047152edac6798c9ccc48b277c599b7306047ad56e33b72d9708d9bbd3cb79571e9a409c10ea0f89d8b4e729bc94868eb2550b807fcf0830f3487a2d42245c82077ac19e39d7ad1a71ec288deb87f2c3e439e1f9a3e0ece516ca1654db2a", + "secret": "7fb6b460e4923fca09cc1b4ca805ee6a4007a324ff3f19b96db535055534735311044294a75063e98f70cce6a932394f30b70688cd68a1a5a2daa05459cfd241779a047152edac6798c9ccc48b277c599b7306047ad56e33b72d9708d9bbd3cbe37f961097812bb8f712a8ff452858dfac6365e5d7026ff09e929ebcf23b2932d5352593444123d6bc6b983bf8c5b569654befac2e53ee678f0747e931dd33de5c82077ac19e39d7ad1a71ec288deb87f2c3e439e1f9a3e0ece516ca1654db2a" + }, + { + "public": "df2c11a924eeedd1198c1a6fed0356ab594c49ffa6be942396ffd4c575f403488a7f87fd513fe299f02e9fcd768960a32c2a5f85d5c6ed7956ee5518bb617010fa1e7986093620e0a1b764697b616ccedc4763bd72fc8236f4345e5dea757269732190743553ecfb5f09eef16b6d65ee61ca1540ff540b66ad261aa27b8786f8", + "secret": "0f619677f802c99d644fa872744abadd629ecc179c5b9d51dfd475b9bfdfe29037f4129f57a5058611d37eb73b1fdf1744cb3c3f6fef86e984c526f57df02cbf8a7f87fd513fe299f02e9fcd768960a32c2a5f85d5c6ed7956ee5518bb6170100b160aba76c228ebea3af9fbfedaa84f6739b96bb51fa97020cd40412caf9bac1e7f37f3eea5da60e1f1e3aa560b7499fa66d6eb5fb1c652b5a680a8f1e0e151732190743553ecfb5f09eef16b6d65ee61ca1540ff540b66ad261aa27b8786f8" + }, + { + "public": "b97184a6f491f566c58f310b97d1995592a9f93880d3856e5ac83965ecf11d04bedbbdcf6392c05eab29c7717df222e07c6bfd34030801a0481a819a3d675a67766aac8dfd2573312d3ebe3b4e6336e1f87c02ad36fe348d0a6387d6badf11e656397b41bdc37027bb7943690612d0dfbb39afe9166f8d998124ff7e0cfa35af", + "secret": "265c5d9e43d564b71cb19c0430c63c5318d2d8306bb1d8504d278aec134102c9aef7b4b24ba2ca0e054bb1844493e4ec78c96c706764c0be98a4a11cb5720ceebedbbdcf6392c05eab29c7717df222e07c6bfd34030801a0481a819a3d675a67e9b769cd36c1396e5b695f8015839b3a009a45dc7315d1dd08af06730a060cec79a446d82833f4a154883346bb32536269a07a4dad3a01da1abdd8a9f8a7b66c56397b41bdc37027bb7943690612d0dfbb39afe9166f8d998124ff7e0cfa35af" + }, + { + "public": "8cec36381bc13b52d726f963e618a176c166b5b15cae3ee0a3f71ba38044b2b0ae950a44debf5a9a0ed074c865f93f92272b20cdbfd37a1c56866098f0a742345cd4179649dd61c8c7d5be9413403848094f3f52747494fc99d5bcd0db36360979e8e7d154939367c958cb1fb8715d2b4ddd1c899939ee045a62294eee36dd90", + "secret": "11b40847fc3bbee98b8df5c6e566782bf4a145f3124a4ddad3c35c4bb1e993e058ae37ff7e23e760146e47d25a7fe57033d17481b1370fd50bfdc950756d0e2bae950a44debf5a9a0ed074c865f93f92272b20cdbfd37a1c56866098f0a74234f15d5882dedef72e08969635592ae8e5e2852f0021e26bbd9eb7192a224ff1d7f45b8ea0f58d99dc6e0c1ac686bda25271ac083599e865d29643eed96def049579e8e7d154939367c958cb1fb8715d2b4ddd1c899939ee045a62294eee36dd90" + }, + { + "public": "36f3366349657acd58d935406fcf630a96399e4445e70fad6feb85ef27e22db791c840584c9ef00baa5f406e9f9fd138035abe9c8e955d7180f44673bd22d86a0fe95b217463f1b352e22e1f54eaa9abac37f6f173fa21be48ee285cbcf734d973f0459e7894715d3febaa681b8224917c106b52f752db326a09aae24d12ca36", + "secret": "c82b5ed3b72406ab97f95db66de33df70a33d2ae71699fa62e301a8682cf2dd7db5dffac09510d88fbe015e178e15e2abf156a27d11d4fb20f23bd0dcc92608191c840584c9ef00baa5f406e9f9fd138035abe9c8e955d7180f44673bd22d86a706941edba62ec1a54c150cb67e35eb5be0641684948b60d917c6380397b0eefbb0dc73b25f696b5c55bca9d65075d485bc135d522ca0168de955cbeb982449373f0459e7894715d3febaa681b8224917c106b52f752db326a09aae24d12ca36" + }, + { + "public": "d585d2a09c7fa4a211d3289e46e503c8cd72b9cf29e8f17f12da616806eb1c03f581c805631564dc185948530bce9689a489c9b1d7e1bc5fdc92e8ec8e5cb29710e65b7dee7646fd56d1d03ad15acd181c7e855cfee87e10ba89b40b6214b8b02c0f85be420e27e8966066877808f9163743ed0410814805185cf7290efeca74", + "secret": "fd5a458427cb8b90dc5167d703992a83680ffec3bd3f5717f4ee591febc2550eb1491590a01d37313a810fcd67df04497f0fccdac66c8184ea3fd1c20cbd91a8f581c805631564dc185948530bce9689a489c9b1d7e1bc5fdc92e8ec8e5cb29772f8bd65f2f52cdfc41201912d7dbd13b53a6e643b841bce81e93f3d880554c7800448f21ee5ab55cf4ea06cfebf3dd42a40b6ce5ded8a6cba00ef93e1ecd16c2c0f85be420e27e8966066877808f9163743ed0410814805185cf7290efeca74" + }, + { + "public": "ea23a0497b49385f61b8608c39b8a2315502e094e23eb639ff2908b12211f24403edfc943c855be6ed1025159f43626f4ec3b5a86945fd26ecb65edf5ecec79114c8bf9f68733188cb11c9ffa022a2564dd4a56f3c49eb5d89a8402ad178ff417845a1d1730d2886047dec99ec347be3f23c67edc383da012fba062cf7e0cb34", + "secret": "5faae616cb6c8ae34590fe1f01e79a4a2891602ccaa3556b718bcedb645734c16ef12efc7b143feea2142cccd61910b65ef82a99d2e6892911ef603c38c904be03edfc943c855be6ed1025159f43626f4ec3b5a86945fd26ecb65edf5ecec7910c9bce4ab66111b775081eee26196901a9ab5c24c9374c5240bfa5de9fd68f68b33d30b3f3de10c1946393405e6ddbaaad6cd54f725e81115690fe619bacecf87845a1d1730d2886047dec99ec347be3f23c67edc383da012fba062cf7e0cb34" + }, + { + "public": "ffb7fc958d15edf21268467564f911722e690d10ac9a62f92b6d6f239faf9d08db4ae702fb75f1a2ee5f1a2e3b0ce9ff7d06c11ac3ff82b0bee23155c1fdad321cee714e7f9e11bc0091ef58d649fb107d8ce0b3b808a6a5669af2a3af8800097cca54e4085a0167ada6279d7892022f9dc8c90e10ec345395dd254d4a75c2f2", + "secret": "3043340c9c966c79b584cdc9bbcbb20e333a11f0ad3bec4288576b3e394957ad80659d8fdb3c671653570212069c53d78fb543dad3b61a622eda4218e50dd4bddb4ae702fb75f1a2ee5f1a2e3b0ce9ff7d06c11ac3ff82b0bee23155c1fdad32262c500278829705eadf0b16e775b6a66c19963f6948700e22252d87bb1ca578caf6f883bcaea55934196423a42187c79fe918a594bd5f59e2f7671c9f254aba7cca54e4085a0167ada6279d7892022f9dc8c90e10ec345395dd254d4a75c2f2" + }, + { + "public": "4e7157efafa5a470eac3d4449e34d7e1db576c30d6081a13442d79f3e2ef46f176f6361098d2a2695279a8ba7eb69d818b5c5c1f6b984148117cb7a60cdfeaabd6a470d2562e00eca12c911815f3580523ca4bd30068b117c28b351af6180c7bcae9bf9ee0b5ca42a9b8bd27bc978d059460a8a96019a80e1315293f221b1738", + "secret": "bf641192357cfae70e7b7a94a0ba056371760dafe18708c9d539daa35b96d05fc2f443916cdb5a6f68f13b75f02a533be100d7d5b3ab3820fe22d8d93032b17476f6361098d2a2695279a8ba7eb69d818b5c5c1f6b984148117cb7a60cdfeaab913c5e1b36bcbd7f60e4f400dc2d11dbfeba0d3c535745810c5e9f6dfdd1db02ab1c295aedf69a9aadb5d15fb4f84117950f06d04f471e34b864f20370d4a53dcae9bf9ee0b5ca42a9b8bd27bc978d059460a8a96019a80e1315293f221b1738" + }, + { + "public": "44fa479905493c5f2d8af7876d1125809c12cb3ab67778f329e93abadc97514a27f78444e2367ce0d2fee0a54020b031ed473f04699b4daeb5fddc6163a89c7bb5b369849b2cdd9674ba80aad4712f198e0518c226e419d8a04c52d20ee83453dee186cea0c7b46e202ee328c92fb76a4861d732c53d5de631747bea7be7d830", + "secret": "2e7c9f4fce8e948a4886ec9a2f5415abbe4f91746224b491595834eaeb0c09d8c428fa69b885d5aed76c9c4980f9066cda2f323d9e3136c40729eaa94980443027f78444e2367ce0d2fee0a54020b031ed473f04699b4daeb5fddc6163a89c7b2099638d2a29efaeae117faa5f12360aea434955af9d783c20076e54208b00f0aab5b4357e9fa91a31bef02ad8ecf3a1808f10719e328f5a2bffa9c69739c437dee186cea0c7b46e202ee328c92fb76a4861d732c53d5de631747bea7be7d830" + }, + { + "public": "491de8a2c9c9f895b7ac62b091b2ef8c05866373127a822e143532f963130588e631b847f7d3f7f57d994750c713d94c21d7fe4e02d1d874ad3f1945766d5d46515dfb0aa6560f71dbe5bdc1c3a67f77f6687f8b2fd944e6f958bea743a01e4a4d3841065cb84b8a7f638fdc2f55dcbc3a61c0397901029e2b7625fa101a8bb2", + "secret": "7a5ca0a6c8025ab62914f650a636ff29bd8d2bd491d3cb1031318c8e1a67b621444659563b7c07f5d0510ff083b8997ba6cdcb54c84fd0b8dbb2054bf2854b69e631b847f7d3f7f57d994750c713d94c21d7fe4e02d1d874ad3f1945766d5d46d2eb03a42e824434591d2323bd3e095f3b297761cf519fc6dee6add1af3c0b15a2566e24789fb5e706e64f12e8a9c864fe54ee1649fe31fe056f4f1600dba9554d3841065cb84b8a7f638fdc2f55dcbc3a61c0397901029e2b7625fa101a8bb2" + }, + { + "public": "1b085fd59b01925bd55f854ea07cb55c35198dcf75ca6d30e62733e10c681f86fc24268ce6b11dc4678f58b7107ec9561bd96f94c8942edf9031d6aaf202456de460fd97c368c840947a72d6fae3f185ae6352184e4499fb5dba752f0f6632553d252a41393cf4956440fed549bb2645ff4b0371d4611beffb8d1f17c437f3ba", + "secret": "dc938983287935b8fd3ea6e2829594b43b8d0ee7e0eb99e71e234dbb1648d3afc088673dc29e845c7b38a97ff82f7eb0a7ad8591886e0076cad4ec6d4ef16d5dfc24268ce6b11dc4678f58b7107ec9561bd96f94c8942edf9031d6aaf202456d6c7dc320cbfb3ba43129e584c1d39cb3028536372f3259377f642f9e9d8e1629974f36d7c1606f802b3cc2eb846884a20b9f0ae186a84af180086793a68662bd3d252a41393cf4956440fed549bb2645ff4b0371d4611beffb8d1f17c437f3ba" + }, + { + "public": "f73654e3ccc6047cbdc4f43090e3b26f707835090ce4b81d1a6c7ff05fcc7687a64932432949d5a86bae99980289a776c9d01ad1405d5643fae3b753f32ccc46bfe0fe3bf6654b0c88c91908f8e40cd0874ae6dc394b1f0ca0a5f64123f0bc5e267ba2e6bb6eb3a939977be2a5a36d124543e971536dd6ffef613d5e93930272", + "secret": "14a2eaeab05a24a8b7815658cc06dfc3a765fb935eae8dec25e871b08dd5309005f948f832aadb6aad58576ff5a6f7c30b4b34b2c0949dc137da3d5ec6294fa5a64932432949d5a86bae99980289a776c9d01ad1405d5643fae3b753f32ccc4683bcce707019d21fadaa74431ca7c1709721f31b1c18a1f2758d641d906bc7e709912389428d6a460dc27ce3578bc0ce5596f53f542cc0f22e58722d67bac807267ba2e6bb6eb3a939977be2a5a36d124543e971536dd6ffef613d5e93930272" + }, + { + "public": "938760a69afe2f09f94c9a0fa8216b103b8100fcc606a5e91742b0aac8253799f9982abfefd5f837f2fce4a4179772f97627f61811cd4c70d183f53ab995e610bf061e6661dd7a28bfbef01b256c002f447172e533051671163525cd9a63fc6400946a3c9fb979bb0954722ed29bb6b165de4de3bdbbe12432f694ccb60a3f72", + "secret": "f1bf4472c057b526d7a1810e4d818a7d75a31519170e04128c8db7de603de7a4e84fd84ebf9fd65252ebc38904076ab14ec630b9f5feee9ff9f6a96cc77a0b19f9982abfefd5f837f2fce4a4179772f97627f61811cd4c70d183f53ab995e6101a3af5fdf4cfeffd0d3f4fbd7b5f2d2819faad5208c9567e23870619e9a048d719cb3f5c3326aa19d56656b5252b55c12da68229ff67fcbbfd6725c149d59ded00946a3c9fb979bb0954722ed29bb6b165de4de3bdbbe12432f694ccb60a3f72" + }, + { + "public": "404eb9a4dd44439198591de9973fcb8f6869cc6d443f87dd72d50704fb766040dbf3921cd1f784499fd49809d8b0169570f09a4665ec5984bb410ab28b2e5e7484f27df9f3474d3a9a66a14d7a95fed07773205b5e6c7a544ee63ac2f4d6504d764a1fef7cbe4fcc9e1c03d59edc7090e500b372367229265b77ad6d0586fa54", + "secret": "202cfa79636dedc2743c9e16c49f04a03f546300a5197b5ab5dd4fe6a99d269c200a3d6db8a790f6e4afd4f44244f596e26d87a359f5fe7a3f6180a0621c5e05dbf3921cd1f784499fd49809d8b0169570f09a4665ec5984bb410ab28b2e5e74bd1fb07bc8c20a51d78eee0c44647ef4e5839e3e0e15acc0d69a047219dc4fa64f352e35ba695e8803d2105b84ae0aef42c8f3b232c3cefa9f8927846e054e23764a1fef7cbe4fcc9e1c03d59edc7090e500b372367229265b77ad6d0586fa54" + }, + { + "public": "58399bbc4caa3265cdcf11dcdc9e540bd16f2647ebd184a43513a83e5c5683138c7e7dfd3ca6ee551ea094e9feef61e2ab3b8c9c6e00ef51c85f2112edfab44808d09a915414c3d9e676f6a225348d8f892649fc8028a4e49af57a39b9c95fdb7008c2acb04fe5b2e62f6753a27f3742c3c0d847be42d1ea0c49fd030b0a0f0b", + "secret": "fef9cb6bbe02a72b51e032a01f18f6ab3f727ca9326f31c701cae408588eecf0226c860ef412633c59a09963e0d1f0c70da57f57c0085a17e47e5bcc7a3e40b38c7e7dfd3ca6ee551ea094e9feef61e2ab3b8c9c6e00ef51c85f2112edfab448d1a198fcf5108d0377bb7fc3d38ee78a40844e5692ff999d823e1bcf08cccaf21c438f85ba9cabff30869c95440691630d8419c5375bfc952136c449a85711107008c2acb04fe5b2e62f6753a27f3742c3c0d847be42d1ea0c49fd030b0a0f0b" + }, + { + "public": "f70899758f6a835b28012799396c49f82337485f29c5f49e698d18073cba32e575be9607ac7e60476389f76b51e71fae6b9cc04c5e298211178b00e4596bedb36c590d3b30b5b91bccf47adef1660eb7234b6bb46c60b504a48510b6685a9bc14515a5b00536de24cf843d6bcc4d9ff81dd36984a50beaa29cfae70ae5a3d42b", + "secret": "bdea9f83e4490934e9618d5ffae69bd447528ff33751e59d93ceb194501da625f33c323b3d4b59038775d126d25f28403d6cff13ac397ed61b96bf082b87844f75be9607ac7e60476389f76b51e71fae6b9cc04c5e298211178b00e4596bedb31f6b0cd6526761e9308fe29449503926134dab65d21c767948a0b985b2dd7492679308ada09b6fb0cd3a33aab7732f372f7647322408827463e15dfcd81e317d4515a5b00536de24cf843d6bcc4d9ff81dd36984a50beaa29cfae70ae5a3d42b" + }, + { + "public": "171728517511c5adc08012c023b1e3f2fccb370f64d73cd54023075e43c331d6c05870ea7cde07df9b6d0ef19ad2b9fcd4c2605f61fa68b395d24a176c42d120bbdcf4fef93e3ff2f854795e7c9d593ff7ff3f1deec3b2c760e5bc969c37e417844364c736d774aa7178116021247a5b9c4a18351f172c19f397c18c1ed75a45", + "secret": "335c48ba4c84df91cc656d723e1a76f08cc2ddec27845bc59763da156f08413abec8d24b3fc240634fb7f2af727f2d3662864d2ee8e9c570046ef971257ada28c05870ea7cde07df9b6d0ef19ad2b9fcd4c2605f61fa68b395d24a176c42d1207355357dd98180aba5b070a3a4d391c2a4149329582fdd647ffc1f16250ee9d75bd667ccb64e06adaf4cdfa3be8a81285cf33250ec653ebad007ea219b9a9d38844364c736d774aa7178116021247a5b9c4a18351f172c19f397c18c1ed75a45" + }, + { + "public": "fad5f4e099d5266f7e872ad5b7697d4e2e7fbc8ed1b717b1203ef03ee196a350cbbfe67fe711690ad52aa7063b0ba21e743dc8b90eef0c524af2c6c4a4db1f40dbb78c74c42a5875b90afd65454a944a60524edb80cd367103ecba0bd9d27ea59c7fea0490857f2b6978861cbe55ec23aabb2ab054d7172e050964fff154be8d", + "secret": "da7de9b53081a544d922ac97b63545f7eaa84d8868e362d578ab27c633aef35c8dd497b8d7d7fb71cd73ceb04448143f5da427e3df574377e1c7d94e1dfd2e28cbbfe67fe711690ad52aa7063b0ba21e743dc8b90eef0c524af2c6c4a4db1f40a72eccd39602eb1e6d00310256c1df6a2a62ceeb8153a25bb643b6fc799f666f566ff0aa62bc1360242d59ac0d22a5621b677e06074b6ed79da8b5c406b84a219c7fea0490857f2b6978861cbe55ec23aabb2ab054d7172e050964fff154be8d" + }, + { + "public": "21df657462542106308e2685da8f17409b8a3d619b65de9d6dcd27114722fc90d29b1c690a1265fbfd69f27f9e38729f5e79d4bc4b18c2833a3755321bc5e38e822efb36acd64ce5f5e6b898b7b222a9f4152b82f7a36a7d480c5e961f394a4f7e03e5edef7839582b6ca0304758b4ac844660f9842d6a33cdbb634b5d859fd7", + "secret": "40fddf63b8f7031c69ab8e217b548467ea6c9a150439b5c6e949ec9a5536d38384fff4a375e1032370f8b2eaaec7a5d42b59411f90b43ac07e783f2e461ca639d29b1c690a1265fbfd69f27f9e38729f5e79d4bc4b18c2833a3755321bc5e38e580daff60a2074e65d0ae92b41da077faab89458a4702aed9935b72c591cf056a893ee88168169f23439810f9d465d03e7fb26e98bf12c11b99a8ce2c80ff0be7e03e5edef7839582b6ca0304758b4ac844660f9842d6a33cdbb634b5d859fd7" + }, + { + "public": "4670fb49ac8284cce293b5d458c7a0e0c38cb3ec9a599902c86b44418a15876201c25283bfd2c23cc4d7950f1c94ae864eb249df62203d82a297068f58ba6fe35a439e45fc8d10313162962291510b344870645d182307c50f9d5f7a545436c1ab2829ce765c51974c506ae84cd6d7fc22c992d5b565aab0c11b5b84c279f6ea", + "secret": "acadf9457fe9c416623ad185aba5ee57f8c7d8be554fb8e3a875f47d8cdf36323c8caf64cd6d8ac807d1f09cc7b73f67a7687be4827e01252f96014cc6a07a9501c25283bfd2c23cc4d7950f1c94ae864eb249df62203d82a297068f58ba6fe378e635f2555d8bb2f5d6f6cb87cd749e2d1e2100c82a97e2f71929d7f163e4500c26e42d1750315735667b0885ca31c25a6b1f9bb0488cac5ed9415a98e57fb5ab2829ce765c51974c506ae84cd6d7fc22c992d5b565aab0c11b5b84c279f6ea" + }, + { + "public": "9d3a71a4e195eb29cbcdea3a063f9125cf5f1d89a6acbf59c939211a75b89b3c984f834c90dd1bea8b61c71952c9906fee8cd79eabd771eb04cac244247e1972b31fdcc15bdaf574e1414ea736ec6ab0a9ecc3adc73d7dd4764e0f9df0cfe51280e77d6f7bb04144252e51f74773a34c71d6f0bb3ebd1d3e3e4858ce564c82ec", + "secret": "22f248a2a3d484f935a8d6dd754ea5e5316dc5ce2ba3e471ed791264152a8203345d6a84711b8594b86a52207d31a9e0a12aaf6738bbde5ab432a6a6e12f7419984f834c90dd1bea8b61c71952c9906fee8cd79eabd771eb04cac244247e19726fb985d33174f963d19bcaffc17d0bc2c15e3d8c85da3220541e2d688475ea9302db54242bd970e22d031576fba5e41e466cffa87f1ad4b20c7d46b04e342eff80e77d6f7bb04144252e51f74773a34c71d6f0bb3ebd1d3e3e4858ce564c82ec" + }, + { + "public": "de1425f55531a1df354f4b9995174ba588cc1ee6e16cce72e43b6a4317ae928c0d117fdb5b26cabe37e5809c3b1b6813aa6bebedf870fcf0582e8b6fe6359a750a33211a618474ee02ebf8db16c178bd9bd958c897a731c0f0d2208644bbe5164698d3064b0e3cc966c28e63218ca956cbb8db2b7afea1862adb1f45f73225c8", + "secret": "947f94b323449fb08b52075c6aa1efc957fb0d86540dc3a88acc6d2479b018cce6013b6bb28e418344982d573589c28ac6d7ee27ed8fdc82ec22c1c2967203470d117fdb5b26cabe37e5809c3b1b6813aa6bebedf870fcf0582e8b6fe6359a7510b3f1f55910fc9fb1fc54f9c6d6215416a19b3670a419863806cf5a7eada3d63a20f5c43b3dcc05f83803a7d1e9d277a68624ee9eb7c869d31c920baf4c67174698d3064b0e3cc966c28e63218ca956cbb8db2b7afea1862adb1f45f73225c8" + }, + { + "public": "c0379e831761780ee6797632de6974fbc721e27cee066f2dd553e1eee7e385db289c796a1fa8eca16a7db0440571767aaedf3ef612493fe669e61e94c2ed22cc669554ad85e56bbe20991fab600abb9a068f40ea3f841444beabec0aa5946a895236cb1ea819ad943fd98ebdf3c1cc1a08d065c3762d0ab43ec481806f069133", + "secret": "7cddd56b1ecfc8865177ae8936b044aea89d24c676af375e3373be2de53ac94b2e452da46090c910b2920ca888b934749cfc78922f5f1cbfa3c5df126fc4d0ad289c796a1fa8eca16a7db0440571767aaedf3ef612493fe669e61e94c2ed22ccc7d63188f6611848f61fd02be75c0ff4576ff8711e67c8ea4002803800a737cbb2b4a68ed4746ac28b352fb837600bd4bac352e7c0b4fee6fd93f79811f190975236cb1ea819ad943fd98ebdf3c1cc1a08d065c3762d0ab43ec481806f069133" + }, + { + "public": "263a9576730d8b6c5c3cd80840ca0fa831b6af9ecd8b38e8916d6c1a05004c514976afa53b3e92ba0772ee0f846c63ded8e1de5c81b9fcdab12af0483027596e09a955ac0025ae9954dba602e2cda9e5b99700208660cbb450879b0d6e781c3cbf7263c553586d247400a90395f3c11560150010082d85c37338fb928bd1fc6a", + "secret": "8181b24f6159db6dea1d079bdcf1395fa5d751bcdb58f8031fd42a6aa71d54f2cd6a90a11663fd7c7e909186e9d73aa3b4dbd08827ae0f436b22c6eded48363f4976afa53b3e92ba0772ee0f846c63ded8e1de5c81b9fcdab12af0483027596ea2009f7ea7e6e543fb7cceda523c8b9781a1744247b4a093f7d6d06f4db95845b50b8edd92959c706c550b2d643bf2feceb2a2e1f094e5f1f8dfa1ebf5e532e0bf7263c553586d247400a90395f3c11560150010082d85c37338fb928bd1fc6a" + }, + { + "public": "a91f847a1c2248a63fcae2e5952c78d0e4f3f7c2453eedccfc966d8d473080124a950bed2176e7c7a0ed849c4685ec42038f4b53b127e70551ecc83328f61e0c50b37cda9a2d63495fe59fff1e137585e9f2928bc1058dbb2b77242949166b102017f5b70a6958f14c57b639b2aafecefda3b7629820a11abef4a14df88b7bdd", + "secret": "c2d9798b467e7f49478ed73b3e457c1858b02523b8ba5a6d820985e47a8a3448e287dc50a3be723078b9fc44c9b3b96452e06db6a1cfd56f7dd9b357642b3fa24a950bed2176e7c7a0ed849c4685ec42038f4b53b127e70551ecc83328f61e0cdef57398c3dbfd7aaf18cbf6baf815d236363640dadc7426ba662939855e9f9ca3ed4d39119fd600c66d01e604554cf6678174108da3be7be76e52a54de0fe3f2017f5b70a6958f14c57b639b2aafecefda3b7629820a11abef4a14df88b7bdd" + }, + { + "public": "b9d0996c2feef7e9f9861d366e227ad15dbdadfd024755c71093d5ded5be2dd61480b321a933dde96a3f2248eb4f1d2461b3fdd140ae6e765e5084f7a7b3faf4ee3dc7921f6b454fb75e17fd88df1f0b368ac11b2963084680e84f1df9137d01befbf2fda270ba9d658176218b5c0f59556890109d5cda32ca1bbf94886dc514", + "secret": "8516cddb4f8a720fdca5f98eb6627691b7d847249ecc7d44846c81202ca1239fe485abb155046922a8fde840859771647a27b0de6ce6700761cbbf1ce9f2cc501480b321a933dde96a3f2248eb4f1d2461b3fdd140ae6e765e5084f7a7b3faf49b14f3e2b7890bfc077df9cc945a705b5ee46386c28e0112561b9d1e2720d81b33d2e81e2920bf059d5db1701125b7a96076d5273e5e4247b9e0e3bcf38e557cbefbf2fda270ba9d658176218b5c0f59556890109d5cda32ca1bbf94886dc514" + }, + { + "public": "a32f8ec9c19f07872121aa74cb9c2540f97dd3927753f0ac52a18988da3b65f23a2d6942ffb35b528ddfb56ad50e88f4ae12276c0495c4233c8a64cd65ce58f7ed6321ea0ad594698147171d25e366ab4d1e27c32470f932bcca740f0e94e3c99f8e42a964b0c59265c2d55f20bcdaa866b8de739f1cc89bbb1160b2cbc88eb2", + "secret": "56d62f999a871137e1a69948cd92868420e93e3afe404a8bd6e2917f1809287aa910b35f05e1b0b6d7c69b9bd9b7d4644bfb62da4d0e6c74f6c03945427473b13a2d6942ffb35b528ddfb56ad50e88f4ae12276c0495c4233c8a64cd65ce58f76f52eb8896c0449d60574ba88fa7d044962cd6a04563026dd7d3570c3baf331dd87bff34fcf4e804500b0ce18a9d3358f3f6d1c78b821f05114062f6224309649f8e42a964b0c59265c2d55f20bcdaa866b8de739f1cc89bbb1160b2cbc88eb2" + }, + { + "public": "053cdb830bc0f4fc244062666127ec55362dbfc615f8b0317d0e87f5b617b0cba3998e902035142fe1ffd876d31dc3b887eb56e8c2914a3ad39191b24503c139b5e84bdf0adf0bb0ae1121081eacbf1a44bae9dc1e60bfeb899c345f74bbe94df682b8923e390afd0f02e4c2473a9ca1df35fe82edf068bc48fa59f7ae558544", + "secret": "e8961fb8953ce90f02abe433c8a806b2f986449bd2854b4f60811c4040409f5459e3cfa4af7b5ca48aa044526af3b2f0968015319786c686db0e114e018dd39ca3998e902035142fe1ffd876d31dc3b887eb56e8c2914a3ad39191b24503c139fcc851a2a2b8742932642662137065c9a4dfb4e0806255f9bd88c6d8d3100e556ebb381cf7c1d1b2ebe1b13e2e4e92f47817dd5afaf6b92f9be8832aea894952f682b8923e390afd0f02e4c2473a9ca1df35fe82edf068bc48fa59f7ae558544" + }, + { + "public": "dda001821efa5fd7f78968fcb12359113d4781b6c9a030ffbe0275947fed1f9ce022e1b1b681c7b49bd565cf5baef492ad29b9a2453694f557c8752093a933a346a565ffd0c0ada461653d9f08298e59e3be5a4dea42c043f54dce8436e047e534558f18fe9ded1dcf6ab1f9069bb94302f9b416a599600a23313561684086e3", + "secret": "815336d159b926f967b49608f06deeb377ac27c4edbf5c42f02fee911218e2a121857696a424488af4056fe5efd5569598b413e2c49a4c248d1cbe44991e8aefe022e1b1b681c7b49bd565cf5baef492ad29b9a2453694f557c8752093a933a38b10253fc85e90858be0bf37f42502de46856299ba6d39aa8b6ce0061f19982dcb4683436084da79234e92a29284ba82c80fb9dfeedafd14a59605a14e8c3fb634558f18fe9ded1dcf6ab1f9069bb94302f9b416a599600a23313561684086e3" + } +] \ No newline at end of file diff --git a/rust/tw_keypair/tests/ed25519_extended_cardano_sign.json b/rust/tw_keypair/tests/ed25519_extended_cardano_sign.json new file mode 100644 index 00000000000..b0731ed70c5 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_extended_cardano_sign.json @@ -0,0 +1,1252 @@ +[ + { + "msg": "", + "secret": "ed250655864a1e9a06213389c54b4c613ac81ec41b43f50d2e21839fa881535f1f2925bdb9bf244094d2884590971c126b11e10d0c91f49a34b9b90e54bd12defb71871ead7ec2cbd005010e66dcc4dcd9fe34a01b34a1c6d26027d09846ecaa5b5a54df16af9d9679b1f861da12aef0993f10bd1855670b2ca7933e34efb46fea3148c50dc26bd9f3b72ea41a1f0b058c1da97f0a7a156833fda32512f11a88cc58e676ade7c158a54b87fe39593b141a07405c031d71b71afbe792e35fbe76", + "signature": "9ff22239581685c79a6b041ea7bb92e6a863d6fbe49f40e28ce3e1ac86038c0352da29ceb85bd76cd49486f1ec103367163c3a91127ce05734933b6c33d9290b" + }, + { + "msg": "dc", + "secret": "20c43540308293cf22324a2ff21504012cda04358f4b7f24e047da942ada8547e0ebfa3b9d9a35bf2289587c96e2c84c9c86cd994e4103f4f69ae8f47ba7acc03cb5fd5de2e9fc2605595486a7cdd15eb5bb21cf77b79b9a1b07b794e4815d7ea89489377e9fbe1d5106bcf8123c0113f0f1e56196c0afa3f3a97caa7b362f9b021bc9be635fabffad8623bde997b17c00c39f3e99fab838d4e7d75bff04b90fab12207ff62f97832dcbc474ffc797dce91f62bff247ca838f54f6a856b69155", + "signature": "130d0fe92ed86b8583a41da94c747eb1210d92c1dcedddb192f71d201c92298131019d46c1cc4dfaced0047118a2e9b04c7cb90726b0c05fb5d51cacd5181b04" + }, + { + "msg": "e370", + "secret": "b959eef5999541074b8e457ba1d8624c2a019c022b12a57dc67c3cd3a321c5e22f309e8a60f5f1ef45488720a379c84884134531489b38195a5ba2aea9119e9d30a3d2d4ebb89bb98d8bae5163a66db7eca6cb56a8aa52d459b4737c4ba9ad7c2261a545afb37e6b4d63e425426e492e1521421593925659cd54be7acf880457af67fe08d0a7e65aa4fd1b32aa34f762913a69be4a886bbde1c437778f6969d22d68a36c150d28e3da816626fe0d329fe479f23f4cec1c3a28e1725e7deb37c7", + "signature": "a2ae583450dc797f5a71b6286e1348ebc1578ff4bda1f78ec14d8f515069313e3757f1f52765042afd72baec4d5d24541cb186f1b219ad920143eb2264021103" + }, + { + "msg": "1de039", + "secret": "77168f24b6ffa37133365177eca5712c7c7e24778b88dfe4a90be9b521437e46a011bbcb46e82f89b1d14d5233da1ae29007efc7c220a8e768133421506ef811c2996bdc9170bb0d1005e8b3832343c11958f06fb46fee2f9129de834deaf1b78c24648e360f1774160945bffe4dc72b981eb7b0eff602099d23b53fb11b0791d38d3a14b94023548a3fc7815b12ea985896e40561c5bd242e927fbc6407486618a3348038cef8feae53d7f097434b61b926dc5098debbba71ac9090f62b8bcc", + "signature": "f50c99805aeab3c47043a9eb5a330219ee8f186cf7d755ff4828a271422e64a83cf497ab29580f377d5b1d98199f316c3f4d239f829e2fb7d853d3e6e4bf480c" + }, + { + "msg": "0f835c4a", + "secret": "de00e0faec7d8e604e7ad2c42e07257af1eeda664ce634224470f96aa2279454cea1e161bba165d1482160738aa1850718881b8093cd1a72939893496f834de4a731b84b298da463431db29e2e96b0bf949cb5294e7a1b79b2c0f5e6d0ac0d11c267d014a51ce01bb3ba3b5db9ab5a2e9e2b61025d784016a619cdfb6081dd9c479ed89e9acd5611ae1854127dc15d9711becfef3087144cf264325e722125deb8406e7aa64f60f8e25fe564e9815d4bfa32493b5da6dae1b4aa18e32e662ec7", + "signature": "18fdeae3b0e813eb33552c4a53e9eda5e082134a5c4154bb839d1be39cd5f344c12b9bc24417dbbe6f1150f37b277750cc0425f51fd7d1935797cef708f97d0b" + }, + { + "msg": "d47af195e5", + "secret": "99673a0f794f2c426e69d0ebab6d808d7fca935648e36b96297704225127b3f3e3eed9615cd442d34bdecfbdf464ec688804ebc09156ba7b204181dcfb8020c9bd6c2e8b37dea2ab102ec8d9fbc788b68dfc368d8c26076944d3104ab2132de863c0d68c265147f30c9d53d2c515397482ba4f520515e6b4ba25a41071fa306ef6dced9b349a0cdf78e6156a86a7552b7ee873ae3b98bb20ca09cf26710236618ef684f4aa2f40692fc436d521bac30f16e4f51ab8de67ea52e36dd543c60a1d", + "signature": "b6368152bf4e0110b49cadf0e828739949b2e4c7067e9166bdae4d62da755812e54dfc10406274a0b68b20849a0f58f76425648165128321773871ca4c3f1b09" + }, + { + "msg": "096bd2b3c0e5", + "secret": "ec8a337d9e26f9a8656abba3b66c82ea8202395b4e779acd49bc24926ee98d799156791421ed50daa4ad71d75ad827a9ca2eb1de26d7ef8bc5f5872b89daa4d0adf098bd596673608529d2984858f158afa112a670293fea010e7209e3f9e44805b71a4c5edd507c3cf5dbf3a6234facadc1704395c08b4bb396a04ab9c71c4de4b19a50eaca45439e6792cc9a8c76f8f3529a2215cb345f5dd2263530aa9a5d69de2408c7da83185c9b11fc5b6dd76d3e3d10f5bf96d46fc05457f10a22275e", + "signature": "a68f4ad5b4a1961b9251cca0ae96ef6b4c3b37e00775850ae7b84529c2a738e19890ca666210efdee2fc830f2f47e3a8434100c81e71a9f6ceb02ea15a25f303" + }, + { + "msg": "1c892685ef88b4", + "secret": "ab2c9494e05a05eef0aed319ebc2509aaa8eab3dbbc9ab501a5d54bda11dd62667f186fc0a8d5c7e7fbd79810ba5fbba26324c09c059d930b2df331028af55410163c416cc69abd2b61bf2af90a16cfcc01e0cca8ff462a227037653ee1185bb03a401370bdc4f7dc52131e0984f220c37ccfce3b83554742086bffcc51ec907f0808d4a3141d2de64ec2e9d3caa87380afcc4fc611bd87783a766979066b4660fd2ef0de90cc4c7d4a8566ffb1c159cd91761536a54e2318ada8dfaf364a85a", + "signature": "dc186be3f06a52adc45157d7aa7dccccc71806dce080207669cf407961252efea3cc0f9424bc1f59376e4543c0c0665d8f7cb52a6b49f55b6e5a2efb8828b108" + }, + { + "msg": "31435f5af8c7d632", + "secret": "1efd4e59c42907ebe96b80a009c600c7b443a65b1d0819f75669cfacc4370db9cc91d5b8689f5756fd0e0d4d03fb69142400d314516cb5e2c7dd894125bacddebec7832a95e7f9bc125434754163af7de42a1c51bf531d07b4417f66d91583d241c0badbc65e75b6245e3f85b5cd10badef127f6af970782bcb94f485365e798ce0785c397f267adeb0aa2e0f1c53ce5c6d6189e804c29bf1baf312d3461c016aebfe9f86d4d974daa444cd667ebc2da12b485e40eb448e9090eabc93087edbe", + "signature": "6b56429e4f956a62a914d7dde1d91925239a3abab5b09eeb118cf27b1d483679b1196726c3a6ada658a2c6e29a6c577ba291d64e0ff39b194450ce2179f5a10b" + }, + { + "msg": "91c4334bc8b30d6e53", + "secret": "0f82c1ed2e6cc831d757a1d813dbf52545a3fa0c6e0447ce4f15bcf477189329c9757cc7d5af7222e0b85dec3866a5075816be5fd95d9e47ce01b89be89547956c2634b82c682bc78321583333d5a9178e2bde87614dd6d6799bb26b2d1d098f240a25694bb3edbd4d727237561daed4de8cd3e5726dcfaab83ed5909fe29e9878e380babfedb4a586728e14c5455b9614d13ed8f0cf4c997da6795c02337dcf188d2f8e173cd0036f229995d0871003eadbde82611c6d700a727d9637fa0c55", + "signature": "d13f2dee2bdc8562f9de9c7be7dc288a0283f553cd2f47da13a3bd758976c0a76a2640cabe1eeae57e26a21d2da6126156a0dd44ea8627270b83f1394adf3100" + }, + { + "msg": "baa303b23e270d368b70", + "secret": "fcf8c766baf7d8e28125903102b7f63e8a99674df87f6e5e45c1bcd6607d03026ad93b4254e2da29e7a61e752933031498b909bc8d2d414d89694b70e5179076f8cf273f8b0c00ef2875fa8bcb8816c699b51d2d9bc88c96131bb80471f568f35e711884a8caa9612a61557e611b56ceb678b377244faef401e2c989dd686f3827c4b8a589aedb765764ca259aac27a44bac0b38d519ed3d10fc76027e93b760d05b89784127a4ac5969b0563fb3414cb460939973bd7f36f183519938bfb699", + "signature": "4f235ddd8faa0c2c420054bfc71f9dfacf89f35e3837174ce125da428f154972a9ed12936952cb87b6484141bde6abc2f7693dd29892abd1cfd3161261e69402" + }, + { + "msg": "1cd07d035f88df29d1c2a7", + "secret": "c58977ee94fc4095fc75930b5391003977670cfc57f93e6b51a97b052419905e06b00ba580e5191114e4e41fe47918392011c3bb0952c83f8b0669afbb44abf202bdb041587038b003e0b1a7ac4841d63423871ece9ed647c4dc59c3ef17cea432a417ac90e03b3441e0a1b5cfd74c7cc0a477d79a825d8ee5032be29abf7cd544e4cea319e1099bb4e875f782bacb29efdf29bdf599fc65840d4c1f79e0f2977fac12fa137c831ecd41fb54d14e95f0626bb53e2b8326222d002724f6f9c87d", + "signature": "9c3780381d5d59f6d00e8aa5f29b5910b371ec74475fe8de7fe36ec68ac09f4ad7a50dd742db1cd4145603521c320047e7c469dc5d695dbc538c65a9187c3905" + }, + { + "msg": "08825f35ccbfa94568a1053e", + "secret": "92718c088ab4f58e74db26f0e9989d9008bd04127547bb6a1d42f526c0d68db55af70a09256432b10078595475b18b881ed403023ad9478a13e1d45a33ff43afe4175dfcc44c993a481e2c11b5c547d5be8c1de1c4a577dbac79db0511ac9226f052a4aad68be2fe30d32351aad674894ca8854ad48c2fb4d1e04a3a7317355e3c26738e41f12e265c1ba93b2516b8a89a4ebb3d04559fa08c9f5d25d5068906c6298df2280337ce6250649991ff331a38ad3f57037eef54f7b684a8ff76d9c0", + "signature": "d407e2f0a25deace7e93c03f5fe4138da6e6082c886d4b6919e25a43963ddd9238052bcfc3535848edb1a8844083d73158ec7cbec0874b9eb1a3ffcb9db75e0f" + }, + { + "msg": "1e1effaf1e26fd98297018bbaf", + "secret": "858713b5710311becad715b07720fd7c60fbccd6bccc9105ec721df9cd2d1df1ba39aea58ba766604b9ad2348501fe9bdf131d7b44fc76ac06e07e047a8f9dda823cb6d3143c3450dee3f6a9c689d6b26f917bb45841d3e97e4960d11d811f8ab4156f2370400f1c2e045baa9b939ebc50255819a154054c1c7347043ddbb80132c1039d70bcff25c5012e885b3b83191da21a58f0d21fbfd7f79839502ab5fcd4152131b692d882c7c06a84132141f50051a2f250d96307099c9636f3121333", + "signature": "7fb8e4de26c7e0ea1f649658d96074bafce3dc29ca1f11301a13c8d4e3cd2e0546a75b92b0aa8e2f5ffc1e174e612dfc2c596932cc97a3518e0ac4b36b93000b" + }, + { + "msg": "b10d1c3b7638ecc5aad8125a07bb", + "secret": "c3a7471feb887f06a308333b2e32992823f6fe21456c7a14ee5d7b5f6847ddb00812c16c85a37e042e18c03e9884156b2f5a9cbe187e7e95cc1bed76fc2c88aec2bb774b77303b78b0c17f3e87f7bc036a1a680f66fc34c2d32c915808f24bbeccc5261ea66abcd316925f5b9e7a76efef1b39394adb3801aa3ff23f975af7730e34253ef99fd54e21d389caec44cc701b66640a8564b680eebc6084324f3605367ed46df56a6aa097306ec47abc92799d8bfe4f2c52bb5c0cf3796d9a7f8d8a", + "signature": "8f4901cb39e22964e9c79908c1ada05d86a8566a9cdc3ae97a57185faa9f769958f23043a2b31dfc1a309398b193719a03f7d8e214bc5519b7ac6e365a68530a" + }, + { + "msg": "d4b325b88ab1b06acb8cc0ac9c4c07", + "secret": "fa6c2bcf013529cdbe5edeec6c992a8cca30e71207fb5936224e8cace0647694340084658b3c438ffc01bdbfdd44311ead0ed85f8c4d9d9b943072ef9173c4d811d484a685b18eb57735ab5b2f2e8f576a2f5b95741e312db16c3683b8b98d2619b9fe58a57403a72d7e08da11872a72e55ad26d0f255ec6b029d2f20feec88ef3f6aec57cce1c65d6ecf891ee3d94a536160d67e5119abe780e5e70ea54218ed10a98861a2f17db790ef7d5826929c44807984dfa5ee448a17914fd868bb5fd", + "signature": "f91e0ea5e8ffa52291a2fab38c264b15f38faa9d95fab1af3b52b6500702172b775c10dc75acbdbcbb2583cc40ba3c3bf30ddd0e80b8e428e63b14820d68c90e" + }, + { + "msg": "5c986263ff281b8f06f09b3c52ad12c2", + "secret": "0a9c99b8229af131119656e4d0953989ad4277c3d3c4e00ef719291986d0f6ede2029566a55d5be92a4878776e0e5212f9be306d06ef9e082743eeae1159dfb5cb4e8d68837f5e07c088affa09b21997e57c5bd68824791d2e554c094efd0a9649e92b1a729e297676a3197581ca70d16297911b8388e7478b30cd74c068321ebe71d1ea8a34a4e50c4502b9ffa35f3f09e33b6f9ef8fb03be809a1d0f93c29ada81c1008189cf976cf9713c5d752523f679ce139162219e4edec79901190d2d", + "signature": "593f57b86df9ee51f13ec48cb8ed517e6887d4f98e0eb7ddbc8974d335d6d824c2c0a41bcfcd53ea70cdc2efa48dbb78e73f3b523aac919cd4bd49ed00f1b00e" + }, + { + "msg": "1a1c597139d69cf78ee1ee720c5b3f9cb8", + "secret": "559dbefeada3bb0370b491edfad2e7c86f169396c489a5880e63f773491463323d3ed9e6097b488986f8fd450c01aecb2c7637bcd8a54a312c12d52a26ee8372e1290704807e65b67ce4de3096edba2bee12b7101f71cc4634f6904a93ae4d8d6c5f15c01ec72bef4bdd4f6755dbb2dc9312c9602abf3f75974bcb414e75ffd9a3dd1799031d1014b0be5102efcd0d6ecebea16edbfd77d23de3d8be2ca0c56b5f25c050751f95bccef6132501f66d15a7607478c93300f76e929d41b3124f82", + "signature": "77e49f290ef40e131427b375743479b62d2c20fff1455bef12f702ddfe75f13e34d0303e6d0d5e778753dd04541509534b3ad2e370e511c95727c47865716207" + }, + { + "msg": "10e14f3ff62252285230732146660d40e59f", + "secret": "c3c0c07a5300dbfb89a30bfbcdde4c901cf0d70794045739aaddd1a2e70dbc26bdad37fde7b2691fc28367234cb17c068a83844c333d50b8db7253a7db9d3fc1b25d56cf7a7f63a6a46df2849547092e0fa2fb613a65e9d50ecdc916e52d3c44d28e02c98bc455a4695934c567d991c2bdd1a878770557d3345d1516ebcb5c1c75ff51121cb00ff3998055db42b6bd05b29894e436900c9abfdaffc8ab8d35d58c68e89e455ce0b758bb3a27bfb91a4b786d60ca553f922877e32c022a0c858d", + "signature": "838b5c25d1c6239f44faa69e378021009847a9b26905ef62f48b64c3c9d6e57f1b123044059b51dc586f7371729f83acd298e86085638c503d82c01970664204" + }, + { + "msg": "0dfa9197d7d90da1a0552713cb2de033bae3d6", + "secret": "bf9a92b7f37e017c8fa8b21dd5c7968ded44225a24e907e6ecd8488ea55c787fff9befe57ab309b81182a724d15218749ec6f92687a2b4745d17d6b111140270c0f64afd4c39343453e9cb2771d6a3e71a7572a1aa4930ac286fc73647b4581b0a5c581b6e445e21c506453fb4bc0f90867031b76e00ff3626c3f0252284c75ee71857274f3300bf953d4b3cf02ac9959dc84a118732a0fddbdb4e219ac6d3947d1ff84722d726b5998f8ebf5b97efe5945e6fd5193ed8d4ac9b9f4e4e70d54f", + "signature": "2d0fca2b11f0ddbda8ecc212c036714ae9abe7c60badc4f9bb2a766c447518dec1e986d7bbe73e6aeb9c210db39516823d2ac0d825d6d05f6ef029c673edef05" + }, + { + "msg": "57f799a203b76beeecd0d9002b3e0f14d140dbd7", + "secret": "05ca57183afd83681287fdc2ae4b7b8b2007b154ed5ebf90aa38e714fa6bdcb66364de3a1e0a9e3ccd175ba36c2764e96ef9f9efaf4f6a635c3fa157363a718391c7ec841aad76aaf49e22a4186d9b9a9a8fab98b246dbe57954d5aacb11ed319e1859e2accfbdf3525a67fbe45254415721dfbc767d126a5d3399583e32724b11b42c1230141df7c878f5c506df6ee07838effa0d5ec4f8cffe8317889c58b240b09be743a5ca8281dd7520ae245fa1966ef302700199d161faba90ded800ca", + "signature": "3ab59f591e4db95f2fb5b6fe31bc98312b645f587980cdd8f47322f031c95d8796c9fe1b1a65175010ce2ac39ba8d6a33e5e757d9a69fab6c834ed3139241b00" + }, + { + "msg": "61c33b635867f2db1e3ac6f71dba0ebb1415b1f240", + "secret": "7d0b6eab990f3c7743a5728bb67c5b77847ef6cb823e856376237ef00b6972d30b8a885c3f8343432e15784d0b88486eb952776886165ffe94f1dc7712a9d86dd28f3a4640962a4da2b331cc7a780ef6c276ecdce03f8f815e2c3e05fe8b5d5c99ab844a2391467ee52d51b6d8cdfa9732154ef8d8df4c13ee2d80584cdf768ce0acf8e60e63d3575f6958badc039129f17633d013a395cfeae908b8d097c54c9dd54a274bb23a109484365423afa49de45adfcfbe2a2c359fce76772fcb794d", + "signature": "4728108cb24ca1b9a8d525084edd872b8d6b66c0296273de8083d482a551b49b878b6d8d2ead9f7c9dc4a56e5b3d289b5c211597fb4ee0035bab1fbb9781ca0d" + }, + { + "msg": "0af943aa1ba947c18566b216164fab0ad1fd5bbb3857", + "secret": "de5724550429379115f1d5d4d9b245ab1ed60112053acaa32b76128b9f9b16291053a9031226fc21792925879e41e2d9b859a37cd28572a068c1e5b2fc6ddf1e3bd5c1352b63147220399a64ae78dc6b3f64c5c0a2033e7af7c1c0b1032c691f9e89477b71cacecde58701df95cd292cc8fc4a8b2be7cd9dde8cd5d17ef738b515b5fa86a602709abd97666ca90b5146c20d46e94b71c87e060cfa1ebbd2230a7835b8f72581f1c2e9008072a8817b975f2dd52a22d6c71c7217f72ace0670c3", + "signature": "9dc5909e9467547c26b56aed4fce436528f74fdceb3169a3e9bb1b7d6be88f95620bf4f019298dc220d693c24881c69eb195aedbef85541200470fa784fc0904" + }, + { + "msg": "5c65a398a56200872c0ca45c45132cdf9a3bd8b817dd18", + "secret": "4431e3f3c02f2ffeb04f3b12d821309e0c56f6e07e8e05cc4e852350ad2c3b3497844c7afc1b4438780ad506780b03827489ad6cc6185c24373759901d70a84e961308da9c154f2486efb30bb1de155d4668f467be08467a7e6ab377e16ae0c8ef64adac1677ac9962899a873193a438c38e33b8abe47431af7256b910c6f38a62192d8d6ece4b10fc57be46e8439ea4858c375ce64d95b89017b35b938874d0b2917d0bf920cc0a41f8c8c189135a83e88ef09fb7f5dc3dd03a5808e04e254b", + "signature": "8d27fe427bee08325ae404e67d6e895ed76b076cc99ab5d5b63fbb1c24bb30565a9daed01a1dc71ceedf02ea0748c147cbdf252b7ad438474de321d160fa1103" + }, + { + "msg": "32ea9c5bc2b35be4326209e63f92a615fdf9f4fd0b8d9ab4", + "secret": "b9d3a544018c3d1f79e7edbf10898ba9077eafe822484cfb1bcac01d6f39a78608ba8aa3f763d3ca6c4926273198ce2e22f8228bc38bccedbc764d536ef946394adc30ac9dd8fcbcc3ccb7de9f8752e1ea2ad35e29fbded3ed3fc3c82b9509ac8dc1739a532b165cbc25134e9e9600fa118e4243a5d0e0d2825d403f4ef0bb9bce34d4afd995250000cb4ee210e688bd759464b28209973ac3c1f1acce3201f923e49953c91ba3b1ea2224bdc08207dd1db076211ca8b978a03b686219003278", + "signature": "9a91d8cad661fbebd4916797da631658619bcbc9f1bafbbc3fedb02209afb2efca054d4dcdb02b71ebf3f6e4450e6a8897033bb66bec8524c532076a0a51ab08" + }, + { + "msg": "f1f00c976756033bc1eb9eb9aec39e686553e3f1e8aaf24b0c", + "secret": "8bd90a0de469940511531fc0807ac4b2668f580c04cdee065504ebd8111c9ae476cbd821999b609f39d493f7f6c3af240d94eea78b9c5f3a90db780bab73fcad84c274aa65206af7f14fdeb18bdd4d14e30fda5fba28ee5df6c177c4aeb858872f5b796d9ae4d71327f9db7a86e75440849358d2f00f7f89c08ac0f7baf82e8b3da65d564f7f7aa6121115fbd0eed6b40a68e717f7a7383a6cccc9905ac100908e58d127d9d52a3aa3153f4c5f6db588984024aea2728710a16f121f65456e1d", + "signature": "4d188f011793923eb01923573248446ccdab67a10705b86f7232f0a56f40ba4d37936a90cc8f3fe6ddb1d3aa921d33c78197d6a46a19c90fae3a4c21694a2702" + }, + { + "msg": "a750a49c41f53114180cc8afebfd522bcb139f5e59d7c63fc3b3", + "secret": "172caf30126c6ddf1028851374c5ec7a66f3be82ef8729fbf58c9f0e68fd81cf54f0c4a711cc7931275a21a6824ea72965da13a3e0109496e5c5045ef368fff56b25c38dc04ffe5242852c4d957f5bdbb5a43d131e1cc456bceeb1b4a8fea1135a5268ef2f07395c397d187036ddbe8703eff542d35480a97570d5813116b70f265e93ceb6bd59a51eede574adf98fe80e80d5f57290472c42dbdbf7effd48172ee4c52a99cbe0fea50851fbe7e752034b75372deca98e5857ed01fdb5420de9", + "signature": "d0a0bb96e092b05382a29c5f61f2c8170b502d93285e7e9da680e0aa88ff622522e365e14bbd584dcc512ea7cb36ffeab3f66f49c5fc98a6d05d8a8868f4f30f" + }, + { + "msg": "377335219ab373789c834914e61f4132fe3add7fd7f8045014c906", + "secret": "9f4eb59f9be1c6c4a14aaed985616df658ac15c9aa2eeb9a6e5ac1752595fb3e58ad0b06ee1583c0296dc547c34e67bc691e593dfac76966c7edbc720e390d31c7ecb62dd1dfb9f9ea5103ff0f67c74c5fc0644ecbbc36dacd30791a98e361d0658f684741f4dec829960cc600920ba752222721f13a53a4a24a090b6fabbb48a85f0ec72c573b08d26817e7393fbd8e73b9c1b6bf02c938491ff3f11bab8b09266fd907a2c44aafabca8cada70210d760bf456c7627232869cea28a2d13af63", + "signature": "a37b5a990faba13742863c143c6c033a84a722cf8d56a854aebd6224f3f0b15a9f0a4d6b4cccaf93a14e6df108865a319bdf045e23ad691993e0d712d61bc305" + }, + { + "msg": "00ed421cf550f588517472901845132d027f2d3e589223b853fc8f3f", + "secret": "ac872d842b74d2a9236d3eac9b6c4a93399ebce7e705d6b1815d2a71adcabe1e14afeac95eab0a538fbeed352ce5ee4bab8f023811c287a8fbe6e4d81eb924f709c580299a0a88bc825232350ba18e9146967fa729348fc0af69e1fabdfdf170b4b7c19f85d18e8b098872559b4696bc32bfe8de00bc0319c3f2f298f3ea90f59a522045a736c1db1aad77792255df64415db1ab3ca817d30e0105285b9f0bbb585cfb0b4148abd582b2d0dbb7803f9b435d306d6ac4997c74545704326229d8", + "signature": "93c388e528126e805cf0bc87f39217ab4a6399f3d24ed9a0f9c807515d5015e8304d6da9797af3e508059027cd5c186d3beb5ea11850754ed2f71a1e25dff50a" + }, + { + "msg": "a83579ddd3fab7b1f1dabc2afb9e0dd2058669864f81032cdf7b187f0d", + "secret": "8e41adf63c83d986313a879c7db81b4852f0291543f410cc6619b3233f3702e7b549e73abe52c917c419e0b44357d46b466ac813bd3cb0f874b6915e4f1fa701e04608e2a25da95d69970a3cba0caf50f46ae13115defeef36a51114f118235b47ed56f1a71ff6b97aca5c63688d7e0b3a67050db74a84f1373001c38d6df83049d6d212a72afb434acfcba26383fdfa8b1a44836fccd227a2b5b1c82699e21479a0e75ddbfb41a8abcb43ad0b08ce1736186c655cfde7be978aac09c9ae52e3", + "signature": "7ebcbfd9bf8287c98d25bcc19f37031cce6eded1e2ebae3bcbe621fa0bf93673091cf49deb9af0cc22ab35c13f35e22b0b4dae5716c4b72d95bb26e133f23e06" + }, + { + "msg": "8e916afaf7a91c0d1699fb87aa1d155664b267c9a6b6bece533b8b0b00f3", + "secret": "eee00b0c665a6f3ec110575ae7581f0a52e28a6eb680ba346027d41db6dbd3cdc9d2420b5899ccd2f9570160f4db0f7b96fbcde967c1a611c74da1207dd6aea6d7c3d0f8a049cfa29895c8bdc0b517a7b0dc45c79762b7632130966ff9bafb264775f071bdfb4218e05694a5caf1ccde0538f560c2a74e751ce9fe4a718b6817627183981e46627d9788825eb5a20b13f07a7329b931a7e43dbfbc4f7ddba74ecc25ef9e902ef9137357cb973372f1bcf55c0d4b883bb8a95361d5d591abf656", + "signature": "81b55efff88064c91a4e7b6e53b2fa9fdfbf4bb4d667ccf372b9397f097680810ff1cce6596c87727f5ff612488733a2e1e2e475f6325546d2e9ee8d77a43d0b" + }, + { + "msg": "0d828225943c42f8f4c52884ffdd66d945389add7c6650f43ce9eec1d01191", + "secret": "c2e19e318dd755d2b6b7cbb3a945cdc94cadc0a3bfe21e61a8730e7cab9b66787a396adb47125e778da24b45a9d5bf012343bcb989db4f21eb7c40b7f6e366294b901e898519a5c533781c82904bb1deb62a6f74482b1c1cb68b514bf264b0170b20c0137125618f789a66fae2406edd11ffc81e12a892abb19dc399c68ad9a9656221dda5a768af0b62ca1dffef438003a8bc6ea729d70cb33a9d41eb3ea973845a0eaeb4ee726df7a0c02b78b88c608e7ad8024244f64995dd9b02515149f8", + "signature": "17a51659742b15aeda331198a23fd734de661c0c24a74fc7a37b646ce4ac3d9adff4520c1b2d557fbc9b8e55e4a13ef362977e04b48a59fdd71bb70a672fe10a" + }, + { + "msg": "84ca29cac060bbbe0b06d3289fa11e2d876038b3652241bf7c54af40065550a4", + "secret": "e2a9bed604160f6f5db3e27399ee7d2c48431832a553e71da3b774b52b84909ad9e9cef64c201f2a5f5865824b4c18be4636129899c386a68639fdd2dff36e538bfeb994c2ad42b0ab92d1a464115f9d69c7d388ae8f3f93d449292fc11f5d9cf0c816bc2c061e6e0d80d367be5a28caee0eb45beb8e32e8f5fea84e7212d4847a3685a83e6298a5f9d3958ac18bb65f99a717a86e88ffe0d9a4477868da849c9461b4d54c8f799899dc3ccc8217098a1b2c681e7079130c1bca8339e605ce6b", + "signature": "1980b9735515ae71f223d45b14a245acaa007ada96f9c006086e345fbf22a038caeda66aadd6ab49bdf2633beeebd7e316983b8758249fac5d4d3ad1bcbf0d0b" + }, + { + "msg": "77cdb7cc037550f00298b09046c74014cee6025720754060bdc57162a101201906", + "secret": "be479f71db1c4efa3bd54fba1df6542d49d1e01ca8373806203bb8179791334a5bcfe55e2d35987fe906836d6be7dcf75e8001c5b925662e180a793fd299bf2f3bacdf5f51d4fa7e36fe01d67a359cd7c8aa505454ddb8f1fc04788f90fc7e37e27a424fdc04dd368d13412546902bac28fbf32ea244b9f6a98bb7c9fe37bfd5ed8de0ec272b4a88752f50c0361e511fb099520a36dbd910b50fa709cd56c18f71d582e26d197170a11f2711b564c4cec2ee2d71208c7fcfd1fe7aa5b59c2aa5", + "signature": "f96d996eca83d78db952a00cf938dbed47d86bdcf14b3db8563bf15055320c001d287111a8d5195f33d58f74e05c0d254aaac3988a621a13d717a8b961630a0e" + }, + { + "msg": "6058fdb0cdb8dc26d8d14eaa20472357654ad5539525c6d78e4c21970cc84f6c0dce", + "secret": "3415ad3114f75c0a292674f30104d8280d89264262f8ec4a1ec8010a81f622bc54893f72aa9c5e09ad411867b0c7c2101b29f457c4f2f0d56c5963b1e0bde5a2aa4e1690b6fbb966bf568cd82ca672fd9a3e1f23c2c66ca2295dc30466d0da44b283735ecf9b9f6a7e92323733e4cbbaf02f6c5788f2ad0b15ae2a397167a5c1198cfa7c2ba75a23cf2c37f1e3dc1e83f89235257032300a1f53f06eb2a8ea7bac97979c82d26517300ba2152feb9cabc82ff5bbbac13dde141788b467eff404", + "signature": "3bd4bf8deefaf5c0c507a7051ae9e5fcd3d00af4f7127a7d97c33e442fc7feb308c64dee7aba2b10fa7881713fe27be8b21151888d9ca68ee264fba045c36c06" + }, + { + "msg": "540dba0bb10071253c1134a67643009e9b25843fa0ade8367ee00859ce0e9871dac412", + "secret": "06f845c19f26e8f0f51944efda5bb412a2a076bd8a9c49aee30a4959161a7c75659af522fa1e4a2ca64527c57501faf1c3faade692a33e9f43cbafbf98d003e0ae370e48989f362cf4eb7f8741d4e93dae478f93f0dd3fc565c702a56748679121755c2fe9cb2560fd3ebdcbd98e1d4ae9580259d32f14a97cd260bdf33a67d53fcd8f633f4fd5aed85701350bd56c9f722fb27b3b2c1dab5972fc2ae24a0565a3d439826304cb776df12fd662aada1e9e6fc8d08434b5a51cfef7e31601072c", + "signature": "82ec709d67b05f26513d40e1dad2afaf7e649dc50ad88a51cb7cdffbb09a42090e1490eec56791ca6387ecfdbdfd53632a55adfd70bd5b3f11449eb859ea380a" + }, + { + "msg": "3dcd91e937259856842629e72958e7087af405f9fc9b38e9e3a6c4853cbdbe482ebd2e0e", + "secret": "09a4c1a3d127715364884e99cf10682c0e601cf7b5d29662f3819696ab4bc8d676af39c949b9d8edccd986ecf5df24623dad8515b88a1243114c481a23b538e0c0cac744fba3a13b68cb42ab5a9ac7ac13beb85d1227037e5809cb55105ed2bbd0305a4006db452aa78cf22e750467c861d9549e252e76df8f2a737db224b98f588825d1b36dc9f830371e74893c024dff874f4aaea57b23fafd0887bcbc410585e554950ff503f754f3cb3613f7b76da6fcbf6352a210e8dbe34468fa7acafc", + "signature": "0e8677030bd08b533ceeebc82866903036aff78993090c1f82d46cb5a4e0643c969098db7cb5a35948f4f5a07389142065b8304ff2e3e035ed1cf1e0f3f34d04" + }, + { + "msg": "98c786b8fb6a341975c36e52d5db2be27f0c37bde3a1c954b23ad50a46b8730575c6e21ca5", + "secret": "113012250e06d0f5b124368f3dda49691e2d5f131091035b94d2f11fcc2c5f8af82fd82d1642e18c57a00b33d82787433a6ccb0f9fa070e5d2b503bb4830428a6ef64ee6234963b9e2dacda022c221c0a30d1828f989ca96635d4b12d07a174a2ab47cebbe833a0ae787a24eb81a956e9506f1a1acf114600741cac4caf2d19b2e1ebea05275f5b9443caf877cfa2701c8577b6a30cb81383805a383a34b07860fb440088254f0d63b247510a052b449d78b786e3ff73d90952df4de7ba41ade", + "signature": "27ac3aa7a64b07e47fa4c1f029bd4edf3f00c22efc7dd2877f2d1fdf963e95feb7ba3e7152b83fb87e4aa16915059746b2129cf6366324ec980618a042795e0b" + }, + { + "msg": "11a9eb5bd6423b92867c2990465c0d5fff23b38a402945892a13a4c083151c94c46de061547f", + "secret": "f68af0ffa72c8ee63283cd73218affadab28ebc63bfab74d71e4a807fc3f8c19b7203a2e2eafdc541f00e43a3e151153b347bd81a05278ab2163161f3eb58653ac18490c3852124d64c86019521fb7116b49059a19015a1027b4da6ae59ec145a5e23e19ff34fc6464f671618c414adf576433a710cf72ca462fe30e9687bc36265e11a943e4444d26ab22c5c4f79f0a0b28d57cc02e9a7808d360504cf4b009da784cff7400830aad210cd054d90f0567a29e59ea8ae0188be4a0a7b1052db5", + "signature": "db64286c0449f321a135716d35507b57ddce158f33910d0f82b1e869dbae1bdef84bf48d8753c8272ae9dbace0e89ff844c5186d421cdd749cd4f4ccc278b101" + }, + { + "msg": "ffdb912456cab3e000e920c0dcaf3a1f8af720727949fe682fde2344ba3c732fd1179ff3ad0d8c", + "secret": "f6f260d3fa72bee5a39e42aa3df87f22bad96b23e2b722f936071aff255e7e4c607688663af30461e42bafc67fbf04b67e0afabb4b2816a8ac255bd8020549e57e36932b6d1588b549c05debe1d89e97d5bf0b226610e68d66691c82bca29fd4e98cfbfd8dec2eed000ca82672c5431304a6e5c7f4f83a213d17d2dcc0dd3b22ee85e7376c2fc25bf49eee9308fcc890c8ec684fc26624481ef1fa99612f890d6cd5fb4039d2e36f40d8e14a3076a6c6e59e7cb0a578d27de774ac8b36f7dabd", + "signature": "df9c9b2054fac67d7a7eda78633d59772b05233c1c08b42ae4a3e20ef445e9b6a39561689ddbf33bf2c535480f629acd5082cce72020d250aec182922d302604" + }, + { + "msg": "3cccd89faeafcd07d321496ca75c5e4056c6615611d831032b95881a4c38adf0c0908d0c11f8d742", + "secret": "d9b95acb359da608f433a72ed3a7fabcb5c6f3e416880faf030e7cf0188990ef21742e8be22f883e7882a2e0443086ca050dfa246811f0bee602f2c409cb2148cdcbbec35ca1b9c8206f58b7d7fc1736d81b164af2fc06046c90a7ff6523ed9917ed244e176f0a8b32016566db1a04e1279b536ffee6200db00234127adee37bcc8e42f3196c60b627677c064028bfe20f5bce3a8c3c5253db629f0e9db3a0a78c7147a38a540b720890a92dac68882d23c100701dc662beb60759c018320945", + "signature": "0840f821fcdddfcd2bcd72bf265c512fe3b305bef8a37630e1fd1e0731f8e9e182096babb9f237774573b1e4b30dbd043ec06f0ef1682f767b22e80fce80c309" + }, + { + "msg": "e38c34ef8d008e16029fa0a45b8157a9f122645fb102edc7c9d6e3a6edc6b516bbff2dbcca7732d0ff", + "secret": "93260e0cd490e4e19ed583490c8e4791dc8f73765cf4856b39b2886f07583a6090a813dd83233ec996dfc79f70d24c1275c4ad10c268769e926d7ef09d6fa9cffbc07699f31aa14cb434a4d22272d85169cb05a1acd276fd7c9392752d475957ae13f289856f55935483c156742f9604694dde232af09d1a214f21fe996077670e9a12d5b792b5a64554142c1a0b6e2cb746d03f033fc6e0b24850392f68ffd276dd07945b2ea5565443b350e09f5c22359bee9ef590eeb28451a0ee9e8f6ec1", + "signature": "9d1072cdf5f7d4ae2d9deb7595f61949b51e00e13362656b574d24b044edd3f6f3cd30c892dd4591543fac20211b0daf14e8769c7c57a4c5a5599de45d7a5e00" + }, + { + "msg": "f97320053eecf1c5ed25e879aef482c2fc250cc5f73f28a07f9eb63ba646b116aad2e4933612ab471b26", + "secret": "dfbc0a6aabe54e60a03e8ad270abca79e77b02e578bc3a6efdac59d3d0e26f5bd5c87074970149a6900f012a8a401128416b9dfc171ec773ce788c2fd59d6977723fdf774589620ae999045aec39ddaa81d2b5fe102c500affc6ec7be7d3dea673958de6809ad821dc206a157224047cb160f6983503621572f3dd4c586a175f4f222a38090f4b80b066c03ad4df7d1c524c9285bc0ebdf52dcbf65173a5727848de231c6c38f2cc7df68ab4724f93af8c54d635bfdbacdc38f765f9eae37563", + "signature": "22bfd4267c82688d7fe4df0a5415bf4ee1915a7d645230d991274f28c75ffe092e649e99700be55ceb2556431f4f556e151c13132483291ba99a42925a983901" + }, + { + "msg": "82cc1dc5100b550145332081b14a0c0a818971099a9ada9081c63e6b97e37b837aa6b1dd1bf64984e12fea", + "secret": "8b760380b168746c45b98c8746f87b76bc5b6a3e1d47e2a9c6a29943ae779758e8def748c32f0741bfb989c352eb82e40ad210c26bdb3124b5402368021e9a541ea5d295cb51e80bfd2b571d5ecaa4af3e8cbbc3133c2df10d5d342c8cde0bdcca48ec6d55e3a496edf3d668e3abfdf2eeb581e9a843e7fc2e79d8e23a60e3d7aba6b5ed2e43f198f551cd16d37c338c9dcbb22f7419d1ed27d90a54bd135506c0bdbbef03c0ee64990f6b9d2f142127fdcffad026da20f1c6ba5161c7c8a1c5", + "signature": "19a64cf1f6d10d79e16a46c4ab6a1efa826559ca22eccda281591feec08103d73e81ae77bd8faf0f6335abfc39c40462651a02d08f9ed9ad215f5b3f9ecef909" + }, + { + "msg": "b3321056c477b29d01d33c4ecb7bdc7eb4e0465ce5baf893cd567f2e5e6903109ef2ff5d9dc3f72a09f33d60", + "secret": "50f14d305b5e2cce21bbfdc1e80d2c692ec3bd1fdf44c227cb54402ea2346257173984a5073e671842badb5feca063b89bea303ba38163a153a28e34dcc00aee670143dd5485b8fd238d0435ca8ac3e4beb5376a3521c8599bcbcdac8ab7f52fa494ddb204a33781c5aada6f7826a4b4387cedabb2fed36ce1d8a95f392a55224c4ab90a50d409fa6b56c82bd3f568bfdc70bc68495611bb6c1cf03bdcfa0dabdaa50e75c6e1c5a1892e1d11b52bffe1a354ca235d2c6237e49455ddabc8e877", + "signature": "354f0691195a4d3a4e4fc25ff9bb8bf9adeefafa401f4f169357fbc6c38465d4ad20d390381bc56d5c9c989b52bf4584e134d69b3dd657b0d994f0cd94553404" + }, + { + "msg": "953ea314478c90cd3011113e67b1bfe2e97d595822756805d61452aafa4c37da2063c0dffca27e53a3ade5dcf5", + "secret": "878ebb00d13d5ac9bc7aa832cdb1f7da676323631fc06da6f5c241cbb0da9ea2cba0a45835bf322c07f43a366bc15d4edb667b290c5e02c311f6f452f96b6d72a1fcc4c0f0dbc93bfb8104a1816066c0800eee867a0c1bc6c6e218e4427aa3acbcc891a8a4a14f38ec9071e324588aee40fc76328f3848f701347b90c64baace691dd613fe867e6e6acf756fdcf0a03347df6e5b1514dfb4673a5c67ffcb0f83aa8a18ebb3ac67e574c7acb2e980f806af4c5a0afa6b1c1e4681ba3050441cfd", + "signature": "029a77d2933cd8b4e787c61e0c5338ae9100d4b563355d44c840ac59eb3eb8904bb5061499ffc7b3edbb20bd8b1001c23b19a5c3a2f2ac2c8ae95ef3e78aed00" + }, + { + "msg": "8efa8f6a4ce297ee6736bbf315b08350cd4c301f8dc99b7fb1f33b9e6ab28de0343aeb60e802f8864794e2207312", + "secret": "cffb85b122672c792af8654935723236f937ee1950131132cbced90ef6217fe28778cd4053c4a9ee32424caffdbc04bc7a39f8185c4d4e55ba41063e9bfcdba304983c4c4c121373a4c0ef94ff51758545450804da495baffcd8ef54c63443cc5df775f4fbde59ade0206fdf350bfc4a2f9ae33040f0623b4f427c27ace1835b2f2a4d2756e254d6e409cd6c586511e2cb319333041fd4084f40fcdeb74c82ea554b756ad1d657a495d12f89a1947cf4bc09c461d4d5797a018a75bde6e51388", + "signature": "ea64399283c2599a0269b19f898d73ed86e6932744f4ba79c816500c40e9ea9d71e75500f482bf7dfebd0e47588d3fd5c848186eaa6a56ab7ba332136f3af205" + }, + { + "msg": "df41b95d5e4843e0a34cc0c435a7c053e29f5ec26da0c67bd73018376aa55a71703347d19d3da0dd2fc9a8a08055d3", + "secret": "67e863a99f3d5dab91e241f7a8fe2d0de4539c7fdcaf03493597d657aac346e414ce9c3001336940dd48fec7bbab7aec1ad5c6a468bd3d18d5f743303de609cd74a6e166f8ffd6506f2c0f57b82c8b4b4756d964dd4cb518bf3069aa164e63fb6646e0db56dc000bf9fac8951369a39c9680ae7cd7ea212498577469f869049a4556d6cb8b971cc56c1b342f3527dca4fb437fc8dad78ab08a296faec512004529fa98f899671e3980673e86eab5df9a745d134582c6a1b5ad360d37c92fbe02", + "signature": "6d498c401d600a0c7a982192d643dea411f3a604607d522001c2a427184a7c4574cb9a3fce5a7c09750ece5f920d6f74616ae7c04bff7d3737a9bc0cff474309" + }, + { + "msg": "f3af2cb265f829835ff1369f2ed88183feac528dbccb3aad4c01d1aa704780ebaa6c653a91fb691449540fd643b89585", + "secret": "a362637f88c3574ab95b0fd2c6a88362aa192ba54b5db894df8911043c37889a3d1e061bdfd7f0dc5d7653093220c65b07ea2221fca145e99e47163655afd9100a7a4603b9f38cfb87f436e22aef5cbc3ffdc7e01715b6bec645ad92e70be6aca41d6aac3a8a1af43ff5d08caf3ef3d86a92497d73f9a83f66739088eceac14ebbe8323083e4778a5b3e9a90e2ff6d84eec159d987fe452ddfb1ec07ca61c474cc7c0c698cf6115aaed40938ea3c9ba23b80637e90b1a47b7daa79e06e5f0053", + "signature": "7b8a7e8df6dd09345c8ab5beefa4ed18f2dfa92662c10f857a77e48e0e1e01d813bdbc836547101690a7b45220fb5a65eb9e3acefc1160b9723932619f952a0f" + }, + { + "msg": "74c22a98fd5f4bbf713cfc702d9e112a0d2c2b1055f40f5b644c23f4755a49881b9a4af7099f63abbd860860f793e68ecc", + "secret": "0c2d2a4e2bfa0b22573092d57825d11db58cfcd4c7a57bf1f164bd6dc0dfdcc6c6a310acdf4dee06a09adac5b61f4206cfc2d04c964f26d98910437ad42c3a86576202010d59771f259fed7cf403ed0aca0ffc2a46f9c033ed8a7cc3a735e071c9183ffd7d33db1664e2c08db0e5b941c4ba53bd49f5734332582c2559f387bd6cabe13a1ce39917f38fb7a797215181435383a5661153166f096179947eaf2c212e3452aca225528b7b88114615aa8b9541afd068e73fca29d069b68b70614d", + "signature": "88d76a83c602995016d499f31c921388683cde0db444eed5ed400eda29f7bd6e8009358a528607f967c699391466ae53ffc26025b2f665335dd69bc931e7e101" + }, + { + "msg": "0d621fc2fff96d2844a9509500a0005c315d41d436f748ac13172e0c87095db54d16a6ff07b05df8266ceec421a433d16c8a", + "secret": "7749512e04192e7755aa5b72600baeabd1d7ad13318dc149a17a7055e9220b12fcb669f5597762d7ba1a3cfb4a6059b71d1690004052e3e72d35db20104c26311e4097bf373f42b2c09797f27ec59fea8f23c97696a62c00cd71bcd41d6ee8c73f2360b894153249484841125fcdfaf09af2d8dfb5bc41c2a4c9167796a3a0664c8e0e397edf862acd504bd3b91d0bd3f54f390836b5dee402f9b90c1d398b77315d0a2941d07581d4671b409f737964078bb759210853bc9514e2ce63cf381a", + "signature": "2e30b313ddd6add786f83a30c52f78c8d25cbd6125cf1397754025d4973547cbb91222611d5af727b71f63d4cfc7ce0ceb0a35eda738392a15e48beea8050605" + }, + { + "msg": "71", + "secret": "c2db8c7775e602050d524c59b2778e12cf5c0d4f763fa758889bdf21578a4055b1b9870630575b6effa9b2c3b6041147059de34584a65e2c90ccbc50831bc9d3fac4c6b730c610feb350606e63a479370cb3883dce85ead0727060ca86acb48712573327cfe541290dd1015851edf75ff8a5eb5e2c5f5058d3d1dc15ddd22412c6035c596ec6995469727d0d3b7045186037e72b21f5e3943151ac40683724e3aa6764bba9001dd38a63582857f19498c1eeef573ae21b0f9ffcdb184923d218", + "signature": "a9d4238318ce5ef9de09d8a40495f806408d0c77d915dcf934b22e46c9b14bd40f286ed5cb14b9142fa1c0167749dee11b637fe29526a2c923bd1bcb2318050a" + }, + { + "msg": "7739", + "secret": "83edfaf292311889d226969f102923171badfeefae36e8e20908d41f025074b95b2a320d6bef56736825f3c36522ef1c98f7979d290482d2ebe16d7357d1049105e4f5bcfec5fcaf668d0c7bf6c870fd7f95bb31d06e7d15dcc9445011b2562437d2cc031d91cc1966849111f26f5c82ddf00a08bd381e45cebb86d40a0172b4743d3fe77c9db7d20198ca873c29063c7749d71fb2ae47337010766046d6e9fc4249ace4a5b727b5a25b9d8cbbf5e29359ea3c57377fbcb60b346c5b1d1bdf92", + "signature": "efbf431e7d13deb4a9f5daad9074daa9b0bc3cb4de66b954bec87eaa095164fdb6084adf3d8632ffe787c0dc280ef228360ef92a539e2bd893857537b1d6ab00" + }, + { + "msg": "8a2b08", + "secret": "c5d64486a09074ab356dfee80338285d376ee5a6b1b82dabf10f08849b13e71f709e20409d6016193dd91af939dde920c14a9879cbb15c4496a7d2282743d32611da427cdd4fb875ecc463d09f023ea8589ffa12028f778c4f21fd712f05039aaec8a2ba453d5500edbb12f70a9171025505dbc9ba8205184c512ef0e411eeb351c6851a40973d9292ceb36be15a737835ee4359430bf32f3404fe9bbf3cc0acd82b469677a6e749d5c31e1c0014b260cc858d41b9e27df21683fdd3f12c7843", + "signature": "6bfa1e0f39bbc23d9e8eb0beb2577523b626a0156c920e44efafa2f85b7c49a2bbbd559dff8edf777cb3ebbfd6a25e87e2f8585e99a45a5de37c73318a5bdd0c" + }, + { + "msg": "1d4497e6", + "secret": "4612daa53de0ed3b27ca80b5af41f6dde86b66f9b48c6d11f67ffa3fa36aac651e066fe699540ef43cfcc36f982cf3885974d06cf6fd52385d31d0eef42a1e0b9e078cc5c11d8038883b0e1e917c4d809d962585c1ae694c321b5b915831d4e3f546a16e29e9d9ff5ccaa270c98f9052be132ffac8c0910ab6cadbebc65e64f01526f69d632da7543a5e7cee6de38fdfbc2ddb77f971799315a04b7e94c6b963097b2abc1d6112efdd5219a505cde990bc41252f866501dc8a407e2356eba4ae", + "signature": "30e7f36a1ed0706ad312b4b3a02d8847f834665472fffb0966277b4614361fa48ba7f9f4c6c79bd933f208366b1995c8ba4eb56b9d658d25a08adfc049e4620d" + }, + { + "msg": "4538573594", + "secret": "4f54ab932b4fc92791beffab44df12436b368d8581cba3804272c342d10c85a74e64a5128486b2f834b0a1a5c091a6a53447fa83fe279b6bc6709be520f8470d3f880036e308ba62b1e5f62efd6034b56d52a3ac886e6c42f87d8aff858a601ee6234b1b2ec999bce25b7f92db3c6ecc3f8aa943045843291483e7233be074acd2e16905940b9473380025197446897c60b102029c14df69106a5e6f73d08c3f1f22a2119e4a3e9123c3c508cb16b18041703148a4bf9865275b802d1c2aab36", + "signature": "60d683b609cee528c0564391a5f823f6da8f5f6c0825260cdc1e551dc437afd543c3d9eb41d86120f25124bd73b9eee8f597cfb666913a1035ec717311471509" + }, + { + "msg": "5a95c9c22742", + "secret": "59b7cf6cf1ce659398773a69c5d29603c414a4b82c72b48d4d23e7e0589cda9dff4cca3dfeedd3608d45c0347fe5eba19a4fa7cd7c6e5a204070a9b3398ec09f62cf3796b1edf380eefecf480a14cf9d44268a2e9ebf16f989b07e0a636bce5cd660e556f6fecd2bbfd0b714bd058b885b134ad0ecc1011985e998eac2937411416ba68e2e96e64a47a9d437fb5a8187cd363be1f20a7a61edcbde51214209d2ce9338363243acfd6f280907c128fc1917d406d20a5c2e0aec693e8d6524ad00", + "signature": "cd5e515306653a1082202a34df46bb0287bc8dcc2f4cfe9fe30c5abfcfcce555d139d706ce9ef82e82c2bf6dface6b2c66cfaad411d95f8cda6cefae74476505" + }, + { + "msg": "ae5c6259f8547a", + "secret": "f7b3b6a5fdd7744654af193cf2f63e6faa6468af348e2fb8f30e2a9d4926966c0fb4aa160aff1bbd3737ce6886167cc0181d81418ae0fa247a8ca491d61338982129244a333c1c6a60db4a290e83218fe4da5090518685dfcf57ea0cc4c887e84b608e736a74ac7679bb5285f45fdc332a296aa9955bbbc03a95d01c704b98a0bf3ca2fed707c09745957522b6beb7088a0ee7983d271bb73d7918d3d1be2d9a3192f94817fd5fe6e3d4e1bf3d0fa72622b90b04c326ec890a8cee8b6d5497a6", + "signature": "f944b9be4dfd5d23a44feac6d3b860b46c28e4e89d1242f5924d154955c57127c7356a29abb29a7b70749d2ab0045f226527ccb6615c37b9e50f180e45b7d503" + }, + { + "msg": "556304c69e120dc8", + "secret": "2ff8a14c01c534654bb3d20a9d104e0458b1f5a710fc26cf6e2cb54df6c7720dc280a5e38bcebaa6de0e84d96839844ed3185f1f6d42dc7ab82865b850009f8edf95e410d4af063e6e703033ccf614fb5b7d3af81071267b697d94f21ae41d761fb51355ad1d804f3363f4f8df17d9acafaa07441c20a8266debf5f528b7c0bdf1202094e1e3fe051cc5bf1937de009724a57c48622473840948f375109718da74a643e52078447c20e8efccd297631a7892b29d091cb37ead0c521d45c04836", + "signature": "34412b82b463d1fb00da90c19f8deeb827a0fe546d64a8fbc969badae07bd8026e16025edfb09e5347918d843a302731a9ec5f8d6cebfa015b688045dc583c0f" + }, + { + "msg": "ce94455c0831ee1bfc", + "secret": "192fd643b0cd48524ba4ddd29cc2a492a4a478a4c565a2d5b349a1b28300e3b440e7042490e106a0f2b8e3811a0a748e8e255c82579e257d6f5cc0b0570187ae3fbbf65b21764097d2ebed24cba4bb89efb56dd14e6a0b640b77c7e6ea5c620e30a0d2caec37fe828188536e8d0a404c712c8a587f091e4f70af7e1b71ea9020f02fc8dcb111d51b84e211a0ea6b5401c673c2cde7f174c6bffd571d0acffaf8157b52960e61da98e117280f5df2272b635768076b3d06a2bab96a6d6845c6bb", + "signature": "e3c932b02268c17f68644f8a5f93816728eaf7df696991452f588518b178fae37caf28df7811412334ae368481411544abe628d66b479814dd5072feb74aa10e" + }, + { + "msg": "43318fd3af81be1bc766", + "secret": "f0a69d79a1cfca50e133882a58d22e56e857a658dcf76e413b25bac2ae33c9086af546dc6b942d46b4862fe7f4c917b94cdabfc162e4902f714374d72ee44c7931acd09a890a5883037bbffdcfc5aba5affea68576d9f6de355dda9abc20883be1797933ff1466e405b67a2726cfa4eace574712a364588c9315243397fb66f3670406880ffecf0545db84a440a15beb6e696dd11d798711913da54a70e78aa32647718902e03cd843e148c710f54f747e43584fb0f10eee7fe2800e9533d207", + "signature": "57d78cbcbb22124d79c7f092fd99f7130dfceb1e1747a650b3f13b2031d83e762e90ff55bad5e882f940f33406572a473c61e126227cc549ccbb9ecfae0ef406" + }, + { + "msg": "91b97994f7d3f12880308e", + "secret": "c145875d318659aff4586fcc91a68abc82b3ce35f5921e7b1420a8735460a0ab46a7806e933cd256f235a8ca2bbd81c63c0023c2f4f2e35e07de7e0c021c093dc9b7cdea54ee519809eb0199c6e8c6f481aa5d932efb867bb300867f91d9800c0d1ec59b3359d64fd7df3b60c2e3667a4f66b76cee5b5bd21909ce32a0b905cc29c01710578a4e79c62497dc2f9e1b78fd3272398403abc1b0846a084a5417805a95a69d5308284b5a63063abccc757199d1d51a65634495e84accf5a2b900b5", + "signature": "76ce479e0039cddb072f21202436ef8de1ebd24bfd2048c3e5f674af53e236b8a343a6a104544451d12bdf2c31c4f8856edc96e8a5afd78b6f3f87e34492170d" + }, + { + "msg": "2fd35849e7291d9b5fd86d09", + "secret": "c6ab6ca0a73cb406318e2585ae859fe0ef58edd0ad3a2c546ae8ac7a6bbd738d83fda6aacd67ee63e2b8aa9c173f87d5ca779dc3da22f1935dd828425010db99423d10bc0c8c88010a230e0b7b1f5f1d794b3eae968daa693efedd0db8403129c33ca776c458ebbf532e1a7541ba6fa341983321c6118db8a8609b45c62d09bca20a31a4e56226d304adda8609ad70a2089841489eba0f87f3ea61de363b2fcae48478a373d4765fab38b3ba9d61b162aad332ded8c2708d841ca0ada8ff999d", + "signature": "7b995c7ab14667215bd96a4ed4495f18a9445c0d7818e3830b512204887d2a101dd1f714b8d91cd7433165b51e182f7424501c5732a7df0555a81701621a9402" + }, + { + "msg": "d48a2a925d7e24b4f8de65ef69", + "secret": "3730e65c56365ca52a6ee12fb82417582dcc8c27be55b529923fcd62b9dbd600db8c794a7bc2aafd8c113cf023c156b103ac19c5f075298f51108b223828b658f978e677a48ff9fcb24b543b4563c317252641bc7e18be57a69fdec7c541e4d8dded9ec11f43539e3ebab51441756c25f0d56ff71b03d8d4ce3c4bbfac8dc241baaa315dd2771b38f13df0bef09ba02e416d39f92ed3732116223779f74f635c7c0556fd14999af25f4079727792250412c473b8b158b6950779753d5e1862ba", + "signature": "5f8970fd922505a93aca9685e0dd6e791b114af6ee9bea92de085e11a797b9d7992b1f81f775769d24d26b7f42c5dce5ccb18dfd8a3bbbfae172365be21bd805" + }, + { + "msg": "10e4b47bc4117e1c4f4ec327b346", + "secret": "0ccdb33c86e08332cc0f3b6a9e78bee88f080cc56b4bfa51f3ce6166d1246776c523507c3632e59d4844adaef3ebf3621cb7f656005a829ebd184827cf69b1c5ea8515c9336f495dbd3ee4f0ad204ae8f50ba9bcad0de08a75754749dc4c589289156a7fd6fca3479135f8cc85f790d95e65f6d2ab403a1c32d4ccd34a0ee0d903cb155b7971f54ac4e68f00c5611b00d904425a99d524944f19aebaaa95f5ffbcc24f896e9a9837ca6a6a4c3754dacd0e3c8d7ba67ba1beccb44d70822e460d", + "signature": "ca636a9bb6a86d400b4b8b3c2b79303f17bc6ee77f8ee0317a892db1a25f09b85d229c3a00c44e040acd2975b6baf3d9b5e879b0deec0fd47a6cc59904627f07" + }, + { + "msg": "b5443507a1409355a720d4b4c03c44", + "secret": "a5b268e351cc2323a52a0e2be6c5726c2e511194c86366a9cc71cab71e716af72d588987d76bc8ea7647f7a874d812a3457e55ee92a3a3b65941756cefbecb637a3021f82f5308a6c3b657ca6ac56f4a314cada55ec7178a6ee922914f6a3e11baaa7cc944542bc6ab658b1a174e8369011b694f30ee4fcbd7141c5f6764c9f25cd5a55870175aef2a5fbda36f152b04f2b2995d5c6a4bec99335c0d7830c52e2246f4a9c4d660e937fd728855df93c7ea6e71ce2c80cfc492174d6ba9016c43", + "signature": "1edd65b799c7a9b0f76429c5934c6d8b71b8e8cca9058a59bfcae7849c11e6d3ff8fe13d4327d17e244d35d4eaa4dbdaa1d2797a6847445bec3ff306949d2704" + }, + { + "msg": "555463c79fc5de710b2389a40437c542", + "secret": "547f16dfce04d0b3fb157de1b997216b371821d131fdb53160da51f66700d2478d12752dff3290f5811bb01a5b3ccdd7d1cdb7b9390de5ee1c7154793e3efd28b3d5b16d51718258234f3fa5c217ad794b309f2d5902f141440f6c0359eba9d29a840dd4d4b8281a50f37c9d66bc93d30ab9440c805c31f0b81463e8ff9d10380d46720a9fab9daca208e4c9263c3bbaa202f986899ff408f0471dd965e8378c60b3313ecac307e68d7a8bbd0660003cc265c7c6d26fe44f63a9cc981651d045", + "signature": "56392632b6446f4178f105012efd53cb352270cadab1df5bc0e0e2e470454872cdbd64a59593ff7fa939e9392ae2cc2ac1b212aa1103acf83095c7c8a9d1ae0d" + }, + { + "msg": "fbfe4c28dd3d6dc626b81ba454caeba957", + "secret": "df2d9d1a11e6df8b3fe7419235702f59e0f5b80a837883b2e3f1d368313e6072824c1c98dbefb3ee6881c1ccec9e3ec0146e401c75a3cb1cff0f0f394f3518467fda3c79de356ff66f7827ccd248062a59c7dab94b2c627e58a7c6dd00aa71e4751e03444e5faf9f27be24f69fcf2fec32439bf994e2e715acdd5943d44696f10196afb7dc0e70b7c28856559c55a7fc6a5dad5a962166392395905a6490f7737282ec3e14e7f72d0e96f83a8e6a9f7736d1c9fb8ae5edd93ec6b9dbf073256f", + "signature": "68f676676877568557f304a518e05585359008264dc0bf2e2aa3e1f5e2f12f307d9546849d8a29f26c9bf45657721e27d9df96b3d38fd7ca8dcd6b44198b2a0e" + }, + { + "msg": "355495c6f1fe5a79bcb9783a13bce1850b96", + "secret": "9a04bcb408e2ead9f04387fb0acb7573b1e545a24d7d22b15ad1183e7014f211ca1968f5880daad337e45182205014cce9f31de4a4e000e881d03fff5c9e4ea2d3865b07f886ae3a9907ee9929338c11c390bb4903e5e6df8b92d6d1dcecff6dae9dfdf8ed85b1f614e08c3f6c8678336021fcaf92e478eaffdb3ba53868e3d2c71ae8947f926b5e32d82de0acf4deda4b67e77f24b5782e7b61e9c1420df396b2389a62c104a87136ba674ffdfbc3411d9adf7b4e8013d433b7a0973106989f", + "signature": "cef6f4194b5556512c6fdc7f038f7348fe820f377c19e3a5889395e956f30a29dba5dfa9910832c83f0eef65c25ae257f6a226adbf8b2543c9d69bd34ec55007" + }, + { + "msg": "ba2b818a9f2adafc8eb094c044518f6fa09549", + "secret": "0edc30262b8665b373424cc11e1b33476d1854b747900743edf22042048530acf54b48966caf7847a691b6dd42e4314ea5e6de6ebbf247b97a3ea3937154cd42c23842dd1f8fa68018329e634cf4275d874bc78348cc301985151011f5210d13581ecf1892c7f5ef76fcb0eb1e1a4094dc62a9c0b42b389fa23ad4a1c7966b60c6efed844c954d9ac2ebe6d918ec4ec24418e9c1d64e5ac4553350dab3e3380839b186433c7eda36f8773e3c41cbb4379154870f727f47e6a4231f6017fb99de", + "signature": "8d496d9f01e0723296362a7128f523e7aceaba327e7568b50e36f696fb51e7ba465a5c3c93592edb99ded2bb1a7aef2bb8500eb423d0e71f218018056acf8008" + }, + { + "msg": "77ef81d099c4759cf87a12c2ab04f01ef76c386a", + "secret": "d10968bc5395a1a0be444a652163c9f4cdf6b98261572eb907dfc85a1cde26cf76b77eb143ba48542a16f688fd060bc3627fb14f3ce47ee6887eaac3a753f2af95f6d7388c55158c3dc13b394603604ba98c7b23e3f04067f226e8bd6786054df1e7755940f11eb254ee7395cd65fe40361e7702078a41fcd676acd2df7a6078dd99a6d3377b83416b8241a9055d2a40f9e6a6b77358f9b54b9a5102a1c12a57cf23e59787f97d64786a4fe00b7a97050ece0920b7c76f9fcbb3860e9e1d9e41", + "signature": "1c36555ee80f469c8f8011c34a1b01d5afb6d805b0243cfe966b692ea291880af0d7d2355af206dae81dc27d484af3fa7b2205a7d42b6b8a710ec8b94f871c0b" + }, + { + "msg": "868fbd177d9aeb1fb3e91a9bec9d8435fa19aa472b", + "secret": "855fcf0c08494020d4bfa988bb7ba5c6b04ac18831f02e2a78eab82c8b58f76ddef71b6caf6778741d56ef1b28786b8f13ce6de9b202576d5c9a93e9354d095730474169c50fc49d8e60283916cf5e2bfb1113081c10a0b036fda98c8684fe564b826aecce147ce08ad3e284a9d601997205468fb0b0d126211716c835644a05571364071187f175e4f9de0c24b4016cf1ea14ab6e180b09d8a9ddb773cf15ef0cd4217f9a774ee5a739c4cd9fa1fbf8ea0029a4a24dd4ac4b8ee07d0d14b79b", + "signature": "4401075c99c0cbef08418a722ff3904b884b6097f08c964ff48bc9a4a16f41342c2daa614976be3d2a113fe8b339a55d30fcd8a8c543956226f3d55b8977ff05" + }, + { + "msg": "c478614224585c4c6ffb63bee114b292f55165cbe067", + "secret": "f3bf74d75989a2583199392c8b7c2b309911220bbc4ea522499a916cee362f81ec4a3082a91e0839ec4aa7e42b9a77b90a906ec6e793a1a6a960ced3fd012aa4c260c32ce7c49da22710ac375e3b13bcadc6985e2c005030520afcefb592962f861c190bdc335332925383934d9acd10b1aa3070cbe486a263ae1ec7b492b4d118692e59f65333a93e823289b78a93af9839527af11c3a7f5b1996419fa698923e68a36e95bb4e34985734dff39e8bbc0f4dad8f3c55175ec9b93ce9746dc7ba", + "signature": "a75ac258e7ef4615b3dbde5464ee27acef9a3cd4f2cacd0f1755a700957adceedd08963f6d32ca276b8555e583d8119b7e0b62102acc67ec73ff08c2936d2403" + }, + { + "msg": "f18dfa7bdbed965156cea46d836779681119939d8e6d9a", + "secret": "0f7239bbd47d0a61a20c640187ebf1b5ac0f1584ffd86f497ac1ce5109c9ba8dd320885dccd4588aa699b068a8c7ab017e036c55d793dba2c725e07a74eb91529ccda6d961a36fdddf1480a2611256d34f26eb4aef50b3d52be6361abf8e615568d29ca6b9f331ae5f8bdf05a7cbab637429daa2145592bfe0b3d22561a77302b42a92ad9121002a9d5894481b1b29db2d844c9e5b00e8c4adf4dfd970b6622a37268746059393799aa76309c5090c88c655e8bb1ff349deadf2e11290e91c99", + "signature": "d287033614ee807b40a921ae1241d21c506361ab46b4cfa158c6b45bf85af7aa4b6021e9ca73b2bf549d6aa28898678979a0eb2c5d9964bdd49274e303828903" + }, + { + "msg": "1a1f2d1f0ea12d494f64d2121bf4b5fd904c881446826ccd", + "secret": "8e8942fbe6cdbfcb67e190672bfadddc327b33b7a8bce886b1bca70850bbadc5098b663543258908b01a7423b3765f23ebbc6790583c9500feb104f900cc84d35cc0f12e3c7598a1d3731883f742b8e3aed0ad15e8578b3c60acc663c34b3363c3ee915fb586d61f1af135fc0a9eb347d36542e31cd69b94c263231a7b4c495a333b7b5b38fcf8c45012437d275e3c90ae4b0a1d68cfdbe19b686bfb9f3b160a8ef0bd13dafbd39ce06ff4cd6392604c3890600f39aa080af31f6985807fca4a", + "signature": "f8361f79d140a9ce2807bcdcb1817e289107217d73561415cfbb605b912e638331e7231bd77c134f499cc7aee71f17e7340d930b1c09efa4bb642f8b250b8809" + }, + { + "msg": "991567b764ccdc9acb6c2de20ea285e805fad514d7c5b69e50", + "secret": "2d8f9a6969541c3706cfdd002a9e76e5a0645c34faa168cb01eae7b8caeac0efb3739081c2e10d7ed17551dc94c9bd5e502ddcc897c95f652da784b80764acb233e5dfebdf96ac8c4230272df4ef0e546e1416c0088c10ac883aaaf744b2d8175a5f3e6d3ed603aaa526d510029ab7e6a4e286accfba125d0a0c8995228bd006092985b78bd8070746eb9b7731f01b4619202e5503658beb99db3a1a2fca90748deac6c6a4947d1cc34b8aea5a34bfc23c6c0cce9da17201386be45133a6256a", + "signature": "cca43a039fd0d3a134f7e8bca4f344fe9fbfed929fa602407bd565c42eaa9ea50a308ee21b482bafefaf9784199f5187ab7ea5c0f1a0f62523a40042c53f3d00" + }, + { + "msg": "b6eec00c87f71a8005b24460ff8e07f6ef43045cb365ca654842", + "secret": "c003feff4d6586198a2818a547e6c6badc7a1f42b701051dd85f70bf39a880ef7c90345c28b358c56469aaa0d812e65429bf8424da5f791e08c9e5b4a038b616660321820474934f17a4e614a6de475cc5ccb0733fca7afabc45ab39357f09520dba7acd3cf6bdc6ca8f6b38271df765a0a0a29fc2d9b72e1a4bef2536455f75571e8118087180ee598cca9d0f7c697bb580181b13226104474ab073e3d3399c7675cbb55db14ab635415679eb7e32ee60647c8a9aa27a8ce139eec52ee15810", + "signature": "9d7704e3ab815270ad4cf6664d1643a04c4312d87445113d63a342649a9b368964240c1f11bb2018acc1451a44a3d5234ee142fd305af17a413a34ddaf822001" + }, + { + "msg": "c3be3dbc4e1ff501f5d08efa5608d0ef9bd034ac6a27b3e29affed", + "secret": "17d1a76bb0711e19cf90ef8b552f7c857cdfc8be54c38c57917d11a7ec7811c5c204c62b6948a2268fffc21671079aed59691d250b9796f33ba6bd883ec760d7c74957ceba98d9e5b5c370bff8966c45ad0a290b14e6be90a2b2499a0270501b5778e147a83ec279e0e0c8202f4928cc94046d99a5280d89bc2b5073b1d26f44604c9896c6f2179a9cf66502c7de868cee3d2b94fd6b53b32365fd57f6fe238ed3f7ef4153a3d6ebf42d5fdfa2dbd17a94ba8c3c96b9ee88853acf4f60a7614c", + "signature": "246bc94d67f130660c591a9c92aab3bbc3465d249dca256e27c38ef0b22c142fdbf838b550534d7228d22064ef163524731bd5290bc6e0dd3324e7942693ea07" + }, + { + "msg": "dd62aec4340f6779c1a02ad847a7f9f9e083bf66e9b06571f74130a7", + "secret": "d2445c9e947e13bcf56465628a3a848c134e06a0eec34f4a354596112f9400a4c386878660bb53b71bfa8173eb14fb863ea4477acf69efa036ac6bec3507a2ec070012da66b1d9d1a30d08a1977b971ca8d0e10c974ed3f45e75207e0cfeb4d8d64b2d13cfdb9a1bcb691c760dec71881c2c4a143c9991b9c15eff6887951ab32f27b9f1890335fd7d5a15edba0233bc24e8541591c90c32a7c752f2f7ce2f2281d984073096e222a3c7b6d7dbf0c39c21aba88b3bdd1493b18f1a814c4dc836", + "signature": "d9c4a50eb53653ef3868cc152613f0ccd18d6c8df2105acb92d26bdc6ad93df53f46bb484d8c601cd05e212d6d54fdd679176712f62477b3809e01d7d1888d0b" + }, + { + "msg": "de7baa6efa3c463e75c619a1fb90dbe620a1f05b51417f1435994cb103", + "secret": "9b908f9b898743de60f1ac698325d7752450e3b9064fef83eb61b4d1cb676c25c63afa471d9ac819cbdde6b5d99a0fed0f3f1e5f20ff847bc13e32f966685f1248488aadf9aa32ed75a57d1917e1b86641455d0f0698acf3323c533fbf87c36b95e174ab94c927ea11df635f8a9ad6792c21dbaa4c51103f19d0cdfce07262c7ac4aefc307b50e662e75dd14bed9a964bc585922ae6528f822b8294b752e3c96613bfa5c59294a183ebc492c6ad1a2a7cd711d9ccc34346aa7fb1d5e1dc5652a", + "signature": "66357c114dcb010dda61641fa35e4a330eb77e5a3edbf03c88a0ed5c77bf952651a3d1d3b4acc500b4eefccfee0fb5ba66cc2fa57795e90bacd1f6992ec1c805" + }, + { + "msg": "4c5d094a612b4fcb49a0f60c6468545af57743d0559fc8015cbacddfd663", + "secret": "e423dedf05abbde705f61e70cba7c19098938a6ea5c8b9cd6b9edbb7ff62484183e3a58c19d3841a6c3ea1c098bfc236d70403007b08393fe4cd605771c2e82874cbc919cc4023c0fe690bdee375a51cd71630671d7c64468138b4bc7349c1f9d98e4547d0ee518ea0e4a47f6493776ef7c72b3482beea61325cb2a0ba8be35512d7e08f70ab106a16f02f52340544aae0f5f4457e7c3ee0f9ec85b98cdfcc41fd2d1009e2f4615aba69f49f4b029207331568fa9e1e32faa9863897c44ffbab", + "signature": "e81104693a674b62a749e1c6a844ab67a18e6f3e44bb468fba312960083d07c9e720d34d42c33211b6108010cfc8979b960bc362023f703fadb07b321e4f3b02" + }, + { + "msg": "c0458fde9616e6f9fbb7762995c04804c9d88d1fcdc2d509e19cbdb6935c9e", + "secret": "732f157190fa07497b6d244a8a06c3d32812359c731e64bb7084b8e6e1c9f890b4c11a9f8ffbb4af292de3212a4ed430df552c54f6bfc392b77f622e8d158410e0d0da9c481ceeca42ecda458b60270271f61fa1f61f83c8f18b81171d20c533566fb5ad2d89a888d0e28cdd106552f85bf2e82ad527b33473874b186d4f0f17cd4d31d7460e78b63655620f4bd9b2079150c50e59dfefc753478720f6f987064ac20c735adc8b91f90819f203c10ac7ba17b075ad4b6be31a111dcbfa14096d", + "signature": "04af494efaccbb631ec0247063cee85d164243826e0ac9b367cdc00d7e7c187522e8873349a2550545ce0cc9db606ab1e1db4810283d62c842a85debc42bc601" + }, + { + "msg": "97f5efab9a3f1c26db8208cd53caab196411faba4cd972545f6f5963a88eb37e", + "secret": "ba48c5b8e83c2fd8cc9db8d97508c41d705e1fd0cb743ad201fece6930228cabeee06d435b098b0217971f12e09262bbc6c9f41239d628387b6a4fd1fde31446c2c10121358e76846a1f56e9494793e34846d274f9724afa4081eefa9844c670aac43081772d2504b3b08ead9a1e129f6ab27524218d8ec1d0eb93694aafccfde60a43a7c34f06b15f5da1f44bbf20d246f2eacfa8c61687288757c24635b8439eef35b409a58df0a500d871c75646ebb63c06d507a1f909ea9ea1d9c86650ae", + "signature": "fa4384d32c8e2e785abe874c8cfb7aa685586e1be02e175b34581170b4ced8e67b990f7279bf70b4fb12ea9d699013b726d5e0a9ac6152c927901ec94a359603" + }, + { + "msg": "8bf9aa5ccfcac8c5c75d9a1b8bee05e2ad561567e05d840872efb1e04e5d19484a", + "secret": "665c4b87611e8e325c78f6515a3a7e03c4ba0c9c5ef7fec6f2bfe60abc3bbd1aff1da367d9e05acc7e22b606a4ebe87d25af5e242ddacf1dec4ac4ab1165f3e042270255cdb925d39d6bdc695a8aa92a8f305b2ab99fedb8fcf9233dce6bc84f16a23699eb89f24ac378e743247113c4afcec5ad0b14c48fa4d23ef6e262b17b91d74109c1cb648c92b765ef45f5e494ab4380e8e1e872762c3660462f490f8f1329048988cac6ea3216034e7012fde7825c9d4175600641b0c262ebd3449f7b", + "signature": "eab978bf84856f158de470fc6d8207ada951ad2130757c20b0e5b5867d47a867b1ba3e8f5eaf5879b3a8ab93cbb8770de2d2399b55451779ffbeec1c8a07890f" + }, + { + "msg": "0cdbd7ec251c3d6011dba0eb769f2e43f5234c3c86476eb07e84a021cbccd1d13170", + "secret": "719cc0f5352d8797b1db01bf320c65042a2f48a7ba74502bc71086a81670c77f4574bf0bf8bfe91115744d542ea103d004900455464f2e67cb2f160f67c896b90b1d615ea9b69b0495b4590c26894cc4632896fa1db1fbe78faebc9728e446b85054d6e71efd6cc32c98abb08d9009a7fede52822c30a4d243e83935cb2448fd59a3e53ba140747a94bd6d3911998fde180fd899fab5ca4945aa1946f54750c4dfb89312c0b08c4acdddff0b77e306d5fcc3fe3183053ee0de0acacddd530aeb", + "signature": "94f0c4b85626a33fd1eab1ea81f60b3f77b47d80b3af9a12409bf289cff787cb32e8123c7b1eb615a00dcd85e9dd5be70f6fa2c2ecbbbb257d1fe2842aee850d" + }, + { + "msg": "e0384662f0619bcba4b7b2116d603bd4082286e859ff8d06c1fd8634fb6188970e8b0e", + "secret": "797ecb6380722135d822224052909d25fe1d66a06658b092fc1c00d6aa74b91bbd90897656285b9b06ffefd10dd5642ff23b9383b1470a28b772927255746cc078ac4c429f422485d67ffa9904a63a867eee8af898a81a12366b22a8b2a61b9ea3861cb98e24e235735fece3c517751a1137e873b0b56b7ac3ec88814dcbe1ca6f231869cc9fcea72526e4d1e63fe3fd70d8d812bb95bb74cc396f6d2f03eb90e1105affd39503e606107d8341e1ff2678ddbfa1d4287b57ba28469c412d738a", + "signature": "45d19d29d8eb9e5980d5eb43514aa0c497692463fa060d5891856b621d43b8e2c46cb8ad05bd0e70978b759c612e36a836aff94b0c9ea262bd2aa64ae4f92101" + }, + { + "msg": "17976f71f0e43017f6bb06c5d5fbf3317c7d6681b81ea455b2cfe4d600a60fa22e9aa037", + "secret": "c43f28e47d2916cbb36acc5de80be6a3827c070826718c99b266852593051a9ac21a3e769a86ce0d00e257dacbb31af970ea1e2d3c8144af823b94e0e3965ccad701c40a1b23d047a5acdeada6cd9fa9437fa770b7aaf1a4461cf780d9ef1914e71bdfdcc180cce5fec2cfe92483a6c905fb2ad1c7c929c1f6636e011b85e7ae7325ec32d50e3100d514792febb4a6558b98d761d6deabfada1272796e353f0e050d5b357fc214c538ac2db30c00ef1e86685a3b4bd992250987fc0f95b7365e", + "signature": "933da18435becfe9fcbb0467b6677380eb9278515176686b05bdcc50eb7522f913bd19f5f6176fc102383afc2736809d8e930575425ab9d7ca8de4c7961e6109" + }, + { + "msg": "039c7594dd090b98dc126ec6fe1aca8e0dee43594a77cb8ee6a7e618e5ea05a84a1c9aaaf6", + "secret": "e782dd57e4bf3cb4e0a117a7138b7efb9820fbaa87182404e673a084c2b0b4875356ee6bdcba2a6f247e5bc6f6f4385b9693052291eda65210bf6ac73bfa4933c6fabb3e29870947c8c902683a7c8e1c45efc3abbae466b3904ab9bad4878433db691575831515df573c0579fd2ba293b85348ff7506a042a87f55f34914f4abf862ab6de32df23a426a3ab03ab96141cac06562b0b025c9c7030bfded26104c99056c3249e6a40cd85d0787f226215a020f4743edadb5e6642b6ba8da68976f", + "signature": "3435c309a326c4025c85f64824d36a5c6f6d0475f91c773f90f9ea9f820bb5039eb223159f37f928380fd5acbd5350eda00142e06a3c15106ca46f1d7f06d501" + }, + { + "msg": "6942bd46b6b19086a3a1dde3a95f45519f54147f92676bcf9cc4390e7b0dbfa732809af990c0", + "secret": "a798ddc0b7dd65902a64fd6ee1c6106fbc2813246a8d0a88ec5fa0edb117457ce3aa5cbed8ba26af5f9aacc4a0a09323a5f1fdef9812d382c4cc46401c55d5642dcb23700bd687ff52a1c0d5e6e17d5cdbec742fa2dd0ff16ddc2812b27af0c976b7dbe91bf9c9e3183669912300b7cddbb0d72cde2de9bc1d35e46f67e4783fc35fc683720286bc850648d766dfaa2f8c046279772781b9a875ceb85747d75389ce078c1b738d463b221fb3e3c337f332e327c292d745c8193c3a5bb9e5aa38", + "signature": "274094d3662eeffb57937974ba171f5fc1149ee404d4a894654755e9f76b8e8f7802754cce6d92756d970eb9fd11dde19515a763adfd6ad5a8f0ab88b5a15300" + }, + { + "msg": "702da967b4783e2616e962e5cdddea716003f6f26d8a8d51f7eae513a0fc489c8714637e7b2843", + "secret": "2d215cbef93b45050abe47a04e41820ea882e15478c80a61700dfe6629e000ed4b5d5f8c30309ef836d50d3153f1abc5acb70692dd95141532e0d9cb76979b761b3d5675471782b7eb5a4ca6ecf2a78333dd7d2525755c7c67bdd84e71a035fd59c884d6c9671930bbf6b7f49758701c1647e167e614dfb78cc1eb5b4e5cae1013e60aa37a9382e6d3028d3566c123cf94707dd28097632f4d381d6d92c9181a6bed954d413b671b4e98f0e962186e312a9f2af63194f6db679c2f6946bc0752", + "signature": "e18f78a9133e29091d77e7734d5c72484686d3b881aec092abfe9ed9a515c975e8eb7bf6361a61b846eedc7d1909000fc0c7b30f035d95d807965794ae0f4f05" + }, + { + "msg": "1df0b454e43327764553d41fdb38c49dd0b6aba7a3a34eca48ac9b51d27723acdfbd2e53a11fc0cb", + "secret": "f29c5e214301a80456e0fdc1ce1057d1c64559140071405c9962efea0c41b4df9ed1165f29b5efd046a24c9cc9f1847858e56ae5713af4427a3dcee9e4e1bd15daca2a02d50fbf13c7b85f6da6f6516446c1e1588a45e5bbbda1df5e5491b9c278a9c11e16dfa316fad872dd85e253253477531e04fae81eb7cb32777ed41bb7f691cdc8f739424e7a562d1a65a159d69dbdc9b5de56830690ee9cd4c74e36421f66f66077b7e991beef93a04a0d4dea009253cbcc7f131ac2b3bf95a112fb87", + "signature": "56f102fc039d0ba0874d0a0dc5fa2e59db387a1f7bb08bd185b2a4f02fe184bf9f8f056cea8fa01bb823ae3870bbead77e0b985d9dfcdd6d3346f66730d7d900" + }, + { + "msg": "6730edea852eb70f034b21ee848e286a5171895b4389a6322221098b42b8dd9725e10b5fb5caa5539b", + "secret": "67e981670154aa18d3399ca9aff90d7fb43fd188e3afb111416f2104e92476afd764e354fa634c3005bedacaaa4e4dabed52b691ad4530613740f5635366e9610b6f5ada70de3601ab9d026323fd00548b080c8c79ea5df26621bfa52634c4e618d514ea6d17ee9e1e94c822a0281134d1f840bc43599f9e6201b2ba2adfede0a9f8084d35b9c640219b35ed253f9b126f001878a942e5bf9ad90f46df9517f7ecaadb10f445c8135f2ff2086ce338c1b3a047b91918833cb430c133b0c73608", + "signature": "a90a69c46d05a10f3c5f6e4c5acac8e905f435b235224a75be3144e166d5bb814e4b70c8ddad099ff6a94cae2280df8f9e79f4d441798eb111d2793887a63d0b" + }, + { + "msg": "c9ab90f5898d54c9d404c116129ea40bf2ef25f4b04ef579d8e682de5c6689ecc50a15069618a5d62d42", + "secret": "9c5be1acdb17e577c995c03e9d5003f5c36fd00d55f379484a28bd7eb1d5d2f76caf507f443b1fbfc1cce6c75d6eca2241a835352e1986f1571b07b9b5c32188fb70571745da40aaccb40c73e28f48ecad299afa81662468f066c21bc6d29e98b66ca73aa5583e0027d588ccdae4045aa821cbc8ebb2d91237c50b46f6401210eb6cfad5f9af177fe05b84a12dd97d9f27dc12213bb025726e5a186956b4d8da767fa378aa4c94dc9e1c722e3abc056af3e5c563d8fe551731ad9e4507e7cc53", + "signature": "a7209f24303da3a436a1cec37742743f4f6a7ec8dc165410452e81dd92ddf3379ba10fd1231f9e30a7752daa285a475e8553614379cee67caa798f1f78a98707" + }, + { + "msg": "5bbcde7382836a1ec2800d6ff099d819073c7de4b1b335047f767a8314e85e613356c8181d9b7070108782", + "secret": "fcd5f264c3ef6ce0daabf14d246a7f2ea9c0cd42557054455ee16e5a2828deb5db0058319d014522d80b62b8c8905947086758c36bf43676d900d1955445002cbc0766071c438963a16b06c185c6b3e15a20c734d131dd794411b92d6a1af13ca20b8c26c0b789c54bfd8dc787ad0ce8d1b8ca95549f52317165d944e665b6c81e0cb062bf848cfb67ac2e7339309ef655de49227ff9c21db3563be10fde1f510e8dfa92c37b7f3ae1630b8d6cb8e45e1a045192afbb6cb4098b0c1aaf67d4ab", + "signature": "00a076b7f9922bee936b34a5401b867869c8857a16154afd30af2d46c49232285716ddf1d6ff55badb6d2373228caea1f6cbec05c5f85a8a6393c51db2557801" + }, + { + "msg": "55c95b1c2341d92648a0292cc8ca91272897d880d049e3910021cfdb3fa93ab9edca4a33e90cd8792aa099f9", + "secret": "164b1ef0092a6cb908444ca14615a9f6d927f05e384e0f2300dba1ee710ea054f9ba6937e0b1ec9f4df1bc443b2c1d0d2ab4f4e101ec3c9852b47ba275be7abd93b75a9bb03fcafc39cd5fdc5e57d354496616d467ca58f4537d48005601198d56181236fc326e5b819b167f5083e45977ed3b5063b3bf5997b4533182f84488e1c80130dd74736c1f7ab39ebb96723bfca8892d4d0a81acba5c0a5663634517c91a1e70e1b9a62c4fcd41bcff71c140a37a6541b5e093f96605eeed64adcb18", + "signature": "a9cdf4e2c3cb174f11f4be60a26763c5136771c05aff665b4bdeda10b134b2c92d554a9ce930e8721e6fc313944023695203583221b01718073aa5fab2f4de0f" + }, + { + "msg": "149bcd7323c0af5994d9f7f7e688d2a12551ebfdf4f747ad00d9a7a3a8de3a9072d3bf2cef800289e882c065ed", + "secret": "6e74b30c68e04365d044af24d65f27f1119aac5eb6218d37109a61388d1f0d5bda118261a1f08b194455454f92040887b49e1257a0495c9ddd892e246389d45fa0d970ea2900ea229650dbb452bf952a09bbf0bf0379a4561126045115b5c8495cd074599ec22b13fe84af63fe478c415611be4ec08c893df96d25a489cc8786c90ebc1c1b375936151b74cadd130df2b250dbf319a046e8da2da361a1a0ea99a23aa72b9603bdc6ab72c2707a1f79414a4fc8647d60bc33a3cd5109684f25b7", + "signature": "7c9a9fab3e76ec10aa4526e603d6370bc4e7fc3ea6e5b089d5f5f899f0bb8bd10f5aa29d4b76a7e987de5070c946a231d55d3de30bda32c48c4444879b09a204" + }, + { + "msg": "07b841a0fe2b2723a0d1584bd74c3ddf2d9cdf0290c2b4973e8bcd31c49ed1f016ce2059d48dc425040cd53e2f0f", + "secret": "71b6e612883220962fa675e3302669f422e523d24b3aa9f84325b0a5888f0c3b8b785206865dde39d541fd899afb3453172f75e67893a6447f5ad03849d385d6ef216457556755ae299dae2b05e021b5bd3b0c86b75b192e66adeab8ffd2a1f48d55b504ad86c7af9c486ff1d9a1ad18f3e52a6dc0a23e494325c6bfdfa2a02f359ae87faa2bc67bdffa590b487e283ecca47e4ef763f1e5e358e268fdc809abc21b3c25fa865248e8724a452913328fc96a77d4727eaaca0d8c259e676d9ddd", + "signature": "ddfdd27c206b8a518813dde99dc9804ab9c37875a83a7f7fe426a12f06cdd4f1de49581eac0ab24cedd551b11928366c9996e1d1962fe880946a79c2db5d9d05" + }, + { + "msg": "10f9c3223dab949dc0524ac80b1fbc30b5cfdd79a501199d51a3401af2685cc21fe75895f0e82dce5dd5cdf3a136fd", + "secret": "aed3ce91f957577ac7f38bce9fa83e624593e8222e43be96167c9404c6753a192643433c94ae29c33f204c74e74261a766ae54369160f20920ffa9d5839d285f0665f278c9718415c9eea3ad73fc767907d0e444eec2acb2afda3dbffc7eb42f845ac5cf6a0d27dd6ccd4ddab6f4c751ab799b7c9cfa5b382d880c66c9ba6dcbd67e1e555b143a5601dc4ffafe5eb9b6c4905a0109643ad27819de166b68b67d1c6810b0a6662bbc79390d3c896a0d7e004a78160a27b73446f06a240a5665f3", + "signature": "bd0312703cfb7980ce4877adc25f6503130db317ecf77fc2dafd27a115acf90b0e825d8613eea5cf8c8e6c766101ab7ab04abf7b300ba66810d630dceb42c608" + }, + { + "msg": "35718573e877ca1e155e82a06ee0cd4392ea8c6ac7a1c1d2edf5bd2340c2d7704d475d21af86b02660371b95b590d05e", + "secret": "b94af5f87f946957046feef4b21b9ac798877af7b815c87c9d9efef55671204c3bd65ae6e608f74cbbb1067c99dd5c2fd319b9040c9f85e70d2c7aec7ffb7cf5f97bcc873dca0f75f414b5e4cf79d60abf61cd3e21b68e5c75444d6b6fe5653dd195149e4a5e23f427f1e30c91b369f1cda937e3c14217e7492159577648f726636c4fdccaa1d00b3d2a2f45bf3bc50b97576406c456e4de7168d577f8098962e1fe889ca7356fedd671e61bd4f88fb2f4ca9489476b5fd621be9cac219b3a5c", + "signature": "286fcb5add84493a16e770739f546eef81d45736d12d94a5aeba13e402ff7e7c63176f768b4279d740d621eecfda8464b58a877e939b1838b2d8e8a8bf4cae0a" + }, + { + "msg": "ba4d9062d9f378d573ffd34ea72962bc6b9e85addbf6c5a5fcf12e8c0567bb20a07c64d7b8f6f0b09bb1bdfb1e11ceaf8a", + "secret": "95d5c46763f8f22fb6d168dd629c726ee3259ea19da14acb96b9755587b902d35e6df94c1e0777f5a3b730ba3f37cf49cfc7493cb2a10bbb368fccc3ffe4c072ceea80673b844f6c9ae8558d6056411e29bf6df7d63330cba79d9f17e143bc68c427be10588b965ea478a61d81d70e52aae0f7b63d87a2eb94e11eff07195aa57b00586c44b9927a4aaecff6fde6630c55995de13d4d075ae9603bbaca615bcafc070ca0c30d45a7971af5c9f1847f8eba688425b2e19ae4b87e298de1026500", + "signature": "4998e0dc515311ca2a20b04880bf2b77c2284d5a08636b449a0073d50fb4b5ab74226f207dfc7181b4b23b2278decd08e75e081346e4c23deab8aa978c5bd70c" + }, + { + "msg": "", + "secret": "3ba0ea5db2ed03ae9634c61f014b842812e7e96f4c256ba05ddb83f5e83f0ed22ded2f38a935e6db3891c5ad98639afe0505a3b4ba525893e72d6ded7445ab0f50bb103fce9a4a533dd23287cb738fca81008d0995fd916f809589f76e9608c26e7f29bffdec661df3ae4815da4d09015638f57d1cbe36ad43cc84f54993a34e517362082e52ee08bb79be5ff113d24b341eb364caf9ba538adf8091fc3aa4e269819a516d26315a7a6bada532f6957a9b2d28aff2eeeccd578dffe63b4772af", + "signature": "2e38872da2b2e85eec400c9445e5ce4714657791a9dd032de609e6167beff1d16d22c93196469ab8fb31d00df3ae8eccec5d3443d9257b112976effc80c6e002" + }, + { + "msg": "b2", + "secret": "a195420fecfabb5ad5a2223d588b3fcfb3c6fc5a1ffdb2073fb5b7f6c948a33ac3bc2981e3d1cb41bd63213298bda719353fa597fd611840b8390b792411416e14edca67a9f2a464667dfa45e9f075eff48204ae81a322c958b44dc86e68a979f389f46cee3b0277b45094a4ee9ce512a65aa06a0134f25a4b64af2e14e678746cef118a327766829c92185014745d1212604ad3220dc46fe2a3d25509eea96ed4ddf3ecf247e19fa04f798c5034ea762da57befc527119a964289d610b4d64c", + "signature": "d1e663325a65080b16e3dd99a25b8b2b1168ded8240a6bcc68da9b60770df6afff8b2e9e0eddeaa6f44005e1677b4d0fff57eeda28baf4b5762877600d0f2606" + }, + { + "msg": "07d3", + "secret": "c5ebd2a7623954696aee29be91f0ec4043d5b63fb4dfd78c283b4c454efff1f7b360c914fc5850761f5670c43684214f419bd9ba38d8ea68a001a8cd84f7a39af1e88b40a58c8d2105c66dfd767fd2d9c0a7ee1b713170311e15239abeb72a73fb4af82f50bffc53e952a4551049c6aa1b981d0a41ee318bf81b458c3fe39d7c458fc086fab52e66c18615597c2273a5786bf0b1231838e66c5be19906c7a9c71568ba415a3c64adcc07a13d61d50ced31bc5438aa3f574ec7a5c1b762bd9316", + "signature": "2fa1b6d7133354ed5891586395824327ef9eaa5b0378afa312f06c89aef3b04914ec9879a1b0247df5dcd1cea9265ef2b86ddaf4899ea0f7213780aa7a9da102" + }, + { + "msg": "02d111", + "secret": "88dcbfc5d67e414d5bd7509c5bbef65ec0b82b9d20007dd4973e146711c9deec3708b0a6bbeafbc8db485b91ac8a6a46493ac9c031083905de7cfce6d5295df3d3f5d3a7782d159fcaea9ca17959b8af1b086f9c163ac618a0dea5b369cf387254ea8da56769cc7a7831119c7c8b2585d21fcf131b7cb158324f75a5af3280ff37f4e15491753f6df12857bbed383b0555a1f7b44066a06451cc4e312c8eb04bee131c0ca6bc82e88b0415f54bc17adfb006d9941ac44997a3b3ffe3d7da6eb2", + "signature": "a0f4964745e93bfc307d3754bb7a4f41d865a82f87898203d23af0d3441eee550560f478c455d1d0ea4a21ddf09f9468c0795d81440bc16f457b91ffbe8f4807" + }, + { + "msg": "9917eb08", + "secret": "27692eb4a3cdc9b03fc64d6ef0bb55a5c98fda73e1bd83c7155c10c246564352e5dea27f3f15fc75521cafb8c134d211a995abeebca035f9d9c2a1dd974e068a7bfdcee3095b90139a6a7bf3aec3b9c1e73083a4bd36c3cbedfc4f975dc4592d7a71d232acd21a2b0047c5dab4171a7bced3ca6af0e50e0090037ebcaf79272eed1d1fb2e2d5f2ba7f72cbdb7365196a01a604b61ac2d066f903e7d9f75ee14264477ba95be025f155b040632fbe92a5f30d3f1eb7069de0aa497db5b10ba2f9", + "signature": "89c0a87371f4b766a81652019687845e8067ef04daeb60a41e783e34d4ad6d6a57589dd0686e508001b9b721229ce2dc6ebf2b20358956d323f10574d22ba103" + }, + { + "msg": "a93ae44df9", + "secret": "07492586f31d55776b660c5970575aea38d5702da28d66115b55629f290dfaf9f62a29a0d8ba8a1985c1268eed0d60e6c7a5d71fdc0a193bb8dfc8e2c405809fc2ccc5dbaf25e19a6bc2a6dd610ef4fcb8115e218b5b877f7bad472cbe764c0f5c4caf61db3662c69cd38daee329bde899fb3b789c7461cb252ab5dfc79daeac6bdd2ccf00e1c169368b1fe460737d2d2fe5b54ea89abb1dfe6bd4f042bccb0a08a360c3984e529207b37319f7330fee3e9c682f66c9738dd563515a7d7474f4", + "signature": "2a5fc751f5db366baec833bf218d7cbd4e66fb1310017ed9b1b7dd7d7ffefda62440e43178eab965565c3a5218197ad8fdd686420a3653de6959297f04b5fd04" + }, + { + "msg": "55665bd054b8", + "secret": "fd3132f16b52d30859be067ba567a12dfda779391b99d608879faaf877c9f08d8b02d3df44a0732b33e4767b667b4274955d917e1c958e4ff913015ae248a61e9be8fbd50a12d7b22cf5735466520f8609cb87bd506d91ad838ae11ea05de32d10a783feba4c0a45c56b39dd816ec18820fb22ff212d26d6219b45693c3357bfc485e549c2bdecef8b756c6fd96bd0177f09443a867d006bf808aed80a80df4d815de889035a3cddf544601c641d2ee3ce845f45e17292e8c6d18d3feb34679b", + "signature": "2afaa08c15f4ce36062651d7b8a2ef7dfa1d33f197632dc83f9b3d75aef82d26ed3652684b6009ba9eb545e6b93df5f1f645479d194b924b376b7c1697ea340d" + }, + { + "msg": "bf90d69cbee512", + "secret": "17771729e55f28ec2f0e25ff2225d04dedd03a72d8847ee4a1198bffc51a400dda3b5d6a7708e7d15b4429feb9d80d5360d88e0c8fda2ec2370fc696110ef394ae3c2cd76c871ed4c5e4baaf03fed268430347ade00eff9ab6c98a5de1ec7596f1b27a58ddfa40ea1be1952174ad42ffd3c5807538665b7a711b2da78dae7ff69baeabcd680a8bdbfcbf4d8529d11a8dd49e0b1f1eb151f64dc4e48e56d285494468a852efe353903234b64eefdc87c3be3efff0b2fa22fc676b7638c39de46f", + "signature": "91dcc09a1f5587308acf11c2d33956f34643eb4b5102bbd9ce59772cb13195771c06076650ff5c1ca6170f3008c7c2aad270a87e03c7a96c5e359664a509fd0e" + }, + { + "msg": "a40e4085e038665b", + "secret": "826f9635ddff35bf34703aab952e592017bfe202fc05fadc61e983bed92edeee281c218451cfa5ac583ee244c26861cb3aa8b47d5e9de5799ee301856acc5b20507f72313b7764973fb62c91727154afbb054fd4eb8ac6416363d962e589de345aa1533808fa4e959b4668523beac083e785b3cdc62050c67f9cf694d0643f6b07e488d582afc333d3e41f39c8bcae3ff70b00d33b9d3241760792ebdddbf5610849f7de700e8a617b2ec2a400a6470561fe41b935d73f04dd0a4e82c6954d4f", + "signature": "343abb2275c8264c3913f85665c4e1fa0450b60940159e62c61cea0f803c8fac0ffe08b1e5a829a21d620229cb34747f9b929ccbb8071b1828c523edb44def04" + }, + { + "msg": "1113c602387e2333f2", + "secret": "7d06abb11feb792c1d27920b46df9159fa51b5642ef41fe2e74b653b9acfce66e1a84ccf09b7a9e3f2488366de8472c92df0bec21200fbe39aa4ac1debdab131452ceced1ed7f216dfe8eb19fbb47d7380c71fe85cad485c608679b4adb0f5ac1407f1def67cf149297979ddba6acc3a6cd40c11bf6e681e281a07ee0bc3b1b0627d72681deec3496fee7a3e70007ef03227556d4aae0bd228ca8e72cfb23e4c167b879d0121da43a3cb7b11dbeb1f21dcd46ef8cdaa42788226cfcec8bc0422", + "signature": "4061bb04c99c2a5d22f077f492b7395fa6222509d597cc957310c81fe214f0a6c1fb37bf3ad14da0839b3fbab399b23854598bc4619642edeaf333fa9e639c02" + }, + { + "msg": "04e97bc384df9ba92581", + "secret": "fbdfe4d74ba65537a7e875b8af768ee17009fe1d962fc82360e5304ed8e117d1e4d95db7cdb4beab99278e9c8d31b4a3edf208f824fc30c92d970f3a5e2ed9debef8ea7c8b57ef5d1c58f89d70fb29473cbafd3dab3a9820991a66a642affb942be42192117ef779729a775aaf99bb64dc5d60a6b5c4dcd34ddbe2413fc9b350e3d3417f4053e1690847c12e244245f8ca0573d209e1209dee50e420f3dc8888e4696e3f4645f62dc5b259b656b51908938ae9bf3373b7e2d7cd203a63e87a54", + "signature": "5995054ff52b238c6e10523f5f5d258758e3a1423e6599cd99f64c6a7e93cca89a38b13ac1a9db40c670915aa629aa97e690b59231cbbd9c45b8bb6969cdb90b" + }, + { + "msg": "24c55e2e9956ceb3413553", + "secret": "ddfd0f60b652452c0426d7214f84cc68dc17ab385664f968a464728facf6d49116801942002ffdcf7510b3da05ff99a3f2095e45a585e8e78585e2c5ffcdee9d35367af9bae9da16222acc625f18dfe8bc5ba880d6958b6544c2fe47fe028e3a7941d55f49e4a31338d2f1915d66f5f3758e56a777cb29f399dafa2a757da7ec52d2f3fd3952398ffd22d8f0974aa5e04f6484bc5701b668d8a9f14e654b716a0d6329a9e72687f812544f4b0096e22a2df9c17dab9e981642cb1ebe84a35844", + "signature": "7ff8ae0b047e5495f3c0e30de704f80561ffdeaeed4046f231d3a40b93de00edbb11fd00a482e514369e9e76be26bb0a69dbc273b8bde3c786703cc543c39102" + }, + { + "msg": "ef5c818d975ee4162d46442d", + "secret": "2a4dd4335294f9d3b23b3f489d46db4a6b4ecaaa025c4416ed1ff99a7ec5946c2f45ad8563979103a9855e43a5a7324028e6c1202991a3f9b5cc655d28ec4940732b045fdb7b96b1c25fc2ee25dc943003819fea0d16800c4afb90354400a44c5bd294ddca43c656c7966771bf9bd52c2c878640835d6a865b9276c320875c05692c7a20ebeef369c716a268725d926662cd3c4d6acd504cb4467476d1534f8e1147280d0b8abb06a16b73282c783d2367f6276deb3a9c4d702e50fb756e09d4", + "signature": "35618bd9283522e36720c11730dc0a4b903bd3b0867b0cf086716edab7ff5df3c0ea824870b2ef5c5cea3ed2a2f577e86e6884c7ca29241c3edaca4ddfc8330c" + }, + { + "msg": "8df88409ca197f5bf4f292cbee", + "secret": "91f3c1ab651c961d1e176679a370d629900098efc731cbe0c81b925cd3983bd1c43f7858a88544cda15c27628cf1a47c49f3775bf73a01391f99c46a4366b14060b968d091d0bc5597772c38533ec4372b0ad55ddbd4434e554277a4659a8ca99ed9ebd7e9a64f92ba50d83a77b69eacdc304697052349e1ebc0bb6d03adc0e18a1771e0a6007a6761b48c6050ab880304b6d79ea80f50b4a4b9ffcf94292a73d2f363a8d38d8ca57fb35f6b7601d9252618a52129fa7edff4d35bd152da9fd8", + "signature": "a93c30fed4b28117993f4531e9b316f75adc2cd592dc92a794fd1727b1c86330f9fbc6711e90d14bd63cf01957675934b6986725967ffb6e542320ce99f2b900" + }, + { + "msg": "7218c3e5c16a8478eabe83e29b58", + "secret": "ecbf3547d2b98a558b7eb2c417b174b4a75fbc17ded2b1f28457b3a8b15d413f31c996352a8b7461d59a27c76b914da045a0bd0efb2be44baefa331a38198a38c11d9736c2b59854ee3d41074d2aa810385ddff1eef3aa9a3856c9b3558f8c46d70061ba08c0ca3adbda33d9067d24d241e7b20c6939d1cc9c1be66620865268712c325610ce1a3b30655fced7f438df57e67200959cf9c6b602d68ffeacedc480e2fd845d31a770ac3432bef7bdc1d780830fa9992fbae98e101a2963db6d75", + "signature": "4ee9c581b8784a945eb7bd93744f86867344cdf135d2991c98f5764231747b6dfcb8b773dc9c71154981b78fa721ebfb9aea03cf84dfc680c155c9e118805d09" + }, + { + "msg": "f4a625a1a31e3f61df2c15d7d1cf9c", + "secret": "c74d2838927971cbcdaed3a935aa4d72ba5d5551e04895c93313642f0d9f56a500ef72424a253d864466c0c3984032e646d1ce9ed1e1eda87fc1301246c151483bd2ee4925f4ee9a5159ba8324ddc1cb6cd782c79b59360f729fc90e3b5b485fece3b592317c11f95a9f4e6feb0b0a46716c22ad1c6d0058fca8abc6ee360983adf5972da7387f964a8746fb65f9c27d46d49db153839fc419c1317df54c1e5c19aa856da962f3c0b11d65246b1c3bacf1c60f30c1e86b2a716f87bdb3d7fa75", + "signature": "3b596cd6757f0242ebcb7aa14fd229c791ab653e0d25afd810055eb5f5a7c9268df4a27350f5c1406dbc8fa331d31bb471ee8aa54254825efe3aa271f8d3c208" + }, + { + "msg": "548e5b96ff60025a3d5186f7075c3e2b", + "secret": "4e6bfdcb3be7119860d5b93eca0edd2cc89ecd64d5a7f558c228d217f0f8724bd13ae7c4e61018bd0e88c31d7fa9447646e673a64afc6dd31df4fac587b0c987de8e89bb26c9acf72f528d53f5145aa42b0c1ec2f18afed9950e3ef397babb912455fab3fc93f9584e5fcc10639e78c9269161a5db3356c221a8541bf9bba8dc10df9ce0e28d6c73c682f70cc2bd5fe6f22ca0057ed95f76695585bea94a18de16539d427d14aef6fa8f8902f90eb799bd7bcca7a6eb83c1e26469b75c5990c0", + "signature": "55ff55d13b3a9e966233c9a8b48116e9e2472e5310c29c8f0c33145d0cfd635783848031176270d155016c21c1e22ed79600e1d5aab52b3fc68249f4b39fcf0e" + }, + { + "msg": "24282ea90f0552e31142a7a83124364dd0", + "secret": "be10978a583b4a2c5f0c3f6428ba938c07ece0eca8e3d728634b81f062b9b3fa22d9c0e65e719ea07428a92e79901fe28e9e6617b73ec8b23d85c605dcdf02b41ef0cd0bce01cacd35281e75db139ce83124de2ca13e015441dc5aef28b1b55db77ab8b24b9d2e162d61fcb0ad9363615d6c8bb053b90f3d97d08ee6063c5a91825aebc0fd2e46471a05c620e4147082dc6bdedaa6e4e9c435753bfb94bc481783f3f91c59b14119f2e034ae3583d628be69b608207b053762eaa5ee42a652af", + "signature": "a3e3125eed5a7e895b153826d7950170dd4a0f4ae45464910bc97dcf73602eaa92d0f231cecf1952072c8407ef192279b76df9ce8a4fa36cbbdf1eea86c8670a" + }, + { + "msg": "86fabb9b4a134db1c204231c70ea38094815", + "secret": "0aed5a4799bda40892b4d471d4b0c087c7fc5d7a560f9dd3df750fcfa7823a93e778414d133d3ff0ffa434898ce5bc18e449e3b1fb9a27b44c6804965eb4d36a753fe1bfc7048d6abaa5d05e2e811020cfb05bf301f95b0ee265201bb77b2787bf117b2771b1d1faad04cf9f43ffce32a59f2ba1885411d7e80c8e255a4fae0764f1b9ae911286d2e8fbdb9571b06063847c6caaa83db9656555a079a9ebc8b95daeb2b94ef1d91c7c965546b7a1687e5a9e2932f4630b7c27490c04ffddbea7", + "signature": "2725ce0b82d1bedb3aeefff5aaa764f0b31b98895a132ef8e7d53f5062eea2c07dc1d12e1c13abbbcae50590dfef99bb9e687f142547a0360c71d1ad30083705" + }, + { + "msg": "acd604e7f62a177a3d18a9534a85804ed8123e", + "secret": "737f29e9e0377c2b314b2f26c697d43b3969dd05cada1ccd44d8cf1e8e674472c28eebdec075efc5a119a5a44aed4be06dabd91bc172a3099cfcd461ed957005cf75ae9a5b342de6c16ec4a08cd7846558bc738c2e9a58a27cac144d7fbb1072d98cd616446757c367f868ecc354b3ad8706ba55522505eefe5ca2f6bc0a5ec3bbf63abe2bb3b0ca510a7e8b4867dc4df6bd20cc4bf505ba975d481a6c4fc8bf69daab5bfc3cc06367e09baa4bada40a7f9ef7cc79f1ddbfd7d2eea0b0ad3852", + "signature": "f57f645f672a17878c5e828aa067c74a818640e4b6e431e68de5dc6127d72947e5c02fda851f4a8ee364d53e1394ce570cf7ddbce1eb160e8f9e6d97bf6bd307" + }, + { + "msg": "689d82c0a2e1db2be2e7011614db37293fe7898e", + "secret": "b87fda13a159a7d8a9475d87bc58065303ebc2e83287c47170cdb7e2f7fbdc88f18fe8782d32c2094abb3a37d662ddf33b4d9ee4f658d36cbb17c964283e373c37438314ec888dfe0e443415fbfa011f657c3f74896450d654b7996a5ce43213b3972911df0e36e8d2da5a6634cd2456ba93792d68517cb21b2f78a4f04020d52c353552891c3fd2f9f7a13d0af18ce578d22fb0655629accc1040f2d67ffb289bdc55125ba4d1e77224e9a6da2dfd55f80826526a7ea93f908db7ece4d15ac5", + "signature": "6f4e279836c911d295c1874a1fd6a49bd5bb68639ec0a94feffb532cf557429e4942f572b30aaa2146f3c66d1d0ccab820fc4ea48bb4c5ce7a7a744c0398ea05" + }, + { + "msg": "9f066adb48675af8eb8d67bced6955633cf74c0f73", + "secret": "731dff33128bd0835dfdf8dc20ebb947af98843ccfe98472e6f5a02bd319c95a48d9d8032c3176301acab12acc237deb510da175a9e11cd3955786a73dfb9c50832d1afd4c6faa9683e769708009e73c2a2e91aab5ba9f7e5c08f9bb641769ef86b3c3fb88be766be32f60d2dc780a1f7dfe0821ec8671622a6117e0b730e6be00083162aac177f5c9528373296058b588b7da4e8a07e6b62a0011263ccbf787718366b8eb5dd3dee01635c5eb7c085d4a031035f527435c4887c3ed28af2118", + "signature": "a040955625362599498043cf6fead1ed39e2821953c04fad46cbf2cc6b05999014e6e9bb440b7edb09fb1e0be60211c7942d14a05d6152c5429c156400bb5c0f" + }, + { + "msg": "e3383a02c47e8e72890ffcb24cd211018f69f9ab8c49", + "secret": "66cf25a5782b57aaaa99b20429c1145d9de562ed37fbbae5e4fcfcb1725d2db23514eaca76e737964c21537bfdb33754aa9910f605de9183482720d6030a265e62c84ab62d7a294803dbab41d15e3bb10446be65617540b490258facc4d1e74c9c23525d55f272774029af2ff12a0b6d8b8ec0c803cb849b40418a8ac00a51620e3c0b157d0e5330123f51536348576c993023ba6798377325110caa87c004aa74b32a1c28226be9b850cb5cfe26241c4c1450b04446f9c2e2b161fa1ffbdc2f", + "signature": "4642b353931d31cc8e3192b6f4815741e418e76af32b2d6f8707742e359e3780ac5f1e501daf9cba82158f35c810b11242ccb747700d1533683464d847815d0f" + }, + { + "msg": "bd3828fcf6081ae1e063cb58c7771df439a75e4bb8952b", + "secret": "d5a62933530213a0ccb01dcf3d508bf048e2998cb8fa071afbb935e516e09d7c5e12830d5c0c2c300d887eae7cb422fd97ecac4bd8e6dbca68f75c7ac4f1696768bda06fc9358954235b47139fa62f3fb5c75dc332d54484a634dcc40ec311250c85b8f298391aa3dbfe295ff01fb378dff9ee3433370553781f01678ebe89360b4c7136321abe5814f918fe73a6623389418398f9d725bcfb22794934247edb9519aba807411e889c50838323de4e6aaa875db375b70576a1b906680eed5b4f", + "signature": "0e2bdee200b0016d1ab4d81f83d3fa62e3eb51399d2f7a134cf9def503b7c10716ca994d8b36c9acc0721d08c04518b2497a3f5440c1af914f44eac8e44ee10a" + }, + { + "msg": "511d5e7857c70f1dfdb8a194bea846071e15031f702e47df", + "secret": "f53708340d2ac758b59cb7d6538fd9c860043ff3443f8cb27e6a1e70946c322a354ec27915bfab3897884a3c536f59274ca627d5dd21fe1e4841f12a31c6437a4e42525edbb11b40ddd685cdc415596b177d2594834b7b11fbb35c3b2c1de751fed3eec10f11f49e99de7ce72797cd47298530d89cec0d192d0d105d71506aca110a933ee15883edba920ed42cdfe42a88788c67c0e8a0b4fc2cf56865b59b785a68f6a2cc8d515d20f5b338d1df775f536a05544fea469685db4b8f7b2f950d", + "signature": "eb2210e0e85ab7226f37190116295b0bcec7e34583dc649b8715721a004281d0acbe9b282ab86beb0b6df2dd41e2aa8de9131150f2201da496d1caed45c38306" + }, + { + "msg": "ffb0824d125bc5480b6e13a0350620e0a3cdfc3a9b199a95a8", + "secret": "1bdb38e077a9a5b7efe378edbc2c75cdf3b24055f38d724333d9bce20f4fb0ee4a7d7f4bf474929bbaec893d5ff8c4609048f3d92cf7c9a0999c54ae7819c0e3af997a14451d42df3878e9ccf1b28b6db07c7cda7b0d244943d0b6b86a60cea10476fdb5517ae909672fcd1ac44b8de8a8c69d17db0b6f7ead054d9671ddf85e3adc44102c0bb5c5fc090b49ba2385125d2c9968c880fd7ff3adaf3cf5f33d7ce4c9d7f6d91a24f08b839d10004e5268a5968dff0a92555c67701e274266b6d3", + "signature": "69398e981ae88a415310160c2735021c1ede52ae905a7f999373ef05abdb674c56b7747506b27b7c9b14775c703851a488b74d254b943f1d7fcba5797b4b3809" + }, + { + "msg": "0a8eecde4e3c49a08197b5d4cf28b09d909025d960e5bf7f00cd", + "secret": "4dc77ebc9bc2619ac096eb094b4181b328914131f92c109fc0645c215c1dbc3e8a4945c6ca6150536b99d59d8fcad095e92c5b8a165cfb4fe0f4a31b8197e96ffada6eb106bb2419fe43c5be9be8e49c1733fe3f505c55482f33c42cbea5605119715515bc85748fd7a3057105ca6e022b958abeebe54cd603c28cc17232be9a23fee76ac701a634b186a1cdfeade860ed6a40072109953900eae878f6906cf5916bd7f0e705e80d171dd5b9b416b98cb2fd9a0e28962afb5cede82a5e1333db", + "signature": "c0396cc3de336bdd093754ae2cc250bc7c19a4f8c36f919111385db5810243ced023847e93cc97cd2ee99ede370a5e408a1e73c2cb4b110318493b592405e302" + }, + { + "msg": "60afde19423e42cde9d69ee1f0d23d1d8621147ba6c06aabc58f82", + "secret": "e9aedf36869273dabc258b1ca1acfb4aa8c7de64f68c63db930d4e00dafd7989a4709acdbed8c2491ac0a653b1fc01a154a694cdd3bfbb06a1b4bf1684da2c6d925493ba79a249c132ff27a49abeba367839cadaa30ca2834cf6128b4d2696f9b72999a06c4b668de383fa5080f22b315856641ce4c15307786ef577b618caabcc655bd04d2aabc2331da51be8f10ddccdd4a105fa021cc9a3b8bb2b0135fd321e1ada1ce667e72f922d87f4652c9f1a1082ee87370e4d12f2feeaaa599d507a", + "signature": "7f274f68075feaf3b4627bbf078558a72848bbfd31610554913ca6dc26ac11b2e8856680d40f26561ca50c7c42804ba67f66a4a979f949baf96655c6b63c630e" + }, + { + "msg": "46f0078e145ee40f6f37cc158c783b3947f237b31b65eab547c910f1", + "secret": "d1b579f4e4c4e53af4abad4709b5b4d15befe3017c6ced6a8961b55f0be9e4a6c77b905da44bb22632dbbe80e7544fc3fcd73ece17004d5e519b6d5c90bc847d95db378e113713d3b31b3e2c62aec9c77d72a907b5cae4ae9ae0b4653d8ad29fe270d04a79ed343c63506b91dcf5cb5f217a398adf06fc71839a319e0a14447251546cff21fc24d14f950aa6537b2dfcf99701a11f10d9527ae602c06f401622ca4f211d822e71af8f576b0e2fea289b2c09d09c99f2fac7880b67a1baf009f4", + "signature": "031215b491cfdaa056a892bada94ca0d8d03fdaca26cf6689a904e7b9b8099b321ad046c8f83cda13fce79e2ad83f5876765c941c5aa0156253ef1cf4d7ed001" + }, + { + "msg": "39fd3596038d2bed3f46d85b23d51560ac1b39dbb14b33763a2fa60d6f", + "secret": "cc15a5e9b92de9227605cd5477e3ddc3131fcff1ba667052cae9baecbd1ef59c3163e987555016e11e1a05636aad1d0a3cb76e050bfd68e3246f8e5b817ad1c344e3681761e48a3a9ec868c0d8110c429298eb2b7b7469cfa2994e175c802f6442f51f6d3dc38a9f7d4e5cf02bc5c4d0dfde227fe381d0b419d6b2d2dd7955de558907b3b983f4c0ffd290844b59615ba86c6a225f8ea5badc27890db2022599c59f4e924c01124e0b502df3309c2e6bec8b8f888a87fd7c75e9701cad7dccac", + "signature": "35af05ed1d553f8bcad186f05dabfb2d3c4eb2761ae8f18b5f180338b59223d4e30d1b97828fe0b0aeef959b658f6f5561d76b201fe1132ccf3f16a9e29c1b0e" + }, + { + "msg": "1c99a7764739f8e0c9aad738d682324634ef2537d7cd8b6ea7f851e18d6c", + "secret": "732a8a082a9519fd3dc9ffd40ee588c294464f17bbb1d7fa28b0366a9d050bbbc65ec86b3a980dc76c0fc5f0cb430a4482daac3632f424de772ced54b7cf35bda4162d4a6aeb43b0641d999f5bf4d9e00205b507b737f72d650c1a220b19039ce6d688ec9edd5aa75fb7e2e6aa15f43a79e511866f9af846119d5c37982cea8943a481064eb29eab2dc54111586f0262b0b2f792adfde5dcee321de8f3ebc84cf363e432069465b1b96da66fcd3be94bec1345608098c2304c8c324fe346d7db", + "signature": "42a14ef6a3cdfec49fd6c5cb20e951137aa765d2c83500115285880afb2a30439add3f5ff434cfcd3ddf93e78ee38daeba8c2ad0b7cdc0b248d2fbc457244c0f" + }, + { + "msg": "64929e10df00f1aa90f9d0eda61e3e22debe6377a8be35157f85b7b0aee0c8", + "secret": "70719c9cdf2ac2394d78eb22ad181db2232238ec93742ffd44d4714bee563e6529bcd455297e28351ece33e1c06afa89f51812990280d4f28653c96a79575a6011dd84f75e32db5ea12daae4f6a02261c7a99f361b87ec8719d538c3e0bcb54361077895b7db8925b154e15d97830b6321d7bd62cb7af434de3459e624817355b5cba770be83ff783e4c7f99b7065e5bcff04a6a93c575781feecf6ba1b0d66209c92f008d726ca659dfe319f710a03001c7269442d4e96bd0e16856f5f58c0c", + "signature": "3bc1cd00cb2bb3187beb801f15eebe92740c90ffb11da86480ee2abb7fed56223c44630a814748324ba68c12ad376c898b9ae17f5bad33ee507052e8d83afb0c" + }, + { + "msg": "af545990fd89df7049da9f3e8a1e2ad3482779f3499b6b86e978fe05061beaee", + "secret": "c14183eb17bf2772e365bed0085578aa08e1040b5f75b904ddab071506553653b09a7ccfdca66ae98ca14be37cab1df8de5abf83751fff7f5cdea7a1fc2f89cb4473295e48795beab231551ad47b55684cd3708146e2252ac285e8496a6a2d522e476c6dd77f920bb149a83780b945da50960154ce7e4f7167f0003a4f43d2278b2fb513f519a7992184301adc866e8a3d79e8b026bbbc843ea90aa1f335c599fcdbf2733abb8531aa293b2ca02ec88fe80936fb96de111c259c665a20a46199", + "signature": "ea39dd36920a0c806028baf6743ba2d35c5dde025ee52e25500cd1aebe7d627977b98d48f09b4953354897cc6d6a47b549e2b7ddf3387af9735ae5714c911405" + }, + { + "msg": "a84f75e8afe27678f98db708390a51220837a4c7ee32b274e228445a8cb3956aef", + "secret": "99bd6256f2a98bd7cd331438bc0e6838803fffbf9f551848e7a724ace8eec0598e35c9c479af6a7a988ab9322a0c419af3190966241015755e041e8f6c00ddaf0004e1c634d566d71bbcc3d38567280972fe4ca57a67fa3abcc320dbca810aefe9a3d9ec8bff13b0e1dba87b44c90424a5c1f3ffc2b79090ba14c9b6d5b483bf528817b199bc59b1c615f6656e3d11f18f3ed14bdd4c78a961fe72ca12d49059f066c7c7ad0da7564ccdae34d173b1575e9dedd107bfdaa5e59eae98087a3f2e", + "signature": "4a9236c04919bf848480690b4588889b07149536e5da423df41d9cb953ba5cd8fbb8b60fb0f96de56995647eb3d752173d82559e48b09cc75dd6f7179bf24c01" + }, + { + "msg": "b2069a1814a8bf1d317c5aaa80fe52e4574dcaa33d52e875fb04cc2dcf1e68d3e40e", + "secret": "42375e4b32bbd1a12ad31564576000ba77d961be083aad33d98bccedc754820a72b33f27b258abe636c72fbf1474ed6a587fbfc9b64d4b9123b4e3384a4914ef96359c927fcdf408e5d80273f0b474a1c73c67da072f214e7b325f35c432b47399c19159e3966b603c58819cdc394d149303bb1fd95794e3455381621b6a13136c567d6cdbadb6d95f8f43eff33b943424c7738ee53b0f5829c727b2396df6e61a1363c94ff0e62107ef8b0955cd6bc40e495a6aa1e1ca08276b9e703eea670c", + "signature": "b63207d5660dfb131dcd521c5894526f2828ba60a20ad3cca6f75f94800d2592d75fc58fd0ba3d81876683fdf941e9ac3d3248faddb20fc96f5be4888fdce505" + }, + { + "msg": "ac48ea03b563a904b1267ed01ed1129de6588955cbab48690cc07b9d92db9c2412e379", + "secret": "6b3af10ad9b126f48ef9e673605755309233a556e2cf948df1e6113a4bdb90c056a85ef4c1e1fa90ece46820f8b2df3c107bee9920218ea0ef6b58de791943a2fea9ee8de42ab5d1ba39265b95f0a7523878ddc2166ebf864d6a2de8cf80b2a7a80a9fa45c0362c58c36d51248935b36a4a8495908a7235465ac71e8ef2d0b1022b3f21d6c7fd58e0f6f42a7f1b36346c77539760f61ee1b78ead10bb5e58084b3604e64f44dc4f96b162d853c1a27b0ac042698f33fd99ccac89097ba09da51", + "signature": "d9c4a2694db3a096fec94896bcca8277f32092d51b85d3bb441d180350896018681e78e26cd0fbb0857398238ac14c859cd05ea1ccdaba499d937e120ca83c0f" + }, + { + "msg": "4b1433b262da3a22b52054d6cabb33becd556a3e27235db655b34141b87b39c4d779f5da", + "secret": "19813194683d3a28cc8658a62e911f82e8f132cfb259ef0b0a8288b2ece4a6dec3fa679cfb293966e5e964c97079cb0bafcec9b47390cd3a6b098c0ec0116cc351f85860a4c84bb9568983f864a0614fae9bc2fd83f97cf174f626952904aed3fdd1ef4f2b26436f6c99937e51ad94667b6b1e2d13fbe04ed7ef3c31879df21fe6ff5de3232e6f64dc24a8b8a28bb20d2280e80e85119e4884d151b27cba62fffc076cc03cd91c4bbe5107e9026e06384199c3bcac0e10a3ed71044cdcab9148", + "signature": "2f145af16a3d907f85f4b8d8093fff281463ee822a2c0c7c684a91e66a4fd073866442aab89ec4f50ca6cc5719d562149335f617ad4116a0c8bcf1fc56e33404" + }, + { + "msg": "73cec893a58cb9b28315313d7545468099a13d9c02dc19b359cd05076dda945f43cc46e243", + "secret": "17dd82667de964450d6f709c4f750de26bfb7baa98bcb216159fb8d3943816c682da2a2c55ebbc2e55cda8ced6c937bd26134ed7bade58b70e9c591461b14a33ac723544627848c679be8f38149a45ffabc05b849b1e5636e251c937738754d2e9dde3148480e9bbcc8a2174639178da3400850a3a744d89630d49c1032bbddbd1c50cebe24a42a81a79e60b186afe1e0fc008d2ba2a5b33ad293d66bd6d2ba2d7938395eb9c1f40900567421b67027589db038066235e53225d7e72229fff05", + "signature": "80703df7bc962ebd021d99742c55fff0f47dd51203d6b0d76da6dffc50818a0cd85dd97c43006fda3f6620be5918b40a53066db9644a7dd907e37e222733ce01" + }, + { + "msg": "6b06b11a97f37a65c4909555e3d70597a77a98441bc03cac1c52f48896ce0d77588c081889b4", + "secret": "aec065a55012a4c9650eeca7e273821289dfb76c7213a1a9a18b2cebdda4fb8bc4e8838c772bd3ef338649c96eae967645c67d8252899c1728ebf64081fbbffda94e5f1d9d4eb40baf2ecc5b6e81aedf7d24f42fc514f4fae90e28de72fb027f256e4ee792a16c80583a648b9544595a6539e409ea8488c23be5dbee2ebaf6a0e9dbcaf3778a84bed11d1807bdc1846edf58bb6f0f13b7f02094a3cf800293eb840d9a6f3895743b5d973f3f5c01924382c62324990989ef7e0be3f3d805179e", + "signature": "c58292e9cefd8512cbd5462bbc9a6aba3ac16d068f270b0639fc44f06a796f67c9ddf4e90b504c0a9caa5bd0328ace759f24372d266a14570851ccdeae0f4002" + }, + { + "msg": "2da9dd67a5c4904167b44fef0440609ab9a88c145179d1e8b8867182f6779d94310ae2d3965997", + "secret": "39e31b24866c55b48c3a9a4121b000513f61da42c5147f17a38e7b1f6c97390eaae71fecf6184667bbad56581ea2752d7e21552f2c5680a6884d3f01e768f4d232d971cfac40bcc186003cc0485df6f0117bdde0311200cdd191f4a739a2ecc8d05a35f9f70bb92ae4c5f8a28bbbfd476b8eda3641cd72a85e1b97245e77fed1b6758023a1b95746436d58f877d867203a2a5a8750a31eadbb2023b9c6bfb7e9e4bc3d57db6023c0e4e33548ddba68fadcccb8e46218c1afc105d819782ff404", + "signature": "69f9d3ff05251995a64404f7dc45ad95eccfb796e7d2a945303d3f02dec0dd923049a8d51f2a013cd7b4d293deb4fc764128c01681d9081af0e5cf03360f9b01" + }, + { + "msg": "282779a570a02a1cdedfc661b7eed3e62a571c582a7d945aa01f9974abafcd81869d7368c68b6311", + "secret": "07e539281f7a316bce5ab69c3baa1b7ddb2d9ffa10ca6d20e5be1a535be5f473f8bc5ccd98726bfa258d9f1b244cfa22f6682f5bcff19d012eae10dc8586a7a21268207a47273a642e726ade2add288d0e176d2858c3cf7c7b9d98124205bceb87a94db639b660379bc2d074755729838ce8b986146d0067329fde3898cfbdee9f3df1490de129ccaf0ead2faa1099cf6c9947743a215de01e89129c7a755b2777515eab14ba40e15e67483d1079fc9007a5ef02a217915af23368b8a952e153", + "signature": "bfa7bce02cf799d2ab552ad548a061e5f7e56a1253b1e6785c1b862957b76d1d5ff03dae21ae8c4ba774a114a446a8cc197746f56fbefd93294cc24b2e72130d" + }, + { + "msg": "b0e53cd8e580a10690b806cd8bb1d9c1b3febdd7b7cbbc9dce6fe888808cff3336f0d06bd927175189", + "secret": "0a8f0041326083d62cd3487a18bcdf325c1d38353006c2e6ae37c3eb356d42e318c3bb551034b0eaa28aa9415f0a2c9fb265f2d62cb9e082844b295400bf953820b2f78cdcc77d2b48aefec01532f599891c3e1892b5f3d87fec6a400be734adedca0c2bcc6961023eeec08a31c5fb5cced39bec4c0b23c6c9ebc0e051a5d37937276d74d5a980cda990f3a8de847ea53c320fcc2130a3473bf4e934463f264b3e8b9401bfc60ce1e080ed277af3a353e9a089793d40106043cb8af2fcb62e82", + "signature": "fe71a2e9d5887ec180ae72cf937cad427cabaff09a59c5c8f26841c0d8d40706e5f69b952118736715658263da6a834ed9740ec944d0ea8f625f3954381edd07" + }, + { + "msg": "2287b517352afc6e1c27a4279681aec462b63260c06e89ffa5a24305bd3ac653c6c9da7129b419a402d9", + "secret": "0c9f7abb2cc8e68ae0ff5a1708e921d7070763c207d6800477e6c8ef15338af1a3cfb868db8e7083cc6fe21e03ae8dd11e78a4db6927a8a99ca0ce198091cf21371f35ed07d4f3576ecd78e850284ebbb26b44b416826b5829f728248ca8b51c52d8018d0cd9469b9b4d22c0b6433525771edb180714b783484b72614e55e8ec00fcf8ad415b6f7af7ae99f04c8778d89e227d359df7619ec527d83e63a1a54fce2bbbe44e7b68196da78a5ad9e19e100dce8f90e502a76bc87097cd451354ca", + "signature": "a6b4951efc130b915fc0bb074f790765753a5bb136614c1777800035763071832685a30a4b886904ebb11c595fd7d9e1fe9b2a7126c31442762930f841ff220c" + }, + { + "msg": "0072222efa9cff32c083f2abd8365863f36230718e51056e9fb8a45c0f4bbe0b08f38902500c564b97f974", + "secret": "93a1f51c5fd34c61570892656cb4f77ed03f1a377a299ab1a36167db5c521c6c84fa4529760f629bafded8648aa9b797241ff87547e077708e044b89e54be5eb757d46714f4d5a2705d0991d81015f6d66b3ef0ccb46c80edbdf2d57233e7b8bbd39939eaae8eb59e064ae7603e2795ef6cebececa592bcf932f9f90b429e4655c49787bf34b1df5f34da503a9820ba96d5d9f8c4d12c4de012723230e378db7506d349cca171d9a95d01a475f3196492cd49139b9db7a85d8811b0c88f2583e", + "signature": "36432a9abe725c277ac5e9786f25e55cde3b04975a774674c26cfa9a8c6f7cb5790324fd23d5c950e59e571a4f4041fc2ef9593b093443754fe80629887de507" + }, + { + "msg": "f2c102343e1ca54f2b898345b43ad901e90afed6966ccd7c59ff65acf3b740de6e6b9022f3e8841bb86a61aa", + "secret": "0e8a7fb9697260a110f57272394150ade4ab01ae3663bad97c86f7d76fcda3142bf3466b48aeb38abab074e04d914d67e219c6bb54212c941c69cd82400ba14d24856e6156ea3a4f6974c133addb27ebefaaf41a759eb53fcb58dddfd93673739b48942b154110d2cb31a1de757cc870ad4e2098d5df5c08fe3bc5c2d5f9de25eff6769c0ec0a72373e760680d396695c57106915429934fcef3542a31e4331abd69a4540538a5bb15b8151f22cb376d052978bf893e58c2e600f828dd066d0d", + "signature": "566e5e2d249638300e0a51f00744e0ed5910a47726d7ddfb9912bc548b178afbcde25712827d7e76ce5bc7cf2e580c8ddf8492f1ad81bdcf0e5701d7b1c3cc0f" + }, + { + "msg": "ee45fd7e94eadc778e69b35b10c718d973c942b3f6019db4d530186f86062532eca9fd15e94b4f2fc6bf52cf8e", + "secret": "7e34a377ee5b1e368d549b45e3844971e3ebce3541d8ddf9cead2f387e16da6dadb89079fec6cd1bdecacfa2d5fa17437c9584c2f6fb95310090b2f4545d28dd48d05d47f4743529dad67ea9952974ec26e9b0f314d2fd11b019baf8c1625d758974a2da253445c4ec240535b52676fea303058a17675798d7aaf0e66cc4b18aa24c11f92ba5ba643a3e614edbbebffca02cc3f651227d7296576d8fa19cb9c2902e13c9f7236b08327b4b13741e9bcd63428f55a550fffd1447b518ef2a7d51", + "signature": "7a45698aa21cf911ffa138db8ce83c331bcadd8d00bb02d01e2a1a8ef45a3d5caabfbb8c66f154cdd9aa51ec6c0acfc5131048022aecc74f679f073de0306601" + }, + { + "msg": "ecbb0a52d487566ae56cfbe8718085140dfb63f3813b0647562fbdbda818cc0d14270e6134d448b38a64f5d97b7d", + "secret": "c4bd8743d9232e8fa2afd0273c6824f012e7b000f71c4a67874bfe822b680246e618f2744d30cf613d3186ab576c56207184b76bd7d7eab2c8eb63df0ad7bcb13bdd066a953c2e77a4e475f9b831ff9c8079b3d9bcc56f52c1e83b27024c91ea7b2bbc95fc769224c193f154c044cb21fd73d9a613dee6b940792f5693ca7b8fa994bd9b0c0235c2deb9191407f320a72d0335ee20d17dab935b389ff3d0d2f8f9a5ca75829d45b1dce76f8a5a00b06b9d859c211db3988695785569e08aed90", + "signature": "2d019062c9122bfe1669eb6316888fae604d75174ca351dc22f05fcd36c40f44d62fa672bf0185cafde175106c7812b9f66bcf43f16abccc2dd51efb9ce54605" + }, + { + "msg": "77b7dbad2eec336deb6b65629ea532f0fcc89a709a69b94c2b7ee587e05c8d8b324b2f5be26b0f02012798cbc55e80", + "secret": "e124bb57d44a54ca7d3d7ed0be641893f6c06a99d71b6b0d79e3514924a3a6885940215ccce86de419bf76dcb730e5429b9e89273d5a3a1bdb321b15868693c1f72bae8d77ba185d3c9e7c2f7148a24471df3d09690462d8d4151506fcf2878cf497753140fa3c2c8b2c092578a3ec903452dee7ddf8914cf0f427c5b01ceaa170b295342ed3d82c4c6577ab17551d2c359861b3072e454bf6b9fd9191b92a8421ab782b434726e28ddcc3d063ac2c8730ae1cbb97a3627cbff3e8c2cf6b3877", + "signature": "c0804d9dc2642f8dbc6e68efbeb72e8f1daa9bf24308041476643462c9c4cb30af2846c44df9daf8cf3f57c0b369dbdf4e57dbfa817868f6c84ec24bc82ede04" + }, + { + "msg": "b6128985e64c7b1bc3cd565f34559ee4157143eb4f68efa2fbd8611777a668cd7d42e4f002d73e64ac0c57c06514d7af", + "secret": "18abcae23dde9fc1a88f666fc0b00f6f77651863e776a4fe401d09afdd1f0f8fd03d1b7d3522a3d2393916717580e2ca013e181e1a51f2d629d7ba2178e601d0c6f32e8f59d86cb178ea682362ac1f5da6f3248a3b6846cf1bb021b7f50985a8f5b39aa5f2176c9357b022ccc2ae6a6c383ac1d1e021199203a5cd625bd49012954ca64ea4842549458bdc9b95fe63834f049e595ea8c2934bbf7c3482ed54b8f80c8498a607c9b1cb66f5a770b0f762eeeee2b4121df542f04ea9d4ad4abb8e", + "signature": "7140db908a8244bc5a4992e020df191679fde7d0d8a6ff691c6784e78d7e967120c371d217676a7f76259a6b765823dddac71914b8fe77882d55387fc94cd206" + }, + { + "msg": "69fda1ca0ebeffcd9e33676e8fc3871c40f4272ac24bbb164d146bca138e2ebe9b6cf192d504c07260e92eb04e67f73bf8", + "secret": "3115f4a637963fc0164293c6eaa13c0f7fb8a33499cea3813814f9e3dea1a71fd7d3fd6c47201f75f716371ab1868a044b2d9e0f0ce07179ea8564f5dabb7bb6751a883a7f1047670397f0fc9d1dd46bd6e0877f12427c0e44687a59201c73a2a9094d28a73eaa99d98585649cb24378bfe1a3c0a296bb98a3d7dc748eee37ef475be9423a50aef2701e777e16126a6c8164d3960431494d0c478b468f9792d6e970a305015ad13f25818f5c9f30456ab1696210f50c66e5996c2e9efb228cd9", + "signature": "0aaf683aaab00187987b38a3a06d2a49310a7cd1e8c8e4bb3ea7455d99aa86cfb9935e605cf50babd4407579066e6ee830278fece828b8674c7f111887eafe07" + }, + { + "msg": "", + "secret": "2e0aa5c85786add6b93988ec78178f8f3edb81043eb597682aec64c9daba491ef8c8124474dbc8fe6b0ec1ade58c9a8be5815666206b04f51a991ab8a3909cb79ff63a0ac6b217ec451aebda887fe10053f70b4c231e96b575f477429eb5cb1e5a0de55e3d06b3273ded396d28f10260a102b0b8eecdd9105209ad29c205eba07e80b9681aae204450cbf39f430eb1ba928bfa3b4826b3508b05c8544d61d00b856a6f8f12a7ea8981301b9a50e11a02d77057de5e4808fd6906c7d0040f5b90", + "signature": "6370200a73d3af797143fe7c755effe8e9a2c4e552273f10702adcc386df3b85c3b171beade6ed10054579c943d18f8ebc02ed87566cbb0fae8279c273822b01" + }, + { + "msg": "c9", + "secret": "762fdc98538a23946913848b58ffb49e5eead4d955e2a194f3f7cf0c4e9bcbfb01996dbc652c8be86ac95710603558f872fa29e7296492fda9e9af4c4518113b52cc2acc2e582c41f2a4a6c5ef833a6bef6a87f17b1d99d122e69002ceb90d7d152a182dd98b43ceb424bb075eac8215f9359e1637285d2e7ffbe99ce45d2fd4193b2c66174c5ce96259f44a129f67541f6086d6b526aba03468cf2d63e763d5fc29f25512f649817c4d5aca3d4dd7dd5b6a40aeeeada01c39b680d747cddb38", + "signature": "8310ed56cb71c4d8b4c31f54f8ad5ba801df58be2ac68315cef7b5bb111da97cf95e32a9f8b43d5f80b09f208c2338f874f294d914ea4536acb338d18b1cc50e" + }, + { + "msg": "b095", + "secret": "9dc9c56a4d97776593e98374cfb55f38b0936c09446a34d1eb9ed9b30408df7d806c3599b40d89d9cfefed6e17b2e5d92d1732a39f20743c1e5552ac83ef148e767a242aa3eff3d6aa8c5592056e410694e94b9e24fb8a76a5eae49fa4e5de6cb0f06019e00f6ababd62529403e64604e2baa603b01f2cd83138e38a61c06bb33fc11115caba8526e664a6d9be4718cb8e719b83829312bd2d5f5205c73a89c334b1b257f8bd7d2a43817596de784200a472b1f0786157e169afcbc7ef2de34a", + "signature": "7868d66a2d4b85010c6bf09d723166e72aed4ea7d44a7475c556b58a6141347302202c464e3f0cfec79d1ed6c440e647d5e9da63e5e2fd0b2823287c2ac8b701" + }, + { + "msg": "196ae3", + "secret": "3841ea8e144bab3578c56b98fadc0be59e218d334fffd6d2ac96525692651d1d955e6e522da67f9156864534d76ea0e7a934d837708fad63a0d53abd3b6e30d64d55b7a103bbaa0000037916507830876c4989de7447829c446f7f9b7de17cdb4ed593160ed739403de39c5d178cd158fb6292e8138257420fa7dd36784390457cfe9ab15e98292bfeafc0678ab61770a200b1bf99db8989221eb66c5e5f433a37f47e2f9d8635e2ee169d249f38c501cb1877e8477bc1dda34f70a24a397ee3", + "signature": "4276f2732b59a075e87af55ae83dca4a371a37829e95d2b83eb413d17e2fc62199da50a59288cf39b9212559651893091d70fe73b75fc3d054b8cc86f094c20d" + }, + { + "msg": "5d24a95c", + "secret": "6c9bb1a35d5c5b63a3e64ec07fe0195b012d644ed86bd88308ee9b7bcd2d4a7e8192fe5e107541554d45016f39bb42cf8c4af348d0fd07e53055af6f7e2c2ddafd5429f5bba4eda12ea0c3b3b20a3d2f0bb56c7f86d79a0d7dcf29b4f17f0ac5375af8a4ebf4ae378975504dbda95fd0b7b4a1dc1106ab5a1a624c22ce145fbb30bd36d6f37bcd7983d7ae111b3ea0f7e0b076ba65ce300fd354ab5dcf611b27fa489cbe170647095896755f51b164e196daa875723756529da503f31e5ccb2a", + "signature": "dd6fd7ea61050ef82703702a9cbeae39174978f4ee1a95ffc3d0a595a5f237a3614fcad04a61664276f2f949c62ef236f306137dfc1b3901a895aa69ae2dff05" + }, + { + "msg": "b0c01450dc", + "secret": "f55c7d29adcd4ad3b719b60bbaee3b9d7f0c3aaf6584681ea1627390cd777d715e51541f1af9cfdfcef377c4902ea7006bef7246963ff989a903fc7c677b908a32f6f43de1fa010bd2653d8465211a969d62c51b9a126d827917bc979c8ad62b1ba68a1687b36e761d4cbcb48561ba900f6f27b0abc63cb1b82b6b7e5ba0e9bd9b8e4c4e2d239e4d1d67481307ec781b7c86f4166a138541942a3334003b862143afb71af929aaf3fb65113ba72bb3c9c40de73363aba1af00bc7d2e8cbfa55b", + "signature": "db0bcef857e31e69ec3c292ea4be5d38b19cf8873283bee237c1f421d058d1b20a66aca7f2b4258717ea2a461e4f59c95d7ba9278bdb3020b50b39925aaa2900" + }, + { + "msg": "674b18fc5837", + "secret": "157fb34ea768cd649dca0f10a08c05abdcef0828896bb2599735bd1a8888b9ef86ef6eb8c75cc972bd84f3a83992e61a6737c689fed9239f31c892254b884e40fb52167b7b7d6c1a9f3eb86b392a0710ce672aa660fc232ef5079de0fee39d408696798c60ffdb725a64db825f2cbefd6fb2157e32ab30a762093e4049be25aa46ad0b7c108be7af7c3b0ac885436333365381cac181b4b3604d9644eb155baa52ea23b87a6747fd91dd09e166ae8baa7e58429e19a7f78bbc3e533a5ecd8c02", + "signature": "7e57845e6f8a2dc86183b1be0b1eb90bf59b95eaaa11f6fb3298fe451d221cd1817d860ef4f24eb397768f3670ffb026037f6e8eaa583bc6dd07b68d2356b205" + }, + { + "msg": "584e50320bf724", + "secret": "ce329c2e233482d03a789f6ae58474ce4f8fb215947830c067a73ce8adf7f0d98e7aa38ecbc231dac6decc8bc86be59117b8bcbe7e859ee32c04b283a7ab83a4af75b08f2fdc938bb9b819ca1d74b27be29f2924316049cae54b4c4ed99d8bc6764ec9b9ceb18fdb02299781bef208632ec387dd675f60c8e641b23a5e67b7b3ce1eac3cd7a8b3077c6c88de7963ad06d20a0d2452c78a102d00adcacccbb2c72a0c35f9c90c5d52d1ce2cc6955559b67e41c7e7e3d86753e1acfd55b4a736a0", + "signature": "c0bf3679679cc840e19d8ad818f87c61009296260a58b6a9e90e90c863fa5ca36850ed0c2de90516c096d64dd2ddd71836049cbe9131b79f1dea6cd3a93c0300" + }, + { + "msg": "8db6281949f6a9c4", + "secret": "cfe15ebfc10118b3a3f7eb7175f6bcbd3aca8ba7072908ac7f3d5b5b4d834edeb9e71578e41041286ce7306896fdc5e6d9563f6e51fbc5a46114d8349d858622b83cf079dd7e3ccf3d6d0e1d47b246c0440ff6d3932a15e32eaa4abf171123280b8e486e7661d0a3699328ff69208431ebc5f52b3c57fba1db41f011aba3896a8900d3a23422fc91996a4b18545100398c247a9cd1d79ede9f208b320212cf9903a78ccd3961e14306c0fb4d4c05dcd8a0e436041a275084feb850b756ef53b4", + "signature": "9ab234b6202cc4fde7253df116e68c707f0077ebd81bd858ced148d58ebae3110286b99f0b41d9d2ca218439c2b38afb7495ca85e803c1e36f60814295a37f0f" + }, + { + "msg": "f29584baede793d105", + "secret": "bf9fcf6a399541f839d4320b00654fe95c74b9fc38a0f43f5a28cec9ed279f7637cbd6ef617a25d67336595b9ac0e83002d2c60bf5765a9580956300bdb83510e56a823ba7d7aa7b5974cf103b6236d45303bed7f021c1698b623af7f6ff8f9bd16f026fd93b2e2e310952509ec60d12e4fa918e8ca8be84b09331ee32d656a65dad3b8b4bda7f54d348e1f05ff041660d8bf6301b2a42c12ba6d2a9f642f0c491c2f7de7850894c703f6fc5e13420f5dba2acae7ee6a4fe3bdbf5fe8808b5c7", + "signature": "033f0829b98ebe8b1caf780f69535db0a8c147e7a11bf86b94726edf7051deb7e042b828c2ba5ec3cd9978a4038f66ef6232ca0f7099d38d70db2693fc377a07" + }, + { + "msg": "dc99e47882a0f98da6b7", + "secret": "8536b1ce8b286a8b162da04a5780cfdf2914b736c06fef127911ef2cdf4500166019b2c636d43f33fdfa28f79c5e70e00163c1cc65a3aab05a16ee869d6edbd20b55b4bb2a82d26393a211c317d83eb1d007607f1364a970184e4ee4afd783cec7970ca36652f162ac1e75cb1b33345f05defdda40b563504afa7a6d93d9ab4a1568dcc19c0a58128eb7c44ad2a33491b7e61d4a4051ea42d9455fd9e10f7fbe33d823f38b4f3e711fa2b1d1334791c7c20b2b37514db14c44877ca4801d0c70", + "signature": "88bbc5ca9095d882d961e3a46f0f174327037e939d018b12f212f82df339c7101d96777c15c8609d3d45548064ef2393046f0e850ac1a47a66a1c8992dfaad08" + }, + { + "msg": "fafb8f67fc6ca154c90c55", + "secret": "0b09c8f3077a2f44e7ab865ff97ede8f9a7fb2f274d659f9c2433d2da2f48a682643e4a5a6a49796777e32fbdf09d814695aaa244a6407436de9cbc0db9e592c43d8ab933477aa32580820e16a32db7e928370a0b6a9d42a5d62441d34416e903df6e0b991008a946d509a2d7f5f56331f8c62e3faa91494a3b5564c5984c42d4980816138cc612db4f35a892ed129c4219b5b94958b94d593d022685638bbdcb5aafadb5d9653fd5b5ee8a6dd1b4487d038fbc331e4700da8eeb25704e14630", + "signature": "e8fe6184aee965cda44b14732cb5685138c37b71b2a479cf7aa8e5fea0f0f631540d35ee4215fd6437ea20f780542b41036f2df05b0cd31a0fa6c7acb280f007" + }, + { + "msg": "b8b8f2844602fef97b7b8279", + "secret": "fe438f5e81d634fad43d95185f751e97c28c10e2efdfeda9c17603eb653a9f4823557675b9e4a3bc6e82e338b3ac3de3f3d981cb58bba86339e6db21444c3186aadde1247cd7481922da1773cf26eeebc850ca1b5f648f9b60f9ee480a5c66e075770cd1831c255978d6f3e987fac70ef69616beb1c2134d28feaf987540c07d4f35240e64fdee50d0fa7513ee7d93117db734154e31408a8aa4f22491596143363b0e763932714c84c9a50ac4c25ab8ceebc9e4fcab43207694e710af1282ae", + "signature": "b9ba258747884685dc3d742286560176d0e156202154715baa5b329dca166e429db133a351a3b81afd4b96241e877979ea8b5fcb283931ed15ffe4d06943ad01" + }, + { + "msg": "e5b17cf871d01be0f13dac0ed7", + "secret": "f504830132b2408be522fbb0358ac94d93a675fc96d42dacbe75024c0a112af63b5a849928f1e7c34bacbe390dc2cfb705a55f4b9c079a6dc14a68095c41a03cc0d66986625a5b42ce5063d3b89e64a6d996348e3543765114bb450e35a11f0bddb87ecf01a219c49dc878fe1ac0787eba986be29bd421e1a4f83e9e787017e22206ae96825b190c2b7bb06809d22b992e224cb10735796f02151a5b301f7bfb189ed147b7d6633987f5d082d0092f12813529516054a9b6e4971db870923d10", + "signature": "c38f2a1e25aa2c2a8e6ff47355b46cadd24c8b65909d253855a5d1bf9129c969383983edc3b1ffb41c2c4c6611b38f3ef0333dc6399452f1136ae775f2e71d04" + }, + { + "msg": "a6165a07db77f23f596e16a66b9e", + "secret": "43923ac886bc99e799924038a698b5255b1e0978c9a300c67cb7bcae9c0e2c687c53511312ad80850dd6803e1afb2390ed5079d0531a689d921fb18325518a5265399d7e35523a4265510d4b18fcb41639aa55780cb45dedfe66fac077bf4df642076ae3ac1cf50ac0257f055a4ef5bd063dbb6ad3558994c83f6c68d70b7e0786f902fac6e8b6b3218d5458bda2daa30c94fb1b9782176754a1014e5404f81f1f58c49d81ca708c553b7b4ae507d711696cfcb44f76d3ee857863db47cb2e1e", + "signature": "d622c4eb02d492f4fe6f974e47777d6225942673ff128b6bc1514c1821fd4eb4f94e6d4bc5c53fe7969247f7994ed8da231b7cadd77c8a9f040a28009e4cae03" + }, + { + "msg": "ccb64d1176faa56baf75cec8835bfd", + "secret": "f967f5eb8ebe18de5398488632243c1b96b0116b713dbf4f59b1303fc66a1890a0c476466669a6424511074b515d8d3e190c4ab18e7921790d29fb4521d7fefe9034290c793f2941f2c27fd546e783fef67061ee07cd4dff9ec7b5184de30474093a06ac5a68a1515361dc3f2ea6d48c52b66ef2f774a021539ee560570fdf92e127ceb50788d089980cc2607fe4f902aaf1a074cea64e824b7a61a4688a23648f8771e9700481fa669ad86a456adbda89a604cfe0f66707e9b360af5ccd37b4", + "signature": "acf17007ae1a3bdf0e29b41a47a56d9e8330092181243b3d2095c82b93b6941389d07ffa24bce9747210beeb2ff77ce80a1f8c58c62f6f26b975e618922abd05" + }, + { + "msg": "d7536271fcdca65fc7d4b12b383b5b80", + "secret": "7ff7eafbc67ff90b8d5083375e494854e79c3e64739fbb04debd25942c6e9ddbfafcf366e42e52b49625989b0de1298ba5075e5ca2e6175feba72a48fcbc9d4d252a7ac3926b41c25eb0ab50a9126783a199e21271457857a486fb1aba1b522be922e2a15762978abb41c5e44c6e2848f2a7a1959c254d75b449efe0bb43a9a7249764f9a3d7d813726acddfd7c5a6333c42557fd488ffc2ff28e209b164d9342a7e602a1926752186623bf4d20b9dc9b2b37aad9676caa85c6549dc6b17ae7c", + "signature": "52af0bbb44329b2e22193ff599f9955f373c2523b20159ea23183d553ab1249c48e453bb73d3f223e4c44afda33ead095f14871d33e5c34e977d347e26cd6205" + }, + { + "msg": "c9019d4f4c6ad1939500cdafe12adc7eb2", + "secret": "cb74adc08de414a19f7667f13de5dca5f56d3a9d0e5ab08916a2408641d3ff0fb305477d84407732b17550c958866b48a7de5d9670460f0c23c79c7ad961b6953d5dc0a0180de80604696c1aeeaf323e465f220fd0ade040675658b5c518ccdcf3ef9cc90b958d239f65a37900feb91469615c4b888f2d66eb214a41c425b3b84b32d729f2298bb3ce8c176bd8642b19c90134fd2fba31980cc06d23713b17c7d1204e7221c5a16c8cce5da1b8d3d021a7c75467a74fd43c4eb37b4ce07f9fd7", + "signature": "fa9429fd50d474ccd39ab6c75eafbcd9d6c11aa2fe4f0cf65003db216d8444da46c4348aa1bbca2decad648bafddd65fbfebe57fb509eda2a92c539774eb4d0e" + }, + { + "msg": "ed862b5506367aa330f24a2afc4067efd9b5", + "secret": "f9c1cd80c01ed6a5ee54d94afd9643ced7f5de174123a73b9837a7d14fa8df7716865424c3d402d9fb50b3ead8fa89bc0ce41c60bdbf3a91dbb15620049f186b31ae45bdffa68f8ffb86ed89679d7ed431d9b017ff52a50e85df2cbf12cb35ed1ce1513435cbb8b3ca6c2f91f1db138d68c44c8264fc2d5f44f0df563e68cefb65ae9947a9fa72411cc8ecdafd42a5c65ce8ee9e364588979cfa5d130809b80c48252034c921a36dfeca9b1d8701b82261bfee45969d444bc690e714caeefb11", + "signature": "0b4cba15b1371bea85bffdf96fe27c16c10ac8e1e3c6c716b5b60781b05a4db45fb8fe481e8201e05aaefc69e4e2ba7f563edfbc8976bcdfb043ebc020e7210a" + }, + { + "msg": "e810792e33f6fd36def8e8cc2e0836076a68d4", + "secret": "c32f41f50dc215528af2ba0f1f45fd2881b94b2d345a067c437da3053727fef9fe268ce0dc4f4234edaff993d5d55309bdd28dce037118893426beada98dbbd748bb586dbb4c9e3ecd91d839cf8e22ab443769ad9b41d143d84c59b1bcace87a6c0298b94cb466e47d0de4f83b4ce0d8a9602c3d43f2e4560a53ae812613dd1ff9f84ef624ba9aaa0baaf618342b9a1f1f19255856ae65f13e1239d0853493ffda1e26d0fa9c5a98eea81c32293ef0ef1a9773afba2806e36c238b9f11556217", + "signature": "089ea0e70356eb61fc80c9a198e6f15ac54b2561bb2cf1e70cf36101a92d273462087c8973d65122474bd12e9cbbfc0bf5681e1a9ce202bab28f3cecc097b80f" + }, + { + "msg": "1e833124a6ea8fbdf454393e5793ab9149a76403", + "secret": "e437fda4026cb5adaa5e7ee9310c480c5faeeb068c4411650bcdfc57ba5aa3639ddedf87ff83d72197f8432ec47ecc3d2e2cc7d4da5f5d7cf00ef8a4f60d045b992e13236108dc627144a06c8085805bfd3b85e3434f676f470c85792eb899366d615f49cebdbcffd793a1d6dbbe6e745fa7d896b5e6b34d2b0357601a194809fc4d58839978d0ce30d19400e21a7bc4fdec53ccfcf43cbd67ef4436cb941beb18429a157b79737262f336d674edebf255bda8013e7e86dd27c96d37ed2dd4be", + "signature": "f0a988f89e2d8861653c99bfb7a392cc08a849772fefb38d3dc93e36f1c6b124b31a14ff4f55d6ce53898122e0acdda11904203f8af63defe50fe4dd1c8f2e0c" + }, + { + "msg": "b27d5cc84e2ae4045b1663e58f305b3a49f6c862e3", + "secret": "ca596e2610328a4f5b7175f415c31dedc5f8df17050122fbbc7949c3d4bd2371447647a35ea924c37c496a1b614c965b3c07119b9ed04a8c4143e7b6a131fb531c379bb5b9c3fca3a7f0436ef91990e3d45d87fc995164e33621beb5299cb8be635c87a50177e9ab5b0ad7a1d3b9c9a60a6ec5faf03b7fa15a14f2268a706f3a694cd1bcaec8cfc676428ea6cc05bf29fc8e6346d912a1aa79ed159f7bd8b7477d6ad7d67afbf0e35d0cb5db863c0f541188b2b7bb8f48d9ead2bd37155a17b9", + "signature": "839962e5545c7cf5d9d5fdddc067561b17cc49c30b2ee25db717763e5fca70227633c126a026493cc375deeafb55be046224883a6a233c9c0103cd45b8eae70a" + }, + { + "msg": "a5d9f5791c26dec2badfe42144d3f95dbc1231bdde64", + "secret": "9fe167ebc41b6ea2a6849a8b73da87d9a366cb0352d858259738c89e7f5d9bfb49026bde944f9bed4bc7246addea0c2fdcd68e0a105d3d967e74ff63ad047d20a0f197a1791931e893534ccaebe75fc8c6a6e51b142232ede4e4c26e77e129f631700a98410f7c743530b981f2958afb060a2aeabccc6c6567db62a88c23528681da2870b8062f834fde4fcef579e6eed2c2f0739f4bdf8c91cdec13351638db721bfa22ac71cee5c60b0883b4c9e6c2f4ed490f19b76f01e2e8629dd88a90ee", + "signature": "2374f5ed0f7569bd9847ce102a43eacee341a31e55840a823bb9de517ee88da2ed6e178dbe3a788c78be1af5c533a1c518b3414ab0a2d366de5224323a524306" + }, + { + "msg": "87579e5c092eddbd1f91f32f0cc5b89e0f6eaf2cfd51dd", + "secret": "e0ec8367d19cd4e18502ab98878e8d68ace73eaab54cf55d9efc064830639365a09705f8f4805c8bcb342b455bef32fc5e4f7601a1c6b27f7159c05b22bd0cc046dbd898e6ede915816a394e3623139ff6317afc9a5aa092103b07d93a3854bbebf036193daa4169efa71247ce4b80e4fff70859c2ca6fafb19a216085746ad6a477056567837086af49665172fdb58e84b9f44fa4f938b464345cda58715043b67038b4cc6a66f99b47f4e32ca12eed9ba3d227e10c8bf720a46b33441ddc56", + "signature": "161c1f4c000093ab623a3bde7e6e40955645c8afc281c3997fc1a65eb037a24c4af541bbbc6293aba31c1074d9023d79a2110fa53b653fcd3eb0cd25a6315b05" + }, + { + "msg": "11235f9a1bcb7984dfae6ec02770b1784ee46867ce0681bd", + "secret": "54daba1de7bcf7e81219d3952338254c74b45cd16279ac90fd6392cda88bda0724d72fb65a5496a584c77ebe9f8f31d5660fccd5d4e082d802b6e2ad212abb12c9608567837f1181829bf991d99b50cfb4234054d7b31773cae0204e68caa02349e35ace09493af3ee1ed30813aac85bb68165ca0f2659b1d74074b41ffeacd71c1c8c20f1f9b69847e2766c1a8bdb9288737c43d4b169ae745dfae9e92600387634241ae315f3703523f824452ae3299499ae34112cff300691063bd5c278e1", + "signature": "6497a69c115107759b35c8c03c0cd777776c805b47720e2b658e316ffb1316f3653d201305d80e1c1216c618a39ff34df973792b68f3b8d165a72cb8d4b4a908" + }, + { + "msg": "751a67143b94db7e9953a1e40b253907a9d09c2d974ec0dccb", + "secret": "6edee25cde32d7c8d58c88ff5cbcee19d29591ae7d535415db076ef778adbafca0aa2e11e4d650f75d3ea15f7439f47a02d2b2efc008a211e5f7df0c4c9e51ca845b1ea9363a59bb881afdf134dd07019943e0bb67dbbb7e839cf5071639c23360410cf507f48231ce00dfad0b3bfafd174ee73be3efaf617e83150174cb0333b50f185382a77dd887aa2c4eab6a7b301000667dcbe993142160b40cf942d7853af2ef3a6915a954acf52da49794d2654e9d589ae1942e701a392c4b0bc12318", + "signature": "d15c08fd709a6990cc8c732e5995b5af4ad65a4a188704fe766f507351f5b464964f3f460a1bad46fc58231afedb469cc56e28d626b6f7d475841748d2fd8101" + }, + { + "msg": "2aafe3554262ef9b2a39edc349d37dcd8e72fa7acf6ddf2d41d4", + "secret": "b27254eb11d1c5ffa03e1e2289a6e9d284086444b8d8c3b503a0de9af8fe475468bfdbdb365def8b5325beb27c63220b06b1880b9dabd83dc18d9cbaa2dca76aacbeb2ae90fa8b30b0fc57cd3de2a249c1acfe91640e5b47c33d4ef85e7f8c35bae3a76d32272ffcf5a03c955a5e4a8efe134540e7d95241b543bdb2d90ff6ec90964071c4903aa17cbfa87cb68b9da781d4535cfaa4aa2619bceb4f50230f1fca0a20a6fa36539768f090064563449c0af5a2486b25dd96857729c327d238d0", + "signature": "8c467daeec122a3039d3956729ae266854af12abb002721c1423e1bea56a8d886d2044754c6fef671fe1f39c9a361497844b8573a2b63d547312f59ffed44300" + }, + { + "msg": "9bad5208a067223ffa12832ef6e069d9f7f69219d5cc702717ba1f", + "secret": "e8d64ae77805cc78e4a1029a31818773c698ef21a85fbc663b6799194c1dc33ecc18ae633d9a77bbb55184ecadb3beb40d81fffbf4f91680cba75e28dbd4a820a36f1c8d242d4ed271a44c8365e8a12555594b82c608d690c3366fbacab93e2d801dba3046bca4ce9d13b3ee00b4971280567736af8dd6b7fb148f16149c1e79f6534d7b6461208bd022ae4e0c5704a9951a99f8d1074930cabd821d59e46a798ea3936aca33b3262910d7a556471589918d650681e623d0dc7ba2837ce67e5a", + "signature": "621a11cd80d47b3edd352fc1950fba70242cf760da50da93d57371ee1767b7758013d29874b6722ee2b01e5aba525fc1799f7c5f13c15e355e307c9b3a9b7905" + }, + { + "msg": "4e8d7a10d6e534303d1d67b38f75c3e2e395e98ec9a99b72c989a090", + "secret": "d5c08cb59a17f5e0b6aa7b7af6489d2aea1e4a1ff541bb37a1461631cc56b582b165f67e5fab2f07093949726757afe67473d54ef08219e69d2eadc1fe95378a85fa916b697c7b14bb85a15ee62ae7ed5dfba4a4037c8932f5743ba1ca17d49e71bb17cf6208fb157bd2d4e324770275f05e7eb33176d4931f4783f59fa96a1804df326fbff2be7a268cae0c89fc9acd77f2c369d640e18b8840c7fe2aa0f86e51c23fd506bbccb96a2728f9ecad43f3614af018faa4392b0ad5349bd7688e2e", + "signature": "c5b5690e5245811bc7dfa05750bd39dc37e768ac19a20b803e632fe3f41082110abe4c71e063a47208604a433a80c05655201d33fae12b83c67807ee19d48908" + }, + { + "msg": "768bae838f0bc46a18def9affb16950d3788476489f56ec4346fc57919", + "secret": "220a02a36805d67eb0715beea873db984901eb47835da6b0c88a4d7efea92d5cf16e31cd4a5538c319cdd6ce2e92e7000a3185da198e6faa1eaa8ba22b36aec9bfbca6b4dd22fc13e13d01deef2d0f00287f310ab22f7bb1b9a7ff01fbc7183ebfc40150a624e02b5529ff99c0cc90558fd313e113d97e06b85355a37ea5c57d09cbb66359c26d3db1f2c74ae7e8f072a84257bfc00b905a2c9597eed0cb70b7c9f26d6e05859dfc6f38469fb0f95bca85b1ebeacb2804adc61291b971b8bf51", + "signature": "275ea8df4eff1604ac40db59bb9f45555503d7781debfff17eae590038de57dd21b9866a2fab7a382028426c0d52bfa0d80ca976bf42f6de9b49cf0494bc5f0a" + }, + { + "msg": "a6c8359ae7d62a737e4d310d312738f11515e08efb7370c6e3e2ce740202", + "secret": "91bffb1f56f5465654360d8bfc65adfe4239b45f83a8905cbf1d38acb84fb7d1977713ceb848d6a5b9138b539227462cc05abbaa49b1c9b41f071d43ef079c1f589cdaac10bcec9d046545e6e8f43b792a3c97d0f8156a8dd271bae3bc4331d7e6be5000725f4b808a2328628f960d2d5e18c1f0a505274ceac7977efc5901284594104fe848f3625645399d2179e41938e237368ca8f577560461a8c053b770fd1a58851ee1f55e6d8c92f35d65f1396678bdcd5ee4b177c7ac46115ab62a4b", + "signature": "b9d9ac4bd686935a79a41288b92cfe8285271d36d9fbecc292528570a1b1028280fc44ff4932f206d77fe202c5bab49ec8f4d87ccaac4dd7e1d425686b83000b" + }, + { + "msg": "3f8612623bae5380254c3ca2caed112b46e709c5d76071b040666679aeac43", + "secret": "4912c5d1db4dfb010238656cbfde9e36f4b8c184e590c80a87f44c8c134c70b78965bdbc3b0242f7b11e4354a58b9438901d1b091e2ac516733603c66c025812c691f37985dae241ad4ba1d0f08fd5523d55c917ef645f826d09349cb98687cfd9c48a65703c9b185c3e10bcc91a191ec0ac245b1a709826b33e65b9b64e7c74e13a5476b7edc943650fa81fa1f4d86ab4a67c9677aaf0a0ceab2b8eb5443c78af1dc82736981f3a10cdb29a7ae9c669a2a98c9e06c50c858afd8339adcc2833", + "signature": "0ed6171d3fb2357bb71437e5f9644f6fce5ac78c8f03a3c6b29ba0e0147b6fca4a0b9942e2b5d19c32b1afbe73f6bf4a2c9aabdd12694cd9e92f1b543974460b" + }, + { + "msg": "b56ee57a284369590ab822eeb03d00f761bfa0d10a8a367d086dfadaf382e302", + "secret": "a961f4516f24121f0b32c6b1dac809ccc2884fa10a1be34ac7acbdbeeb0e99476ecf8b35448692d96214fe02a64bb7605b240bd54cc3dba9d53d562f5e5d5f3fb17f5820c8cebde3c65f564269ba7088b19b3ef7f4f8c256cce683233e30e131e3479b30ff50c2e5d092eef3067225dc4016a0aa798eec36587a5f358853b955d3cd1b7c0f697b2f0caa241f20e7d72233e60f0917de37342c63668717bfaa38bf19868c91fc3ecd3be9e0b973741f31179b2ccd6a2956889ad1157fa8e6e312", + "signature": "91d0bbb4a9df4f9c23e8066dc4ffe8e6aefbdead93cf6ce8066224ca47bfab55b9db6134736a61e3ee5b8282de3eb5606cfacbd48e0cc985c508b07ad9c85508" + }, + { + "msg": "7856f8717163ff349c2872607c45dcfcc4a3b1540d23462fb018d6608c737d0eb3", + "secret": "572d7d5e452a7f4d25d941aae38b922e40908ff1b4217f2bb3bddcc9c1fd4fd73a3b1bc526af7ac98ca0bd6e05d0ac88893cdd36c4f9c6fa17bc934faee8ba36d7f679d9208ca4692794efb6e9ee957767ca08ac72fb21668de5c1440888914869d4002e204e78053e4749da7c9d817777b70d0b3d20e4b3d5ad84ca3bd6b7ee8f7aa65b68eefebcb0be7738be62ddb41dee83daabfac15ee05d2121f15007a67fa4ea41909362eeb61d223fc1bafe20de6f298d5e2952560fb294a290383825", + "signature": "09111de12109584f4b177511fd928f3766214fd0efc12ac1c970f4a48971cf4d153e6d293e991de384eeefab161dc3ed369262812bdf16043b5c77dd782bd204" + }, + { + "msg": "53b9e1d1535137cd0407bdaf7c46358ccf50195959bbd1fc2f3521061fa2ba61e893", + "secret": "58b15ce561663637eccac501a74896ed6e1ed9d72eaf4af1d80296295c83af83c22bda769c519434390293c4340e2461eb7b830fe48431fff8010d0daa8f639a55e0faae82ebaddb4753138bc0e40a3b0ee747ac93bfa8cd12755ee9e8fbaac75591e72923485117925da924eabd73893ee7034cffc58ce31e75ceb4be9cfc89606d2aafd98dd8674db03027ed70e0468ee7bd03b602293f0c4b0ca297b37305c7751abb0047b0dd7702e65d8288aed41f2bf6b906515dbf272520b2c9c230d8", + "signature": "bfe8d910e153d7b5679f95d13107c52d6f04e7b1aacd7890870111dc01cdf08a856216f6d6bcff504de320c64408bf743398fdb27c857b57533305383f83af01" + }, + { + "msg": "8b99bb4b8ac57cd70124ad463729dedb0902a254a0c3bb67bafb4d8f3fe1b98930cb7c", + "secret": "c3848a872d07d465bef5d24168954ea6c259be10dae4144883286181718ade82a6cc71a6f3ebeb5cdd29dfa60373603c4e36f9dfe8d915633b29fe81e666949a84f6929877248624b21022ce04e578442741a7bd9c011d360e3edc2b4a00a1368252ae301cb89e115b08cf8aeaf0e6ad3ee9b1f01803b165e494d663545bf7c99c0909d6c671612c3620ae1bee7ee8f5fc29c04792726a3c4ee852da5df805999de49dcb1a7a304695eb15374fb514cb62caf28ca60b4e8d4ec4f44bb5698e9f", + "signature": "97c4b15e5d8afa06dba52513bba2631c929d00227dc162260008ec9e6bca8d0046caa322bf882f83ce5a6dc5816021829566f888d0ae8ff70d5f9cc28da8ad03" + }, + { + "msg": "f2c0f46efc1c29b555ad337093959e9cc654618d7ed5661af441cb7a55ed04cf08bb1a7f", + "secret": "015038b0f31cf1019211557198de1ab761789c0307b9318028040345dee89c2a54d00b7c9cdd9ce3fe738380b02badb1d18412e192dfed526131394e6f33d8e180193e7d56c5937160d91d1985e11ee2239d148bc130e51042d82ccd286ed6fefe0322149d215a847583ae73add9c991ca17a3e299d19371e0a61c4a1eef250a2df923ad92780ed2c414ecd41a7e11bb8adffa228399b57da2a870843eff1390ececa0146ce09bd3169a3c2e218d67a760235b8c7d7301826ff931f49234a737", + "signature": "9afbdc8f242ce2e75898b1c42e66493cc69799ddde503efd6b2c2213084b562ee42f1feb7e264d4151951984c03b4f95a9522418033b49dfd91d21230e3b0802" + }, + { + "msg": "108b2dab73389271270335e1b4d90f53de0d8bbfa348c945580cfd41f821a0d293cc59b14d", + "secret": "ff822c5219fcc3bc6f8382ff1102d7ff8a2cf54f7cf54a6040e42dd054abb9a31130e89f5934c6aa463b6f7b81c8c4ed7e17d7cdf1281be298f2a9c7ff9bf92c879a37083101a0ff7386638e8373129d27b9bc6198ba3dc97a68e1f14d38041ae042c6d5c4912ca08fc0ec723f0a1eea32d7cd546f6b1ac5aec6bb86830b8a9f5e13440af2d78de2f5d0308a76fdd83564e9076ca9561354124d53473a07f572f3d11bbdc9b40e15449110867310e489bef13a1399d8d9f19da3374b859677ce", + "signature": "c0707c7ffa8ed24b268d1ab89317497cc8074e2ad63cb7fc1b13d8f91fb838ad924bbe88139e1d0bde735cd4999d1d20bafd16dc716b495b77f8573ed263d40b" + }, + { + "msg": "46e879732ade4e2c332f808da6e50b27033554bcdf97063a947ea186274741918a9d3fc92e46", + "secret": "37c497476f7182a2ad19284576ac787566c4e68ae5327d318969b318c22734a35e60dd73dcf1456b5dae81395d9857ff65286e7f7931ba5d90105cf7aed6e9c45c60cb0e1c449a4a9ed82ba228f7bf655bdf236ce134f331cd2be2b034126d146516979cd5f5a067eb8c01272db9bc982db325a3794e51c3c9363262dac32f9f8a28edfccea80c0fc149c2fb3ee9804b81897b06d58c82a4e662e9f0f4290e824a2e1537dd944458d93e9ae9a4aafb1e55a45cf5f1bf9e04a61282c4d421f51b", + "signature": "f4ef72e195402528bf7d77eb727dab27d7d5b34c561c5ddb76698c7df62251eecc875c09faa360db2fa2c168ebb7d0b062cca3a0d6450f14e6d231052ead0d06" + }, + { + "msg": "07887316bad2036fbf7a9adcae32290b8ab291efb9f41874a4699889b01783df4db42f8975923a", + "secret": "2a3db38560c60fe66eef269fb5d99caa36460bc14846606c754e3afefff38fd9f7a81aee17b52fe284adbeb3382ff24472690f8e69bc358f3103fa4bff11421f5f717da5dee652c9a8360a37cc0bdf633cf05da4b25de390f866acb850314c1c04823cc03cffb5aba018e27465736914b6bf47e82b89fe379b271030b86279162310e53c99aa84e9336f09a18300d9de900780c33e9736b502b93c6c0227e30bd21e7ea7ff7cbb29cbfed444bd63f8c42ee2e4c113c93c5369095ccb5e442b12", + "signature": "e8f6f4c29cbe6b5f256e06a333b0ca9c5ecd42fc19e4dec92bc482d88803c9a3992f2a36b52436a990cce98bf30a8e7e030f4ef30281127b5ca60a28c9e6ea09" + }, + { + "msg": "3818a8e3cf632304fb93c982285e2ff5658aab0558767afa09b26338dace35f28e3fa36b33627cfa", + "secret": "b023d033d53a5813b7000b050dcece766d20a822391c4d6c65930681c83cd1b01f564012738761f01ce9e9133e27cd35b8cade7ff5457bae2fba85dce440c693ee76fe8e67c32767c151400e7bffd2ac4fc6a8adb99214215a18de761980a46d1420358b031d3cd7a97dc3673598f38680cfa63089a2f84272c25c23936b934cc7fc9b0ac1afe4ddfbc2a3db2807ec3a2f0fd23d5d9e7830bb4101d2c5bea3626c9b1ea466a5ccdbb316656c846eb4cf2027977c64118184f2e3e1d72669de06", + "signature": "7d7055882337f67899390b66c98cffcae4981d5890f5043bf2e4813e3f5b35a75f5d88a0e91cb15488a2ecb94d163cab82a2860fede7a20c4e5273fde18d7204" + }, + { + "msg": "0fe04c64f078105907e72030d237ad885f62e8d818b04b4948eb30e06aa2e23253a6412fa63c177e41", + "secret": "d21708a11d62efca1b4d826bac250ce6128c0ed063ca23ce74d6c69442a9c0529dc0e0366b1cbf7f3363e582e603fb63730735c7cec509cb65ffc2d93e72e7aac2c59e1d6ae40c7765907db2efa694eece3ae093cda6fdf9104cb138557f8ec6f36ccf1e02d652aa1493db60290189aab5295cb3f4ea9672da8bfd6677057097cce31da2fb5e115619154c2c4b15eb77572869b3c0c9905a34bd5e7020b77232a57b05cf0c515421e33ed2f5a5ea861724db76bae296cc09feb6920f4177ede6", + "signature": "c030a2213bee079328169f09da34190559666bb83ac8d8a3846fd777aa56166edf9f99507d21b85966374a0cf709c8912addf403ffd056c22f0436e7418bb807" + }, + { + "msg": "4c641d9841835e1fc2cc1024e084eebd9e9297f6bfdb244033a97cbd66df1d9ddcae75619abb1fb5872a", + "secret": "137123357aab6c25c1c1d49e339047abe50c995cf7a7e2f29ef4be9f80340bd7fb7b1f8c8bb75b9a8c1180ef9af6877cbf1d10aad19a8f2a7c6a3cb067375ef46c77d2e5ea80bd5e5800bc1506333678e3fae746c9285a5a25e967817ea9dae4b36aff7012aa84ca4938a426aabd460dd20ee8e6dbfe9b6a465bffb40a22c04a6f6898b9d2cb1770aaed84ca505da527d078daacf5e74730ae38067f03963a125e817a645090c5c5ed3e63883ac09c41ff8f632a6d06d5e45d7b5be0acb34980", + "signature": "e55d4d724b582a83c3a0b57d469355d3d7ff1aeab31ed4711d5f66dd8f988f213908c1ab1cff483daf1ed9ccc2192b76116c6ef390311e8a8c4891984323ec0a" + }, + { + "msg": "55562cca8bf1a6f8a107900b0abad5cdafb6b37be4320003aa98007d392380d964d572c43af60f44b73c44", + "secret": "c6d15f49c920acf9dfa007973c11294534ce1a0b011a68f6041c9396858db0fe013bb210b9c395a75674b57c9cf68130ff95150bc746708df37ff001a7aaafc52f857434e6631519c3a9ceb88d8c163e84aa1788772009cb7d9ac3d91543195df35f2f7b48d1d04d82c8885c2f89d51c24c3327d6c063e22ac029af3820d53821a5fc9afc442ad6755ccb77dc0f6d02d18dee7085d43dfa22943808c655d81059a35b55196db3dc46cbf72dc28537e70e2817ede65d1434c3e00034271b1affe", + "signature": "0332eb7101def2b58f0d59593cde54b36d1a64654404603f656706fd2acc23a622d186d129b96fcc202eaf9f27cc5cab81e2120adb7782655c505a6ff81f3409" + }, + { + "msg": "5a2fdf7563200996500a37dab893ff2ea7095984877310d301b07ade72b746ebb9e02a41a740d7111b8f591b", + "secret": "30788c4f50f21b2cce6a75738e7f48c286d15df2d300f0183b0d3c1eb15e66d7d0a6fe7d715335f98e5f7a682a8472688bd2376eef5bf1758f8b8288336e507cb785b786cab5c7762305a9759bdd66827b1632e2f36f5aac41c9d4b18dd0f301c74baaf5f49cd02a0b5059b3130167d3ae8d23785a99b1f770d6ec05731e02aa1353247d381100f9e55e58a9c2fc048f8435d4a6866da2b8f49ac02931fd48a55417cfdad8d423e09a997f094448c4ffadd269e9f831a11114a6a3794eeee491", + "signature": "16803aadeef8eb7fe0127ea2a212646ca774f1065a6a81e495188c0a14c2f48cb8b043a0b90a6be54500cf0dce2d4115ab19ba4b54545f21bb17008c54c2d508" + }, + { + "msg": "4b680720b1a0595ee2be72e5de4c50633390dcf77eec07f1d4e472166cf33af9e78506f8b6644021a225792456", + "secret": "504ab3fa5fd0929da987044f182bbec693e2c3e36afffd92c148a1318326f98ab39369f343d903189a7fcf1c6e78968ba26412593f3d9530499432c30581e81356e1f3a74cbb6c55a8a1bd4a235f761db7533418f15f90922a7045637e8baa42ee9e18fdc809236ed4fbca499e5f3e1ba9ca9665fb184ec92caee61bc24df3fd3cc8c078ea382e2fe7d31b59911777d287c8b346a9354390f7f16ad48d7467ca84f07b99983347077880cfd9ead567026cdb48a797e7e34a03c9798c1ebeaaa7", + "signature": "1e45ab548e871480dd09c9f8da63156e2b0111a562b1364eed490646c843d127af8ae26c6c57996644d92aaddca9d463adcf0c9f9e5e8688b5ddf0f920312e00" + }, + { + "msg": "23b6820724d6baf31b7d3d63ee2b1ec25a015db6c5a31b540315d6f87f99ef39239f3ed39f9ed51bb6e1452e00fd", + "secret": "f5ed74b5ac5b3f6b4bbbd0e3dfbf84bd359ac8327ead8dde776996040de6dc893193df5f480a3784279c38220a4cd3eaac13447a96cb60d154b58632331e355f039a39db42af711a8132ade75d3035293c9921a8bad471cd04e1b7ea31f1fe474910e1eda214449c60aeca8fe1908702a574dde5981ecf02c0473e37b913b14fc6d77df452b99d0981c0fcbf7a9b9772627ba9e9c8fbc76281889d86988a8ae7c28558fdd9a4174cbf9a5cae26a24e5e5f9a52b29288270153c14ced1cc79962", + "signature": "29e428123aacbacdbd2630f423656f15d6f8676b84e3661f7d77fc399c056c78289da0570eb6ef8d78fcd5d6a9822678d1f0728238b06cfaba50b5bc2d06cf0a" + }, + { + "msg": "6ccdb49f0b759ad8991465406ab943e497bb0fd9abfac4709c703b98b18e3e114eff01e4455f6be76f5f100fa0af8c", + "secret": "cb7d348bf471045822e6a5c51afaf9b758ba021320bca8d52cc2117d99cda103a98d497a17cfd4f4b076e7b09d496507a31389f61da8cb9f262015fbfaae7c9ac964185695323879656360a97f2b9fa2dcc2fb93b3b3a0f7cc92d52701cc2a3135f37cdd64d5a04882c5607238a041848a6fe0cbfab9ea4b0404d463a2145c8833e52912dceb6af540daf7ecc36eefd6601768712663ec8e05da8a1148376dc080d90f5a165b0a1fef0ee4fc7e9e0b1899fc093201b6709ba785e4b0a204b020", + "signature": "4b559efde2e70cf4cd01260830c957353ed1ef75b97dbb52d07ca41f79aadbe3234bd7740997494eaec426e2a133ed9b6b1f9dbd3f7a38adb8a961f1f2bdb009" + }, + { + "msg": "32d82beefcde2b010f6288c05de8ded0513eb5d8726e4237eb1385067cebd844a5ac72fde64c5b5c341aa6b738b66ffc", + "secret": "6da1fb40bde4e86a3e86feee59b3fc1208fa85b69c8d22914bcc54293e8ccfa9d25140a3ad0201e41ab94b7fe43353b760373e637bcebacd658df5bfd12634f02671f6264aaeb4703e8fd99a587ade60cf00d75400eb4b81bde5fe69c501a3f1e5176f9c43d8bbca604672460b49a1622e1fbb3ddafe18fd9b147207bf0ee1574d557e60206731141a3818692c05eb1d408d30844100c5d48f502216c374fa359c74cbfa7942608ef4e65c4c11cb5d462ef757dd9676f8b24a5e58f1d59f4bff", + "signature": "9abb210a7a2e5dedb84cba728686a8035c33dc1069ebd4609e6ca9b3d23d70e002e839e58cd05841222e2c66a0a5bd558c3933dab12f4bf2f15211add719900b" + }, + { + "msg": "df0b1b4bf17a300d8ac3743e710fa7cdfb4d37c8c9caccc870e232e96b87fb661f586f542aed5c885c71d6874711b458bb", + "secret": "bbde4e1f485eb562641f8bb1623ed9905ab11a208991953ad9fce1490763bd267edd302b84cf10799561dd1fccba59d66be9f02141a0465de17baf6768741e905f1dc35d65b262f4fbc416c6db33de19d1071c7889f700b057461775544dfb7cb3670a0719dbbc7cc42e4ee509d1a4515ab5e4259878f05b6bb5c1205e7bc34841c36d52f17fecc916748a6879751370b2b1c5cf5d5d04592fc48a7dd8068135aefcd174719c6ba720cc0714c599b6ab6e3c8724152b14c7ea2ea37c51db5349", + "signature": "ca19678146db4221b3a5978c0b3898192b6e209ee9122f05bbf13955283dbec1b774f367ba23a20223d8f27b419f84b21a7b298840c8a72a96c995d1d7109b0f" + }, + { + "msg": "", + "secret": "a8d01cfb7876d15f5b62c0a4766f9c6380d28ab5e47fea14c1b6908eb73311202f32d503d7cdbb32bf9201dea3ec544ceaa0e4b53fbd855686b0512ca4a8aef02f3ed741a78fa20f0e4e93db03b98047924cc8fbd1061f9f60eaa7393811ba790442e1716dd85e3e8fdd894a2fc88b31cd47a86e2a538a66d56827be604d07aee4c9cac544d72c1c52c51545a895035d89f9894e1817aedaf363d52ccc30cef31e7c35b9368c430434cb222ec1cd2354da920b2478dff532904b3f9482da55e0", + "signature": "be9d4325caae49d7b5f7682b1a2024d458d010918b1bf445a528fabbb81ad8ec5130cecf14f5c7eceae68811751d33335b93cc4d19eb05fec509b7173b1a6c0f" + }, + { + "msg": "a9", + "secret": "20b1801f0f5838e764079bdee6a6215ded32534492a9361275d0e2bc8278c4a373c853d301bdeefaca3f1a3f62330e52011bd3ae47d34efa271ed48fe1c934e9022ffb02650e14c30e83293251a0c13ecb22524813577d2c480cce49a56021be57a243211cbf7aaa9369a39b19ebec7e2f8528f31d87b51cbd01b1e4ae1f999fece3bb8a01f95e5d3f1254dc68ec414744b62b80be4db298c63642cfb13847dee35d454b709343ea54db679b816ea735e801d414a9fd5c6b96a5a2593530eaca", + "signature": "ec302a75a5618fd6b35314a774dc41031f069bf3c8d4a3bce75cbeba8894de3e4f0cb72d65db4e82c56ed8e099846e781cd07ea6924d1713d261906373693903" + }, + { + "msg": "c97c", + "secret": "7236c9db5f02bb3c1b5c248ba9ec9eeb1d28430df8e8ac65c82666840284bf06b1ad8bf10ac63db6b7b8cb51b25593e5f3cb0132fe10c915e1043ff1a16f2073ca497510ee0195ccf8afd8657b7bb21419a6acf3d9de7e1e2e40274b90d635a80029368d6ef79f6e78176da0b5d25a763f242e5066ac6fd9672e1ecebc0e9f3a3a39e5f083cddf497aeff540f736b71d26cd0adb827169699869057444b58eecdc68ecdbda98228858623af51ddc575136ac70963365a8bfe753d1d90553548f", + "signature": "a407d8eed252ac5b351a2d5f726d637bac6ab400d8513f1ef1d1e70f9fe5fec07719d18fd02a52497c88282b9f04c49143b09db6b5ae8a3de246d57d7513d70a" + }, + { + "msg": "4cab09", + "secret": "0d6a6e065691b3d83150b1ead638f7605d166dd3ceab9dc641134529c58f57d63f469dc4efde9d0f4e881863858208747178c5bf4cf135f16a41df9531b449525083cebbdf2d084db7d11f1d53ec07aaed45b88c959a19d1c039a86c3cc6f22fdda09228bb78a59c4ee393821f7c2fdd63eda40f7450bed8a52a9389d4afbf44ce8cb6b9a8c7adce6d93e1cd738e5b43791ea4df8fd649f5d2a38c79f13c218706ea9118a921960a9a6c64f9d3b3ee719a0aaf7e276938bac654561d12985aeb", + "signature": "02573562c88c156d0c6fa236d72673465940eda787019ba398ab8a5991f4a7d38da2648da60ef9e5bdb18c39cd1a5f491195321940b3a13f7d60a973c803f900" + }, + { + "msg": "8b931b7c", + "secret": "bfa7fc6ad48ec458d713dee4650de23b5b4ad2f96fa109c3e934a4c911810ab82dbb5725b8d6aa7acdb8ce5b1a157dea015c903789a66d13023fb2e7fbf055448cb9ccdd48d36773e40cb5fdb5a1a4a3eb5fe1f5bfb7137f3ad50942c661562b221b2d68c116f7ad5d21058d898c6aa03415a1224b95002a9d6459ebd25d788aa9a9af508e0167495e14d51d2f780cd28c29a18447a882c872cabde8f01c7ac24afdd16c7ac6ec6edfa6cb738090c164f75f7d7b8b61624077685a184c92dd7a", + "signature": "e780eb3d712a960229164143723c1d1438fb46ac81e7fa2bca52f8035dd68aa88e1ab7d9017f4d04c7d13a73708c9255faeaf26883b664f3d3e155e2c85ec30e" + }, + { + "msg": "86f790be7a", + "secret": "36fb426b68453982cba6c20c8a228c438821a235ca5968854a9f8ed7fe2725e03c73c9bdc1eb08954072b5f58b2ab364ef4f3183a3f421ec2b2036c8e1599a95c8c642e9f460d15ac8c4817d6bce832916af99e163effe51cefb056225e02f9996f29d0292111caf372ec14467c2bbf561ae5198c577820ca8a61210e4d7018afa7bbcec396c0494c14d3f11f49e0758b2345fb0aba90a6d291d320cc073a4827d714a6702d4c281492998aec1749853bd2023c473b4d85b40e8d1b84501e062", + "signature": "830a2ada61d86ecb37d2c94af017249a6518a624948513a47cb69a6bde67ef30c1fd19a3e505f94cb14af1b9d3552a19005b93c8b6877be8e4e8c72be3e7ad0c" + }, + { + "msg": "bea477732cf8", + "secret": "2337f9fabf72d2aa6aa739f26aa02ae98d642d2357af13d13878d11ffce779225633c671d0f2290d8779545d2db7d6f23b05333e0fe526c6cbe8b4e0f2b13d554766016a9385f76d22b5df1ced288e61caa1293bd4c52fdad263ba36e25ad12c54ac282d39ab94b7632db4bbd8bf944438a258fb8334958fe619a4b18f3b515e2c0c27a39744972438a0952d7eb9c2f82ed348757387349c00e3b0196efa7bd9dc75a169a11f00fe9fba0b7a1db841f0505f66d2d47c777c40a51804d007888d", + "signature": "2c782fb7284bdc04c7cbffb92809ff9135a037a98d04e30f47ec53842a85add630889c9a2aa93df7ece8c5f17be9d4d3339e61197506535354dac187c23aec03" + }, + { + "msg": "f010c6ebae848d", + "secret": "ab43923fc2459dac5b457d29ff690d318b6f4c5ff1529d643c9949deaa9d3b846705bdded2cea28cbbedcc5b97cb9ff0d5f91134cfbc89a178cb783ef9e0cb8b2b3f5971dbbcf81c68304e75bb7a4dc7d0368d00f49180bc738aecff0a78175193c2394005e8c5730437b72f68b4a8e0c76185cc380053afedce381c3c908513c650989da1ca4bfb65ad80550f5225b407974e28fefd15ebbfed2f33e68f109a0cc8a2509a0387214a53b7927a7446203a7a81997065f4b7cdcc5c0484fd9acb", + "signature": "91906afc1e7e69c87a4f5ae0290b0317db33514eacf3b5d9a792b5d53883168dae8713f285018e27eaae82a1b9b3e96c99866c6ee8a7f1dea787f5652d49e502" + }, + { + "msg": "922eaa2ae657dba0", + "secret": "c9e6c5248932f306efac7711ae0852a924324bc03350bc6545c49b8a8a5021b3b4427e8fc5923b54ff29f0d53bfc0982d30b73a7063584243b6acb1c73d1246f40e63fd72525c7b873bccb6f0a4fc7751d6663dd4fe5624c12f2d55d10dfb6481735813eda95c6ea7ddc675deabd5eb0732bee0aa38f590e95ffc8a605c5202162b114e9455a02e30590141bf6fbf41127286659d13b0b308e3089090b067709cbb1740003e02294107ebbd9e12f9012056a73008fcf6d7858c9b4dad892a40d", + "signature": "319f65b371305a0a0f175f0782cc51abf0930c079a64f026a1a4a50f25699f41c3741ab6769ebf60c91b117268cf96fd0d562ec7d2cd5e803716f6db113dc60a" + }, + { + "msg": "7850f57cf9095c58b6", + "secret": "77d6f071e2273f5d43b17174ef803cbfb2c448353121bb8dba819b3fbbb2c13a1df356b7cf340baf54f8b79085ffc7239551c9be6a941cdf2ddc15c1e9a8489a91814f3eb0c45b9e6cc3c09e2580c46addffbd0832162a2afc55ca6166951810917aa2233b37e2934ae2388d95fad91f1d33e75b1c991bfadd559f1731e97678c517a4c29b896d98d308599beaf011b7d4d7abb7ef5cbe982bc50987cbc117b78088eae2f711da4baaa9b9fca394756b8fe801a7c8b61039a17d61214ab915af", + "signature": "7406da5caa09b4b676c810fa01542b09cc27113ae45612e2cca9d7a283aa78915d5542639bba11b4829b981e606ce8a95cfeff93e5cb04e92feea386d0c66801" + }, + { + "msg": "91fdf398ccfc9df4ebf4", + "secret": "5e48201d43df5027ae155d9c5433c5f133fc537307d5c09fcf8ecb5d0ad6c6c136a2ad282f6d09b33abecc09d046cbe8eeb15aa122b99933e3d287f5bfb9a57b51c79331578d3f5ead9eb7c9050c3284beacb4a4292e6d13207211a8dca5ea197f143d37bc0c0be1943eafbc0b9f5c412a82dd92d2a3a2b163af5270e3dbf1dbfe6c9b606bc0c16b841a9dcfbc8bc2e1f41ca1cd9ca28aace9af34330513926dc06ecceadfa0f8ed023ceb82cd268b64d8c82d91f8c3ac13fe32d1c5888f1539", + "signature": "8261793320fe8fa4b4b14a5b8811215984d99aa6d8e05ad43a12a6026621afecfafbed7b9885c254fa34a2f59824c06c373f1fe5fbd8202c4e1080d4ad2d810f" + }, + { + "msg": "f8a2cd15d1c62a3848fd1e", + "secret": "9291e14b71689fdefe68c8638a6a83e00e28682b332af6df6272f80d0fbbbf5c9ecb82fbc7588b5a1096bd07c69b01bcdaa48c6621ef94779e3212737593b56e9b47cc26a99bf36890b865d3c291d3fc3d373c830781857b31d8e72e438ea3b4281fd8856118f3a7c1cfe53bd5939eb09278cfb055133abfbc290598187736fd49b59ebc17abc21d639cc8394ff290a7bc156f42f9efb71b7d1d4a50ce1a699534e85b57fa7930dab81f9a260a5b5aac74238b2df884e4d3dd4094c536d059c9", + "signature": "9bd1fa4386d646e1a4d7f55bbc0d7acae355d2487704667a12a11f221fb23eed70cdedf5d171e9081562dd776ee36af89a46ed8d330df45b8a12e9dbf02fa00b" + }, + { + "msg": "bcf513ce24c7ffc7d384edc0", + "secret": "cb544bd2a37241387f94839de3e001e0a1e3b214713123b55748a47ad6c73b5e0aa9222a2029158fd950053e12cdfbeb4f29b6abe69f2f941d9e58b5da76917ebc9e1aac756f288afc60c7a60ee3819c46e0724bea072de769f8a748fdf553249619966046112085a8f50da3c56827bde3712e105b4493545142a2347c957fd243a10f0d977a6232d6e39058218431517c47daf43ceeea1da1c959c984bbd195cd90a0990f7b89360f60b8288069e8a73a48fa6df5851851271bfac68e17812f", + "signature": "f3119a323126e33d04dce0418ca1a092607102a6b7280b9ca18578911bf9f57546a0c0370a0586bb9a0a4e6dcff1e47d63dc69e62564745782006b3920fbe400" + }, + { + "msg": "fbe55fdef48015bd8d33f6daf9", + "secret": "d3f054d148b6fd2e1b26a747c380dd0f26af9cc793763d32e4b42c980e7b6a91975c58b117c54ee48681edc69910a0f611b49e97e77c4dbd4cc3f9e3890cddf807158424c06cdbe48f2fbe9aee503c233d5072950669133f55f40d7a3ced9551101c220d1fee227307039cb929ca8c5cf673d5a2ddeb22893451b06e9efc0f91e8927f9a62a1b7f200ad19c598a25d08ecaf56c8597d711befde8d2c66f19045f279a6132cd50db577addb284c483fb70a9f9436e59c22cc1238fd5869b8c58a", + "signature": "081c392eabbfed9fd8867c03e6dc750d1cbccb0ba6af60635ae2b913e73e3a03635462b5918347a352c3fdf8d392ebc06921143b16097a4a1688703878552e07" + }, + { + "msg": "e851d8bf9d41d441e3db9c66c094", + "secret": "a101446e8105299d72de305a371dae7ee83ccda6b28458d8f40c2987c3ea60c3b5e5207b2a02a267234b9d90f7cd84ee4126280d3fc1a21332a167d64d0bce7f488d4edef4a83c06262832042615872d4e080f76e6902767fd4a3a542e696f3288670bd60e1f1083e9b956b6762b04c493d95c75cc6661e3d5380fdba03425cb6892fc601ba613f302c8947757b541d90e9f2bd08390289af794073d5e11f401c4aaa0bb71f60c509af5162e06ac8606ad111321c523d6433f25a64d80d568ab", + "signature": "60f2b6d7b7034a6ca7abc289fa286aa7c16d746cddf2f11296cd2923992cb648a835cd9df24db7d25419c4cd50d1e2ea4f79da6d2b24dc73fc69d0f08b2cab00" + }, + { + "msg": "780812fae55fdcfe2635fbc367cb69", + "secret": "917c05ed4dc708284b50e024e930a94f2e8a7264d9ec615607c179fc77864910ad2b9bc5efcb6731279a0a20f5f7f341e9f22bc79ad2fc3f6aadce3f9d6a523649496a5c03c2cc1ebd0bc6135aa17f73ba0ed6c14e94f3929b8179ea7b2e8ea3d7035b255dff6afe8738f86109e2dbe8353062b2f23bf398f15b2159ecc3aa737246b0eb5729e626371a6425ff3c130e87c36cd2f4936de91700fdc5b24f0cfc998a9f292e0fc44be62a38afcfd201ffc8a152f88223ce5aa82e1c7c129f5e38", + "signature": "d7b2ebb5a2a840aa56f0e3d8c8b1966c64bcf38723d8db4350b92877cfa158447b084b4508c53eed9f08cfb2e39ec818225b744736b5b114291fbfae7da33c01" + }, + { + "msg": "107165ff738bb612af6921c65232aac3", + "secret": "2d65ddeb2c0e1b4a7367f48a75558a1f845aa3a56db721e89d1adf55470a909bcff160c3cece6e51d6a320a286985c666eb52586c2c8adc0e83551748f3ae1406b9608ac34a13fc8aba6c0ea8e03fb7539c5e19ca9b27747f179210e1fd112e9fd37c5fce2c177ed19013b29b0bb1cc2fbd19071a33131cf19d1b778f0840187f5fce23ae74e362b254f78e97b1e4160b81a4a6971ea27b54a3ebdd5989ede7fabe98b073c47e2f98b9792d7aa1b8dd71e0f0263ff31085574a5042eb3d88229", + "signature": "746a5521def37b51cbcefd830443df40ca41423964895ef2a94992d02e28c9aaaf89d46a0ebaea6e16fc334f1a5a237db95104b8b60daca833e3bbe1f435e303" + }, + { + "msg": "550a1cbea975f16127b5934aa11888a939", + "secret": "829baaf00005756d581137e5313526cc7040c66b7885dacf503f8a13c0d40170b3ce8f6dcb7beafb8d26319a0ae16551f773a8bc7bd9340c8f1c017ac92ae775ec2a2479249c35401b5dfd48fd9828e783c08c8febfa81c266d3d56efe9a711735c44b02bf9bd8973d1e21939bfb4131f0fdad9557581a0181613681c3ad864bc1c9ee237dfbd60ee1e16bd13a58bf481f4ce7157345ae98b37aad777d926960aee6aa69c0f0feecbf878d50b29949e60e9fd67f699dd89c5632439d121dac10", + "signature": "7173ae4ee1b71ca289ee2ed3bc8db884074da413449d32473eda8189b26b67f76a8ca1a331d32695070c25e98906ad7cdc607e80ab1487acd0b9b251a6d0420a" + }, + { + "msg": "d50afd74b12fd0f92b33b6b10288e2e5be49", + "secret": "f0938ae2badd1574a707181cb24eaea9d7bc90837c602a5cabcd2201d1f56a02f45e375c07aecfb140e20a47221706ddc0c89af357a3862da6e0e4f732e0d7100cd3e0a2b89ed88341893f3a2830ebb1d3b1fc2d9dd230de49bf1bdfc8e5aa9ba53bff80da56af146fbdf7633190247ef443aad8dcb101feb5d40bcfc9e8cf636ffb25dcb9fcb01099be23cf703247560e5af865ca0c385f80849f7336766f7a9c234c92ddf49e7bf61636b735e59683a4755a893a0cfb9b55e0486e7c4b872d", + "signature": "7ecade303cb3ebd2f5b3cc7e7d0ba186a96b1c4c8778b8bcfbf69f735f48f0c5b70eed98075adc76c888cdf22efde5052caf29c635dc767bd33188d084d00f0a" + }, + { + "msg": "0856facdcaae8d0a1910d88573616e5dcceed7", + "secret": "d5780692f494d450e5dd93bd9719d68e47445e0b36b6e1e3b3c6d823e2b71effa6e73d6c6e90810485cfe919bb837bc90e84dc6b605656870fa12d4bc052fece6d0a12dcdf206e90886439435991c4cb345785e3fd5cd2384d125e65665d5a3589ecbc40086ab208448bd92155c0c768403a99ed2d5caf23fa6eb3838fa9a4c7ec9b71c2085cf522531b7da5eb43dd98171c6927b9f1d071ca98f4fcb2999dcb06cc6cdb9703e8c33db2b2658cde41f94c7ec2cc139f7b615b258f3d4a963d6f", + "signature": "aaca14a602ea801c3dbbc78461153aa6e0f2324a3c71023fa27bf1366a4018e9ea8d67eb1fc801fa993d965f0730a9cd8e3650a23f56daf3d6c20c26ea24ec06" + }, + { + "msg": "5b9e415a91fab5323909b1e261554e6c47f77c34", + "secret": "1c968b1c87f566d5885e126cb78ca9c03f3bfde0097454bdbdde3f694e1372f6f443d26f9e037e4627b7a20343e0f302d0652bff824a9f08f1e1f9736f282aad3c4c86d707c641f768600457d06c6b37b8527397777ae1332d4ec94f9ada39ef04e1a5895a951a7cd3ddf380e7d34306ae238f8eefcabafb7e90243e677f94c4ea6601f52bafab2dd3fd896c39d1cdbad5069739ff853a64d2fdbfd705a5eda3ea6b50c2a5d4f72329fff52343eda89ac435553bff5a876ea485fed5ebecc129", + "signature": "1c4f6a59d38022c71b81b8e460f6c40a880ecb474f973ab366453da652134e507290031a8147d71c511e38d98054bcf3d159e5592914340d81f41b44134fd30d" + }, + { + "msg": "668174e03bbafdbb94401a377028fdc56c1bbe6194", + "secret": "f15b45a1d326a9e4414f83fda3434e8381dce9bb9277fbf38d3fc456dd79a57d92d53acbb388c99b2596d31445df3d67952439f37657a0062bcd6281b569c1f36f5215109b55aab25bbe6afce9ced642d752aa14a94ba32339d829824674aa873384ce0ed511bb1da0894e64ffa8424b2cbf22f536d61cb2d74c658103e3a80322f3da88f839b6129091e8b5a645085726674ab61731620452aeb1216e568d84bb4da5fbda065f30878e4143d369dd46adedf9321fd4dd18eed41f395b260c4a", + "signature": "04b853d023894e8602de90da189966674e959fa01501957da58d24c86eafa39b688d9c66712c1eccdfa8ddea5d2b6cc5d461fabc9bc356d2bd7822d0115ee507" + }, + { + "msg": "d8f1931591cd7b1e14caae6b3993753c0dba71b2818a", + "secret": "eb5097d43af0b624e99ed26dbace0ea17b1c92e151400f2c041a3a296b0106615b80a808bc7986cc3027971be32ab1f61c779fbc039c7567093d1ac0c3b5037b44bda634d98dd07cc1ac1dccfa4378db343ef9fc905faf4b610601429a019d554dca3142afe4fb300e03d0b26d66ed6eb53c8514533220acf5b827c2bede1d51c64d17131b0e7465d41437cb46e72b5b9f1906110fb89190fd735cb9bc83a0863121c9a54a76a3531c074c23a6b26e14116d6513d57fdbfd7faaa87f08e7ae6d", + "signature": "ca5d633736ad577357a5f82123b95430bf735f9dfea3a64aafe4180c83b11cf6db2cf0129db51fc9221621f4b793a73f2fe240a2b2a985972279304785edc50a" + }, + { + "msg": "6a438d306b1988bc913c97ca06d9dc271c17cc6a33531b", + "secret": "f326b716af7d10e3cc64a327c2742e1ac1fcc195734d8f84ddad73738c9cdb545c8a5744bc582fe3112fe3f1bd42882278e39dfa6c09231a7ab2589b77d74e56fb65f7bf95ec9ce63260628d6b4dd14aa65616c1ac60fa8c3d2307542ee05677eeea392c7e2539ab18f4d6b0f065064f59da35b8e5ff0b62eee5417adb1750747a7faa9a88547b10729f4f4c348e44b295bd86f2771e6d05ca139167a3f94d0ba961304fc3444d6f038041560eba68fe749a4b80620dc61c3a5027d124e9c911", + "signature": "b46c519f9d241f938c6baed47b8cc6097a998f561826607d910e5c91a7ae4122aa6554b992b7edc88902268ac207c69b741d336fc2bfbac630006f262c7f040e" + }, + { + "msg": "04a45dde4c81dc636978196820b935443a45451cad41e827", + "secret": "7353d18f2d635a640a861532405ed589e2ca16163e6a4696622f937d6724fe6924c370ef8b86e836b5c0b4c3338472959faa5cb83bcd0abab427fca8da6fb07de2c01a8b72ec9cd01437beb54880c5cc581fc344de59e9deeec0128c5845f74c23f8ca3ab2c9d1c4398dc494b7d74121f92842684c84179b6916c9930215dcc110ae3768494dbcb10512b216410af02144c2b2caf7ccc11c129dc1ec7f18e86d96c397ca4ae24fab9e4b098eb67366d92ad86770922f29990fddab21072d28aa", + "signature": "46b667bbb3c33475835bb2a05d6c11033461d4812d3dae6426c214cd9d55a2bf1e3a6dd2496631c0c18457bf55f675ab2aa5000c1ba2aa92ced2e279d3fdd60b" + }, + { + "msg": "1cda2d715263cfd20ee44aa5b3b118e1cc0f10442d7cc86067", + "secret": "3bd820d166a42fd7b74e7b8a5ce8a368758eece8a46054a1a3c6004c5a7545f2a99e942d38f1e928f35070a4036807df867fd0c9116c5ab14c37486516c038622e8bb532096b1b1c39c28297b82c89d4182caba6a285e4ff9c1a3f73b9efc391df14a2c56ebd2858f9e6340e3653f56e1f83e57637d36318f6e16a935c2fe96bb44198fe2ec4db6b6461c41f87f6ffe7b854951045cc9bcb24381029a8d357b78eb52d2b2c594c716d4c0b4b6416da353d818bafc2c114f137a44dfa932a615a", + "signature": "aa5ead9f659f9aa6266c4b63f1e5e48aebf1a158d851bc5b50ac7697a43e70d36b1b512166b1e105c888a85982c4601d017e78f3db53e2f356921d0075cb9803" + }, + { + "msg": "ae3ff11bf8d155641f0964050866f0dd1ae4b2231c55267a7391", + "secret": "d24545096331e08049a4043583aa8adce08a9bc8973bef0719ea0342bf29abac37404255b20d12e86a0dc096600fc1bc5b6e2d755d8818b6a6041c7a09cdf9214f142f1eb8d2814dd9146fe10e25ad23bae60bacf02c3003e2e772a1f7dd848d2d0d9e8f36d3cf10e6014ee1355d544f79c33f96afe0f3dbe3d2d13d7b8104bc5b45e03bda8ed5dbc5ac64f5ba10edd314229143814483dc611fb3a39f5d526dbf5ad29b4ec9062052737cd617c7bafc524fb85422021fafe114e25e21193c81", + "signature": "8e9cc29bc290cbcf92f801814238132d17c79eea7924e2f93493ce07ecfeffdf545f1cd61bbdd4fea43f2af323dc83da8ed73abb275ccc69c06c7b146700d30a" + }, + { + "msg": "b3d0bfcbadd7faa75fc7612a4be8519ef4d85beff169a8e9b902f1", + "secret": "846c5e198fde1d9fceb81781c1a126f2e93a9173a80ac245843e58e2d3b886d13749f59c4de2122b2a175f5b0f7be33723556250e078170b83fce2569f6bc86d4967672645312e0f915e6ad43dd892ab15b237c7946b1fcad7b38e9f3469ba0297a4f5a877114f184df580475ec17d04dec5b57a37c65ff044c3fc4ba58a230068e3cd848a5cc1e0235a764346f9e5c41eb2ac6fbe9341bc054dc19bf35ba72c441eb69c61a93fdf36bce5f98f0ae7c9644e7c5c3ad6bcb7ac214adf4f44a6b7", + "signature": "aa448ffde51cdda827c5222a650af2608033e924ff76b66cfbfeee65a1fbf9de85eb09e91d69077607b0e7755b479b0080859ef97540f5ad841e113c0c6fcf08" + }, + { + "msg": "2b164d2e3bd95010bd8cb4b61d944b0d022f80a57a70fa84be97e399", + "secret": "0dcac71efec2df84bca76e49ce3531c79990bed5953e1fe13e99911ed6bde6e371ba9cb6910322e911816168700ac8e2f6250ea31ab9947cc8b11f8a603077b491a95f2aae23a43aacb2efef0d9e40161953afa8ab6284345b540e8775c59747667aa1a699e0ce073ffde284307b78540da8e559fdb8628967f45462f54eea45e38b453f2a61f2856b1e47bbd204844d18c57c3864a7e08b9397957448d22578dfbc1455e5ab909c511b23380c85559bb57358fa962ee11015c94374a5b4162e", + "signature": "fd2cd3678f4c42787bba16bc63af7c08d3058c0f5b16a35091e77691e546d5480b83d4b31b36627eba65ae45b6a1c38ed42761b7f39ad16e9e535e2acd610d07" + }, + { + "msg": "18cee04e7c03d58bab320dc472ac7c89d70d5490a225f3d4fd73198dea", + "secret": "0d92d1bc9a72e314fff026c909aab6feadd0357c4f357c611acdc39d671af8688ccfeefa8665079ac1a1c310a70db3ef0b23c8e63f6f778ba9dccfbb2baaea526b7720a0ee8b9fb4c61dcb0cb74fd023d9b59681122f5fdd38020a139c569c5778598f54e89d07ccaf33a89c36dec1ab7f228acc51b79f0fbef045f63693d5cc287fbe88ff676af9d2242322fd06ac8c088accd191f28f700e12f741c393b91f381dd07d011a557177434ccff06555c3a767d2287f68de8578ba2302f78344dc", + "signature": "8a1616806a7f08b74c58418f2921a373069d3c5198e792c2c2ad86f2c2939e56237bad18c1657f597f15680857959168854f333af7456caa127fe5201d2a4e06" + }, + { + "msg": "979a541b9b73f458d7a9b8819e331042e5f740f12f659070db86e4f1faba", + "secret": "88854fdc0c64075e124d15eea6ecdbdaefdb9a74436cf9c0ca7263b030d2387b8daeacfcd5fb911acf094b53b251d83539ba4cedff71a863f756c79aa6cae1df328ac58be1be7450bf82a699eed8701b062c7350e842ea7f25daf4326a5ff0cca93dc7730d89e7a5757574288999e89386ee927e6080ee46f7a31017de0b7db7d53f0cc0475b33a7c0d0512b114afb263af8e1f291886c7137789fda631a6408d196f91d61be9c8355fa90244fdeb799d7b048d40fa606fae22cc0a5b3c26aac", + "signature": "f3ee8537a465192e4249e3abc16addd85039c728a1f97e9480c089f9b79e5ee9c5bf1a8505e3e4ebe9c7baa195db919ba1395f6054399bf462f56381469e6a07" + }, + { + "msg": "db4adf1dfbab008ecbd2212faf4acf81a59af197015c63863278cc5af2aabd", + "secret": "ed1fcfbba4cb10ab59db6a97776136c6bd7f8cb8f96984ea64738766cc741338678c9d93e82eaff5ed2508d97dee2234108f130636c6e6ca7488cc96dd57dc17e5f0761d728b03e9bc60e66d3238ab80e5e453cd4bf86e2fc65a3fe0957135da8769c2d3c01e0b506bc4cf36b5abd5ae25c50f73cb578cf37b713ce6aabf3a70f850701c12121d0f9e80abe3114a8c3dc0d06fb730bf907a230c37eb27ca8a6b5aad53a8019263f3985fa3026edcdca96c7dd0f5da26aafbfc6167a920c989c5", + "signature": "bc70822e169d830089d3194871f6718c7dd1886c41452bc8b1e815ee5a2397200bfe8bfe6b606582b7b7639ee88b852555d102531d990847db56524770032c0c" + }, + { + "msg": "e01ae21265ddee0cdcd4dc350dbcee1d1fb8d3fbf545d0c5bee96e0668825c24", + "secret": "c45a677946f1511df8c01eef00884c53fffdf3de367146328531ea842bc9748a2affc42ffa29fed0501a6f22076f011dfc8ac058710e166c5805113c3e0e4dd523e814389a277f61e78141daf1d68bb4d024d7271d907b8f104360b532ad3c175ff0e34f7fe3f14258dbd36cff6c122d961385ae6c1457c3d6dedfa3e9bb7f994e1ea54934c9a3c9b6235f8bf49cb30e843da8457a194c692e8887702c3fa9e9e62dfa906af7c768f9fe9ae9b271d5c2cb6a53a9772694beff237c611de77342", + "signature": "90a89769fab368f723261fcbfe6f933e96a73b036e4f8f5880e0d474506936516c0a60879b49716af7897345c5b1a6e8a59c1bc41a8d9eac68e2d7f67b7a3b03" + }, + { + "msg": "043e93ae76068e1185e4c0a4e2e60ac8d3e3bc686d05c47cc1d50b59600702f3ee", + "secret": "e8cd9ed7294fddd856a5b9cd6b1819a82e40bbb7125128c2018f515697d7725e597ec769b00edd11bb55b834bf825324335b0be6e2661399dc6334c0c441f749af83ea81f83c02d2a6d9c06dfe50bb198dba92f13e78b9022a51cb9aa67ef9bdba7c5cf3d27d92ad56bb08ef3204776b5be78224b7475158f0f8cd5b09d915cc4ee2e09f75b0b0847f66fe0e16b2841d6b6e3de36382695f54a4d9780bad43bd0d3be66a6bb4786cd2ec19f490c1fb165823b2e04e0721053c702d3dfbf975cd", + "signature": "0e228dbdf47fdbf663434a176603290e8a630154afa881dcc75240fc2b922c134711b110bdec5f2e810d02844d762a87aa9064a91da4cddf2a9e5c539cff0600" + }, + { + "msg": "7eb387bf995b0fed1fde8d0af12d4ba6977a41bcd9bf979f5a2b03df86232994a0d3", + "secret": "64cab97eb0004cdce967216e1de2ec05470c90c70b854aaf9e858fffa617b3debad9710b2221831e1d920ca4a0d7a2fe5384cb364105209100feec9c3fb41d08b01b6697efb9811bc624ec9abab0b11038734364f5c91c97c02a292bec56e43ea6c9d3541af9ada3f452e1a18522a96d31d59813443bf405e36be3b8828a6f3818f2bf116ab65ffb96d4fdb040a643a01785de83088a5a8870ff81af59560106c737ee75a3da531393eb08d6009a21ed094647a7f7ee17c99625e7ebedc1828e", + "signature": "e7de90deb56181ac51b78b46a2a674a0f225cd88afd1e206ddaa2057df9d5b0a6c1ba826b7b1c3bfc42ee2898304399834f0c102a2a47ba5461f1ae01bcc430b" + }, + { + "msg": "b580081385829a440a2cfb79125c4d47a06790bbcf6647b8749f994ca4e23d4a208267", + "secret": "ebd5559f57f4daf5077ff5b313154fd8904b4f41c0b1970dd92b6d29200ac8002eae20953bda520b2313a178e88a369e06ab400385e0e4f19a16fe8870cb08355b887632d6d08a09ad21a281bd8921003caeba1856051069d14010edc91289e5c132d537caf403d126b3c55b6f4cd255f4965d6c9fe4148dc5c3d949eb268d9e2421cc2c443b3b3c2a3cb64164ee4bf0a912f39466702f12224e4d492632974e351b763420811aaf13de2e005259deefa6f8c2128570393c2394a0c9ea9808cb", + "signature": "7fdbafbba0b703d0e4f2dc85874c184264416d9b409641ea6d3ddb501fcf5ee856ae2b7cb758bba5f6c97f3171f99969db05f3878a35153aaf5857b13c70350b" + }, + { + "msg": "0eea76f04acb8c78ef3328dbf1cf8afa9764ac3677cb4c786ea4b9f23e43d5fabf5dda66", + "secret": "6d1419b45f23d1e9a07bb66ad2cfbb1fd6dff4b21902d94519ed47bab554149f27c9b10c5e062b5983a02814e91fe5552cdf810e1572e508db8f6a5839f2dae4d35d93c837208272a849c5b5a0a04c2898a2a2266584ee17950e15bb731e8b91844989ca79604d8eddad2c1d5dace0394c98e0d80264735b9c2b54eb4356ad51e066a7364e52d23caf59f83058beeda4b15d4d5ecaf9688aae7f5f55811997d97d39df88fc8b8ec83da0b6f1f439d1f290b97fd17f27611bdf33b83eace5f7d0", + "signature": "b7f0cf3af1af92cd69b95b2834f6e9fa4750af6705f3ad34282893f35878a53ec695155477f2d0a08f5992b56846ef16abe165935be7cf476433f053d85e2802" + }, + { + "msg": "75839af085afb5285a5eda48e72bf10c91e4c1950888c11c7204466b67e828bed514a51b43", + "secret": "df348c3e6a72b2e05ec4504e326c62c0daab0d6ac56321bdfab9fbe6427fd4e53dda192df54c1c93a7716606f26827cd40d1f90edb5a7f72a5242f2ba7b32fc3e89e367e8113e903c762fb0d08a6e2b1609b0536177b5434fba923958786a462884e4d5465a04800eff6b1245c94b2bbad7f4ed736bcd3346fde2eae177efda8877fab7b80a68e97f03dae47194c4499cc07293ac90805fdc830dbbc820e5de0eb1c7b8c28aae533262231899cab18b032ec32b6da709bba39a76683dbab8af5", + "signature": "c26f562f1450f6de94ed97f3e712ff7695a0f8937a89f8e4abdff4c2ae3bdd5722be4eb0128c311719b2671fbfd46b9ae7ed017ed26fb9f3c024505a2175c209" + }, + { + "msg": "4cce4a8af441f44557f9d95270ee6d51401f5aac363413e41bd2b6389421a0744e35755ce5d7", + "secret": "1744a6ac25c9abacf29b04743a9e15ba845fefd3884b1c60cb83700317fa9cb6a62de467fff5164cb617acacf9b8961769ecd7c1e91936d43a308a53f56eeaed1e17e92b04fa979c408e49bd300505cc4db5e4ae64d855d4b0d792d2c215588a72dd193be2df5e40cffba835395a218d7551f9f9550d63eb16bcb8a2250f59ac1c87601338eda6efe000aeed7fc16294b26a91dbe4525741af75f7b3545d12dfb6359ad11302f0d3ea4c2a67f091ab1db70c214c86e09a7b1de409baf76d849a", + "signature": "2bfe6c0966a045794fb528805bac5001b3085a2f4ba010ea68cd81cba28203f157238d3880761ff2b6ffbc21712a8642117b1eb80a755a4fbdf730be38a0ba01" + }, + { + "msg": "c30d798c0352809e0453b2f55fc856b137ffdf36c043ecbf419427313229a5d67dcee42bd5724e", + "secret": "af63581ec85c6da16dbb9a2a5ff4611444141249737471e0b562df46752b9dbd220a9597cf8cb374a82a5dd22340c917c31dc2b4a18bad1b93d27d44ea1dae6ec6772136742f94bb34188614c1f98498448636500237adffc42578fa3f01d38168203a8c11ac40bbf973b5eeca8780e1b5186ea41ad28b8a7faf32a9dcb577ffad512df13ca7c318bb9f63cfa8d90718dfde97680169b4e33b9f01f9b827222bc2216ef7101f832058d01604dd6f83f65200d305547002db7c4f605b8f50e084", + "signature": "ebc44d960393c080e4e89cf899c3cdbcbb779ad6a9f074e4dccb943a05c4eb4d38e728703ea312d5c5bfbfe0d51297718b66ad11711410f6f8d4d6ef9051ec02" + }, + { + "msg": "72c740ac0b2e7691747231d8c074fa1f166b648efda4c2286f382471561fc148a36cb892e384e4ec", + "secret": "bea36fe00a35e50c7a8f8292ba730d10cea9cb1d5f4863f9ce43ec57906173cc6fb5e6f68c0cc78ee9762d869ef40dd795588ee26ba5f78375d075194e72d66dfe6c641bc4c10e13fc0e4a62c8242bcd28d573aa146c85738f788f0c0414a1c317427209520cfa3f3727e8e3c09d5247bba75fb08fa43f77fc101849fb8bd29d85f66fb7b95dbf1ea1e985b67273cd8b9ede0dadc93e850bbed3cc965a4a1844cf9a128f849daa9ae9ff2f4794a64e5b5c69a9eefd2a3d88385969ac2fe8ba15", + "signature": "8b087947b837e2e40aee95aa16dc3520ea116215d66273cec2aed5ef5a7adc2c30ea50e698c8c8eae391d552bdbb3d0958911e9583badc88faf52073ee8a930e" + }, + { + "msg": "928244fb8abfeaa716ae0e70a744caf4c9b769b2dbd2f0da4b25409d6e76fe4e6240aea0f175f6f9e2", + "secret": "0dc810122e52f727b5e51a0490a6ed94d4842a4053b1aff17e9c9c126d63c302684c27ca62583a5825b9c74f9ea4c1c0841264d65ff3b2532d892ac734bf6decf8d8af5aad4bd9890525590f2251123ee7d4637acee995aa08e649826adab7e02cc85a030be85bd99382c8cd43191cd073d586aa1b82530c5d0e2f14c051b128724b66fbbfa76340321a387628209f9c1f74f603b0ccc417073b191d12197585814a51117e57c3119b526c4421f2287f62d239157c1193c9177dfe70a5d4eacb", + "signature": "58916991017f8c73f350a1412161046a069963e419d019857db3410b4f610655db044586d341b22903e11838bba9616c83bb74f9341980c6bf84178a74753b09" + }, + { + "msg": "62f9d109ee0baea4d31235eea68ccd276319fbda7ae94c7ffa1294a48b99f42a414720b146fa79f42ae8", + "secret": "0cdf6066a7274c735785ec3abf083f980f03fef1e76214191da99bfafba436318436a7e8b1952ca8a969e580d3cf6663fa8347b4e2574abd1899b9ac285a85f7894449fbef7dbbbf8e05b926e03ab38e8564626229d379d8386d14f56b92623fdf4c06fef3af9d7be22f5587e9aad6eea2573aaafb33956d76cb5fdc705dcf5964fc896931b85770c8c8676b10f3f79b97635468b5f3056cc84a332ad1a3c221d6796d37f85c3c511a89f7eb99b33903ab268a09ad0d82d2a475a031c63d0d5a", + "signature": "82cadc3b349ed1e95be4fe2f8f7516b775a125c95102015d0030b94dff5cd93963ef4fbb38ba415aa9377a59471d95bc4b3c9986e80c92d77d1b97458c29e40e" + }, + { + "msg": "e4894c7009d533615e465b1a47ba59a0bf4c82cbc48843b6166740046dca27b9b983af09e5807fc6acfd6c", + "secret": "f3ac5bc39c6fcb6a06e468ce95c01370bf89f53ec3b3d754dbf9445d557176b0dc4752a458b2570f9c515b4e7fc985d8a105b00bd28bfe1f438ae0832e732baff94631846f4b745ef42a5fad2bab881568a4a29cf6632f2a17dfd791668ca71e666f5a1cb51f1213399c8197fdb631d2f977166e85acc2b1e4afe9190c41d9db5fd83e29d9eec336abc371ef87511a52801eb00587e4b4891049b2bd7cf3b160dc7d25b9838a8863ea3a0eebec7397514de93907854799bfeb508ccabe6c6419", + "signature": "533151b35349871991bc92ecda7ed46c9e938a36b131787168e7ba573edf8d25fb1574fdfb3607ed8c2fabf9d4427e5967ec2c14b3211f66e868b7aa5447960c" + }, + { + "msg": "1cb95296ec68adc4a8f1e0a1e790750eff5caa1878b1c3b03230f7998605d350bc7ebeae8f0fdfe083a7a75e", + "secret": "5364f25c6067d44dbfc527795ee2323dc03581b2f612b22454236862833fa33dccea9188bb31deb0cbdf7e550ada6d813cf6baf88bd5b5710351760104d5066814e30c15a78c8beb5d74c2295ff1017f630e9c04fe032b66388e0881793c2ef9693b9bf9ff2e30ed0dcc71e9998b08600c6c6e240c45c0ca14f7fe0545ce168cc431409bc1114715bc12629add84b2669da3aa03fd547c0f4755427d3e062a072006a6ae6d45268dc353a844c89215287dd665215c66c1bf5792d353f40c6b64", + "signature": "bc729413fa8be229dc3b0a82c4311f0a65c462cd3cb40b66b8e376f01a84dea193dbccffaf102a53aedebbee0505012e22cd0b2ad4df7c868a84b2a8f2943d0c" + }, + { + "msg": "486de8ffcb1496d7a0848535b66733ca6fec32dc318a3bed742877bb67cf482fe7d8c3a798deef7b1a856337bd", + "secret": "ac55d350d614cbe05f30fafd3f67e48b04ddb5361de4aac721488ae76e186c2af028c4b4ce6e997dec9dbffb66e289739a0a4e05db26519fa13f20b044615d74e7f665a815aebb81cd78f2dcee48cef23c5cf264dc41df8edab9e80f5c52261c1e3741ad8cdfd3256c5d4678aa853b936585e186b8dbc1f43dca9bdfff9811fd49589a1489c01269f6cedc55fdfb2bc192d2c7165703a8152d8e81fc5a4cb12a01edf66660d93ef9a904471c6e1af19e04958aaada6cf299a32fe98fe7b17071", + "signature": "8360ec7dba4771353ccf56fb2fd07680ef8b26f4ab3cf06c8d13d6fb1b46edde3a290832697f4b639660e3723ee4cbe0c7ca89673f7d905259cc2d406d6ad003" + }, + { + "msg": "387aa45863af7e6cb00ac445c16458401456e722744dc6b8f28c9eac0d372b5421daf52a61b3594cc3f212ca606b", + "secret": "4b8e9316c2a489526aef496b5e65de5f98f9117f7f7fef0fa0e125ea8ee0832c763da8108a928aea39ce3d9dd08ef6cfad823147eac1c72b95d5845e028569a7847af74bb9e239b0d941591a0ca814d75a7ad742d9804b93285581fa141b8db849ea11fad7e531599c6c34c3688608260b198cde0084037e7f625f6e349c527763b55b1ce3e7556cf1b0ff150c96b02a701eee21eba18745a26d9ba3d24a3d465e3468646f7b183c8df2dc62db2a3539152a2df24cc1a877f4ab7fd3e5b2fb2f", + "signature": "0eda03cc37e1da479d0e39b42683f455d7dea89113b1547da682121640c217a25e424a67a84d6afcef5c55c820ab3cc436e68ff447e619bb91c3aca3781bf603" + }, + { + "msg": "1098ec2a9b4c53757de09788b588aa9e4cc8b0c0bc201a287e98c933b1c1814eb75ceff13177f169685bf527863691", + "secret": "3ea317d8215d8aac9d60b0d5c4674ac3546c0e6a697d1e3ca417d04599a89aa892d379b6cdcef1c57a99991f62854f3b94a9f7837b9be3f9b9c10731804a0594a7ac3f89296c85b9293b16965574dadef97d481763d306090bfa4febb828e0c0783289468455124e813af3875e347d3252dfd884393665bf8977c777498b668d9d0c77190fe85a587155bb23bc479e33cae02c37088e9ac17dff8fb5432175ccb6262f9428cfb6cf2041a763d133b254f33fa3805a493cc8a9e3a09f950cd989", + "signature": "0da64030dce5a62c600ee0f6cf1c351d1d89bb25647c70d547897b7187718787c3816ff40c02eea713924fb30a741bb1370ee35d802b55819ad4c645f7017704" + }, + { + "msg": "c98fadb1d5ca93cf2c8e42af954cb059fe3799f77d850d7cd631ba9a367e9dbef0482fa6e0c21e3ff439d1a5ad465db6", + "secret": "b4a75ff8eb908d096f7383766f0f2818d2235108c24e736ee450877919a3686189b7b0269201430b2bd50334d6a8784f7d346aeb017e695c0877a5c76f16e4652fb8383bc5be757834abf7d2d394ec643babdbf57603e252f51d2c78af32dd0077a9f736d927f5df646eade8558308bae859f86979d53298c32b398b18eefacc7b2e725846f827dfdab4071cf79c1474b3a9e8010498cbff891c78fd27d2df4ad28c0984ba6a27c2d6187d314c1aec13773b5e91d3d78d379cbb7ea95f54636b", + "signature": "334220c39b4f834d206df406d2c665b15040f4e456029a82c6532491b714aed8aa833bf6ce7dd54214d24683461e363d743a9f52bee1b9dc266649455efbec06" + }, + { + "msg": "95510f10e9a15c261468bd3a4bd30d738844eeeafc10eb4b3f41255be4f666c37e43d0b6f1b8ed6ef2a8e124963ca06b63", + "secret": "17cee45611d1f0b86d0e758de69a256bb2bd84e7fca241006123b84a086bf18c1ebc58fc075b36e1b630c048d0a47b7ec4b75ed3dc4e2bca6cc7b8857cb183666a9eafb209faabb631221903f46c1d1ffd27e664474c1feb5f68e470f07e2eba8ce547a80843a6deed5db0ad97009c37684fa4e7e2a2646e67c156e5462f3a4d5e0391cdc02f4df15e53838b1ea39dea013ac01832ed5b082c414b3cf628be18be1d2360707ce78dc9611a12c4f5d7d58720ef00e5edbf83f6140fea47decb45", + "signature": "e705118a48a41e10271067295c9194a25a0dc0aea85813144ed4ae5ae42ad378ece5f2d317d1aacf4c776b118b54b8e3ac358b3371baa595e5227fc7f9987f05" + } +] \ No newline at end of file diff --git a/rust/tw_keypair/tests/ed25519_extended_cardano_tests.rs b/rust/tw_keypair/tests/ed25519_extended_cardano_tests.rs new file mode 100644 index 00000000000..818b5ef208c --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_extended_cardano_tests.rs @@ -0,0 +1,62 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; +use tw_encoding::hex; +use tw_hash::H512; +use tw_keypair::ed25519::cardano::ExtendedKeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const ED25519_EXTENDED_CARDANO_SIGN: &str = include_str!("ed25519_extended_cardano_sign.json"); +const ED25519_EXTENDED_CARDANO_PRIV_TO_PUB: &str = + include_str!("ed25519_extended_cardano_priv_to_pub.json"); + +#[derive(Deserialize)] +struct Ed255191ExtendedCardanoSignTest { + secret: String, + msg: String, + signature: H512, +} + +#[derive(Deserialize)] +struct Ed255191ExtendedCardanoPrivToPub { + secret: String, + public: String, +} + +#[test] +fn test_ed25519_extended_cardano_sign() { + let tests: Vec = + serde_json::from_str(ED25519_EXTENDED_CARDANO_SIGN).unwrap(); + for test in tests.into_iter() { + let msg = hex::decode(&test.msg).unwrap(); + let secret = hex::decode(&test.secret).unwrap(); + + let keypair = ExtendedKeyPair::try_from(secret.as_slice()).unwrap(); + let actual = keypair.sign(msg.clone()).unwrap(); + assert_eq!(actual.to_bytes(), test.signature); + + assert!(keypair.verify(actual, msg)); + } +} + +#[test] +fn test_ed25519_extended_cardano_priv_to_pub() { + let tests: Vec = + serde_json::from_str(ED25519_EXTENDED_CARDANO_PRIV_TO_PUB).unwrap(); + + for test in tests.into_iter() { + let secret = hex::decode(&test.secret).unwrap(); + + let keypair = ExtendedKeyPair::try_from(secret.as_slice()).unwrap(); + assert_eq!(keypair.private().to_zeroizing_vec().as_slice(), secret); + + let public = keypair.public(); + assert_eq!(hex::encode(public.to_vec(), false), test.public); + } +} diff --git a/rust/tw_keypair/tests/ed25519_priv_to_pub.json b/rust/tw_keypair/tests/ed25519_priv_to_pub.json new file mode 100644 index 00000000000..f0cdcdfff09 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_priv_to_pub.json @@ -0,0 +1,2002 @@ +[ + { + "public": "84f3555539fd7a0287c45f6f6273249a736b268955874ccdf6b4bcf52e2a20de", + "secret": "f400c297a83a759bfc7ff89a079c04ab755dc90a51e32db794386ff7e14f3495" + }, + { + "public": "5bf8e02201c8c08eea75616b1a9b26c0c254510aeff0f54385e5924ee6777bbc", + "secret": "aec5de8c25da6f177d4c8077912e06c793db1e23d62f37db39ca3738ebc0bb91" + }, + { + "public": "aadc3f989f965876e545bac9db582bbe7eb636e8dced276e6f4866fde438e8d2", + "secret": "2b30069121856a5f510e0bd539f5e459a03ad2885d2e9607a9a38eb12af18f06" + }, + { + "public": "d8121013f8852749a6fcfeef50312ba04e3f071e88b6b4462736c84238e029c1", + "secret": "35a6c3d0381e2e9cb38a7c21b912f4460e8e0144fb93cddecd5e76f9de63482a" + }, + { + "public": "c28fd3c20493169bb8bbb5d46a58e993f62d618b73e526bf9e05a26f30ffd0e4", + "secret": "c5edfc7ce883bd7e47e616d1c8588283023459d3f374c0986f75e768bfb849af" + }, + { + "public": "000d5f5a70a9f4b47d35167036d7e17d2e383287949eab3bce1d29fa107f180f", + "secret": "22eddd4711bbdaa2fd5329bba15d846db3b666b7d9d846277d362eb9e4551b4e" + }, + { + "public": "7fc7d2f0246c036058bee1b6e6f43f47cca9a3eb3197da70f04fe27903644623", + "secret": "4d8c3c1816ec3ea58e7c6b6e10a337a69d75aa5dc48f6b0011d2258bd3c2523b" + }, + { + "public": "5eeab191591df74d2111ad3aa5a79e70a2ea74cfae39bc182b72bad60d26b7d9", + "secret": "418585aece0df8119481adf9920775b1610a0ab52c9fae3f041e12653adb0d90" + }, + { + "public": "74216e14110b646383b6477351645d6f0a97ac13d829dd0f689868669a110686", + "secret": "9d2f692df35683e94dde273fbf56a06ba76ecaf3b10c09fdeb8f9bdb0cc060c6" + }, + { + "public": "7519d8de67fb297d881723958283c690a67af1ac6add4b21503b95b90dae19ec", + "secret": "527f9f6463a3d0b9a6c6b5b48d2909353766dc13423b685fe85c3d54377b734f" + }, + { + "public": "ad1a584b9e7664f05582c130385a97d5ac73388643fa73150f0c99e288c37db7", + "secret": "5cb52219b1edbad73b2df614189267ecc41fa770ccd6bcc57b5b7b7809ef6701" + }, + { + "public": "0b47ea97fccbc101ffab8f2b720dd1df5f8b4b91dbdcdf5ad35c1a7d3ca619df", + "secret": "9777eca69734023424c7ea8b21d9ae4159697fd8b5f40fdf3dc1434f4fbe3830" + }, + { + "public": "d6d3058b0f53e0be3309213cf56522c439a6ba416d45a5f683802fd8d2ac7bd4", + "secret": "31be1707207b0a7c3025790bf950d686173479ed7dad7630c2dc132cfdcfa616" + }, + { + "public": "4caf3948eed55c36f764529567d3cbb9e21d3f2bb29e83694be371044a2857a7", + "secret": "dff344b72d7455d225ddc7433d37ae9ba1b128877c00aad6b68cdbf15bbfc71c" + }, + { + "public": "f7c607ae3f486f506573352c91bb9ec5839245bea2625dbf313f842a95aec569", + "secret": "fbaa0f3546b4871cd03ca1d95a1665b954b668d1854b734272cf4f6166695eaf" + }, + { + "public": "21f01adb3b4b9dd201ff74a255e606bff1899ef51d49ff44bd8437b79b2ec642", + "secret": "951702b80e35c6d37f80da18eb4b643857922f88291adb7161f26789f3eb2229" + }, + { + "public": "90c23fae5aa1e8618d73227031b6e19b053f166c5bc4a2c8321f6f9d464586ce", + "secret": "514cee2dea9c882080ae6e3a21cb0ac5b69b73c45c7eca59ed6cbef9e62facde" + }, + { + "public": "27a4e0c809205a8da2a88493de35fb7d4375fb7a78ce5cba9b80c70705d831b5", + "secret": "862f65e095c7dca826c15e3e5895465b7baf656f004fb707bc4e016246385b4a" + }, + { + "public": "e9bc0d472181d424c13f1edbfd2d6f1f52ad49ddc573627e7acb4c2de6bbdeed", + "secret": "7a0a37c98b89b92a640b85a094f37380e18a8b4d861305e1c25ea3e652254e56" + }, + { + "public": "bdacb49fcd1c3f6fadada5a08d283ad710c72215ab33ce444ec15304634e1e1a", + "secret": "68c3fdc1ed94cd9a00442245893aadd614afb2e8583124034779c86126cfbef5" + }, + { + "public": "efe1d4fef1f66242b0eee77287785bbb7cf3661c3139d65264b8ebc87376c9f5", + "secret": "1d5f19e050e9801d1ee1752deea21cf790c0ed518f5b4dd4eed18090e1faa063" + }, + { + "public": "b2c87b7d663689a9e90d9de4314c17bb824af893167e596494e62953ab4f1ff0", + "secret": "4239ac869f7cd0421d611912d3afbd4a5bb0edd6fd77e0e2eb0e7c83aedbfb39" + }, + { + "public": "4d36143eb4b5f244e099431078ada5256898b12269ebdeecd42f9158f1faa3c6", + "secret": "fe895bff5f431941abf6427d5ae03968c5a6bdfb49d8c802e729e06d5f508b13" + }, + { + "public": "b2b7140c309f2eae5f4462f650ced07c07aa830f1b422c9b9f0fbab8ca445242", + "secret": "d7107c5878b34bbaecf7a1b3059a7da5618c4c26eccb39bab445894b7587ef4c" + }, + { + "public": "ab87d8de2556d13567832f413ba5af5314a8cebfa8973ec5fa14b4170abd6ce9", + "secret": "4b7cfc999c4784fa894e685133f83a2beb48e058bbea3fa23f15b16e98dca2d0" + }, + { + "public": "4b6797afb41a78e00e90a6eca1eb7ad0fcbffa61119150985c62e1fe0f2e0f8a", + "secret": "973021d2dc1c8949a397d05ac99caf8d745c721c054b9d59fb2252101bf6263e" + }, + { + "public": "27acc7bfdd9beb9e37af70121893d9bca4669b79a2eb6d9aeb9cbe30c693019b", + "secret": "a86fe317f2087969cf622b87f3294d7a312340a0682989d4a8e1453eaae11a6b" + }, + { + "public": "a193742ec16ccfcce9da1b2daf0827ea5e413b187944c3a346f426348c46a044", + "secret": "5bfa98563cc3eaf39c40806f271a0e8605683e35a9eb0e9f8f3d9e98657bdba0" + }, + { + "public": "93c9fa3727c88a9fd64961642430c368afa8aa5212dce0e1781c1893ae72412c", + "secret": "a2779bb8a177aeffa87f1e507e0908c99f2f12d8236e3fa08f30f4f52caa5e7b" + }, + { + "public": "0d3707fd6dc6f1e8e187b88a6f3ccfcd7bfc2464688a974f7034e1d24d67752a", + "secret": "896ffcb173d3abc1bae5ebd86a119fb996172cf9a70f46f9f0e0a6462f96147b" + }, + { + "public": "3111251e9167235fc781522c914d79098d6df14112098e60c2660c0d7cc63c39", + "secret": "5f5528c40976dcd9406860812bd58901b219767763d4646e3a0c987d1031f714" + }, + { + "public": "acc678dfa68cb3ee0c726f917b4465fd15000d45c240c410de8be36b0364965c", + "secret": "90768a5a1c5689965dbd38bb3c15f67a2f32b9b206a87a2aeed3323830727807" + }, + { + "public": "f037e2b145ac7dfc26c191a451dcb4955fe59777db404a807ed9196bbb2c5f09", + "secret": "a43824ba5cc60fcec62c72d7dd10bded98e6f8e0cc2f8b75ace7e2f948964851" + }, + { + "public": "860e0b15de1e8aa43e69ad7216e9764f32ef72798ad1b5dc65b8558a28ae336d", + "secret": "1c5e55460c753dbf1a5ddc5991fe6a5afc80141f5a0c4583e6aa764d45ec705a" + }, + { + "public": "f390028b17f26a896fedf1a8bd8d12af67814f979b5d3adf662e666d236c7700", + "secret": "8043d6c98be18e5dfac47df5703b475bdb6ee429f6abd7e5c55e0327576f11e4" + }, + { + "public": "72033bd969c6fd459944930ddb89c940cffa6153cef9ca0b0f05ebcb9f06ce04", + "secret": "55daf915e823ad4e8b564d9d54db970149fcba7acabc4e449bde3da8cdad7ce3" + }, + { + "public": "68dd695c21397c1c76bdb8a1b9469be6310ec0e550cd99884cf48307c59c2098", + "secret": "b0598f4bfdc623290045ba9edbed613b54dff424495e37caacc1c9f513359196" + }, + { + "public": "4d0cca88b31ca19cb380499062993f968ef7ad7c17d7db31204eb407b2f55a3c", + "secret": "eb8a8dd07904496e59ba2106a699808b8256ec15c04977563729911a21d66001" + }, + { + "public": "d1ea442550480cde96282c934f010defc0f5a774b2ca0c4abbde5f24827b5d10", + "secret": "958c2cc0627b8df283e5b75936c717c87f19dfec3bad5d7702ed7413e16266dd" + }, + { + "public": "635222508ac740a545ae3a2e1b531ae834dbc3b9ba2fb69a675e2835fac09e59", + "secret": "cb82a5aa6f02921cfe5997a7d8fbb9ac04d0123026ea02080461656283936fd0" + }, + { + "public": "36ffc1a12dbe8d3b67c1285c33c1a60e0ef4feed15b23d5ece1c23f08997d381", + "secret": "ad3b1870bb1420b869740ccf8b5d872b9da79072c0dbcf637cff2fb12405668c" + }, + { + "public": "a88b824107cbb11922171d1acc9018a432185c4a3e9c629c7ecf7ed0453751e1", + "secret": "fdb260e4086296c76b8dc27db3f844d67eff0e2c482b6fc4988b08db364f2b56" + }, + { + "public": "a0b7d6353da9a7a176e0513fdbe428a7a08965141384f4210119be5e2de35cac", + "secret": "0abcc2ca67f3cf3bba047e5956b54db7c887fa546f039aa37b2b5f677029adfa" + }, + { + "public": "5858d39e19433849c95be07fe9273b683c56198ba6e396f7b549a98ea8aaea4c", + "secret": "29527782410ce88624e2ceffec8dbdcfc135e04b228406a127d94968aa146858" + }, + { + "public": "31ddfcda0ab66ba5d515238e2bd0cea57b73e9d36f9996e5b9671a59e66fb47a", + "secret": "9cde91a16cc82527b108304b9a45563f524d1ea1ff8c6db0849ebacecca9cf20" + }, + { + "public": "64f1704e9a0d3526d1812df2cd90e7e9b54960d7389873eec48884d1a9c4fdbc", + "secret": "beb922414cbd021a4df2e9ae5635f37c96139dd9fdcdbe03042167aac21d6f7e" + }, + { + "public": "992faed57684e050c325598f46bfe8c00521d61c1bfd1fec2a44a706105c16da", + "secret": "e40b9a5c9c6157fd55219a521ba076fdaba65db61bda660c03fc924423a0f94c" + }, + { + "public": "309ee14771171e677d5e240d88bcb9d1c80920a0ad048aee25878f98a5075d05", + "secret": "98dc24e2cde873a8f3cfe3723191deea7f69b06f57879ef196154c7e350625ac" + }, + { + "public": "dd228455a96281ee87e8d37a74afa232ea2652dde5b8edf0e60584b26a792b30", + "secret": "74a1eb437e170c0b5421bc0caa5919a2ffd5b636586f56ce749d17f2aba827dc" + }, + { + "public": "2767c2f30b66791a352e168e85b900fc4322f6ff962c269ea61d38c13a5d2605", + "secret": "aa88a461c6231ec22f5d81e820d5f8966216a30670e54c92cf7701271be488a7" + }, + { + "public": "b6ce0609ac4f3414193a3737621358b005c0271ab1f30a5144cd919f122a238b", + "secret": "d029db3e85761b79d76782dd5a206461b7632bebc4d1bf1807dad95670dc476c" + }, + { + "public": "ee0a91723535b1e665f3622e85481e58fc0f2694944f3af03f9ddf73490cdff1", + "secret": "f806e9b7ed27e629a08ad81857e45edd29437f45e7371b0015165550dbc3445e" + }, + { + "public": "210e1696b11739debe4e0d2a67c70eb9466f423dcb7bdf263a565a3fe5fd7db4", + "secret": "5d9875d708b104b55d4d00bca0c3d62eac4aaf81686c71d4e4e9886f120ea0b1" + }, + { + "public": "205a1476a451baaf356e3d1749b1b965d2bc12039ed9594e1b0b52d75c52d562", + "secret": "b80e4082a35e55474fc925d2bde375c7c02e6c8c5d1e9656520526da75d8cedf" + }, + { + "public": "824a592ec3ad92c21e06745adebe8d9788cf5751c98177b8ef842f9e4d151968", + "secret": "f904158d81fd1c5c14de708b92041ad611ea323e5400d6928234b3a919251121" + }, + { + "public": "d92420a8118b141ff942b04791e6feb156aee7de9afa8b2ba7d55a0d39d31650", + "secret": "835f1e9898f4f6ef8933f91e25d3c58df24f04045d9e40626caed09b813e37c4" + }, + { + "public": "8a18aecd24e50be11d9a71cc0d0ca5a7a0acc15035cf4bb4fb17f4f6a8104761", + "secret": "fd8e3072758d683f4d704892aff1213a70d2f533ba9414e47aa11670fe4aa934" + }, + { + "public": "ad13b24afc50c5de27ddd0ec44a5f05f437da4cc7beff60755b48fe294e0683c", + "secret": "d5ef06ff7c86a0fe26744480474af9ed23a41b946c28e8997649139f959a4822" + }, + { + "public": "ea616e17a8b7305ace349db45f22dbefef27cd9f8d22a715e7ac526230a4021f", + "secret": "84f39e92aeabba8dfebbc75aeb679fe64e054c542728c158306c60b7304e154d" + }, + { + "public": "f65fdeaeace9bde8f7ebc87ee55af205309eb8add9a1ce71ff5f04d6bfda05f0", + "secret": "03d89f00868c68efc8d6366deda83f1b2e88eabba91e693797c3e72a8f88f48c" + }, + { + "public": "51a5b62bf0bfb06dc3d1c80d585f8b7cd5cf08369777bcd3c2d3501de2a3e9c2", + "secret": "a3f7274b4e581f5046c9f7e2d4c6ff6df95b5cb158e874a9b7e27810a8c9f239" + }, + { + "public": "30fb3953c75933098dd457f23e131dcec1790bdcd14d3756c7bbf7010d4398cb", + "secret": "fe2da0666f35787c11f0a13f560828a31813ce0c693ce1f94536556bd24064af" + }, + { + "public": "08c61d07885bb0b40012c1055140a61fcd793c2f28621586e88d77241872c2d0", + "secret": "979c6563227739b56351cc34dd726f6ff5288ea1cbd2c62b970d3408c41c2e64" + }, + { + "public": "8fb74969499cf2b2e45e835cd15f465e347b60bb63b4ea3884353254e8795cac", + "secret": "552c0bb73536e51ba48dd06a70a367b1b4aea90f0ea9a7cecfa8871101d7a048" + }, + { + "public": "7c6d8a910269a1adb45ab69073597c04486adb033c4129e1f65d8ec85dd5f40b", + "secret": "d2f2130652e5aae6140d27f05c19cde4f157ed5740c5d41342846365bb6057cd" + }, + { + "public": "55d54e1e3f2f6f143b68e0342d70812a41c143a1ff221494741b364adecca4cf", + "secret": "acc98122a7bd03146c971a6f42d2637e1df9a2719955917c218cedb8ca156a67" + }, + { + "public": "f2417acc7db550e45419d1e7bcbc64e159fe74a388d0cba0b3b9db7147506f6e", + "secret": "ee683019e064e95379f391aa1a8c3bb03af0238d57f86d1669b8df334e962bdd" + }, + { + "public": "0d006ab03e70a5921f8c62799ed5e690a0034ee6bc07e50df0a135eb846a1d6d", + "secret": "434868143c2fbd71e3e06dc1d71883766442d6382da6a24791fdf329a0ad9b72" + }, + { + "public": "4d03079cc959e97f55c250ca80cef3973c307852bb22a9930668298350cbaccf", + "secret": "5874231e3a6e0a3ca29acfbc4d8b88fad5f15566412e9285a96a47bed9acaa50" + }, + { + "public": "7ed28b2e6897e0246bab7ccf771e1cbc2dd716d94eda2c70c48c537239a0866b", + "secret": "bbbc68b2b2d564c0a713566e331757f5fa3d08563431279892f5ae4ac8f64387" + }, + { + "public": "78e0a77470d5b61aee66eff5b831fd5f6b4eecc4a895928ffcebb1229b5c7dd0", + "secret": "47f979075df1bf0b944e827cb5d9b4718853dfae4eb7a9cd0fa228022f09f8e3" + }, + { + "public": "6c9d6d3442c6a5982ffd9b96b2ec828ee85191c95b7b773e1b67f3e1857e8925", + "secret": "9ea903004bccf379b819177e6ca56779817340f8957ef6f9403300047f2aa822" + }, + { + "public": "b026903d283d251236046643e695b782f3daf9a4d7bbea4b007567a2c2ceea6d", + "secret": "00512e0f6b6f4f8d7674407de53fa6b1e307412c4b36cd2594a2f15583707f3f" + }, + { + "public": "f6584c29a31812d35b180156acb3e35b94721ee9aec71fac524dda3927f90848", + "secret": "113a58cb3ee6e951f65d3cddc4ecf79b83eb7252ea3d270f841d66cccd774f73" + }, + { + "public": "c12c0ff07322ab294966b064361efe71772a3c90153f4a5fbcb0c76276074c94", + "secret": "d82bfd183553b32723ad988b0f00cf258bd78e29aabe38508cf3144c4cada63c" + }, + { + "public": "9535384e1f5ac1e1bb9195ba7b1d42d0a1c65772b87aa7c81f526ab7699336f6", + "secret": "4315f753db1eee9158c4d403a62b3f2de5b56783b88ec99acb2a96e501d5d791" + }, + { + "public": "86ea8574b55ac5e5449feec55bc625ab51b709324c6c1d873818f12026535af3", + "secret": "37d346987531c9580239c039adf1e5e3d41090e9a8eff8c6f2088df23507edba" + }, + { + "public": "5e35cb913be094450566058fe5c5a61b181e9c2a0892e1bd2d831c93ef470b21", + "secret": "73c6689863032902f456ed9854750085798b4ee963dacb34219bd6b8307ebe84" + }, + { + "public": "48e3d1fa5ddd4ef7df94475e0d2696fbeb5018fc5b3527f8f0347c3159a0c962", + "secret": "894825346dd6202a48475157967c5406d1bf0dc3400e1caee006e01a76c04566" + }, + { + "public": "37c316dc53458dab34d6a54b21b7a9c82f783a614e2905b8d5cda5a426f4afcc", + "secret": "d61efde38f048ad5d634daec05b4a6c58988a3c32c4e380d096c181b0229e818" + }, + { + "public": "d689984485436dcb7a159286a7d934beb706faeeb088e2e016af5c70cecc0a51", + "secret": "9ee3258f7525a6f812e754208571cb3adcd096679f8d8ab1230de74ac7b5b7c3" + }, + { + "public": "1a74de65f689599432202dd28d7a1bccc0131629f61ffba16ac3c915beb3a201", + "secret": "15c58cffd7409065d7bfc9bcc42eda2dcf184e64ad799df3da81260961388558" + }, + { + "public": "077660db09fe8f5633cd5259b2f0501750bf4afbe6fa690216da69c20b6c3b71", + "secret": "45f0e40b32cec47dc43ee1c62a5f490a1e2b73ee580ab37c6c769cd8282ac707" + }, + { + "public": "afddd125f720d9eea185a52a0d8f182b571d2a5930763079c7501fa6cfe79b8c", + "secret": "1d79ecd13af75bdf98e86bdf3ae8b40d93efe3e2921b440981b837d85530ae61" + }, + { + "public": "b06975ecf22d4819d445f62793cefd36f794f5499c47dfc11d9d6a3718ed98f7", + "secret": "42ee2757b2f9105e7b7bdcb0593be3cea4fa6cb2a91952663d732f17e5e60c40" + }, + { + "public": "ba30e3fc9b02f222413e6d3d86f6e4d2c2aeb5595ad1ce9820453d955a390bee", + "secret": "4ae24666ee1c7eda9c262fc554bece6956eb733a3024a2992247703fe21ccbf9" + }, + { + "public": "22c4d789d8999b6a0b25b39d0bee3d201ed50c52483f0683517640d7205a2f73", + "secret": "822f1047ff121976e5fea52605a0f9a94294106090b7b81319a93e546fc27edf" + }, + { + "public": "3a77674342e4b72694fb35c1467ace0ccc83220ca49971fe2f56b197faa0ecb9", + "secret": "2038010528766ffa20c27d4ceac7d74b5e496670a14049ace05f4e831f7dcd7c" + }, + { + "public": "89e74aa5387de4ab4639885233fc1c5eba82c986f41b0249680a1b31755fed08", + "secret": "7c222c2d5e36cce49e21b52ddd36ff73889318846f2f69096c9be2ce5680952b" + }, + { + "public": "7c0c6b57a30316f325cf0846593923961dbc6e6336ae2e9003dd2260a8ccb834", + "secret": "cd5a74e9e861f62e05f447bd8342efdda403ce0c5047101c2ba89f70c21808c7" + }, + { + "public": "5f8734cac055b3174b720f5fb0488e95bb8d30c914c53d2e9c7cbc359e78cc9d", + "secret": "46304ad4c56bc7fc7a4a55cbcd7677fcd5dfd968a61ef2d1f1f7b13e517a1feb" + }, + { + "public": "5d6c0447eb19b81e9fb8293911ae6f910a75939e70e2b800b51233930cdeaa99", + "secret": "ff314af9ebb0fa59c0f8cb0670da269247ae793c79402b1f1a6f4b722c34155e" + }, + { + "public": "2945b1e68ec9a9a129e7fa40a70ab3168cbc74d51d533d41bf09e889c87550cb", + "secret": "6327be47b7f2ab7f3e25d84b9b8cb0d173509d355760fd52c0c5de14d7027d6a" + }, + { + "public": "3eb1832356a0eec3cf4da7a94f5a132789045edbb119d674434985c9f6d40462", + "secret": "32cb8055716b97082dc0208c7596159b8394bcf32659d61d742bfb0ffb4865d6" + }, + { + "public": "6da67c23d20e87434830fdad59d9bab8b296684eaf69d82f68a3efe170d4f714", + "secret": "78994a0fae2bb2c2ad8ab795b34fe139c1aa00532593d07ca25e29002d51e868" + }, + { + "public": "8a6bd5ccd2d01920b72f83cab25e9c6dfc591aa607b3672c93faf79b0c176af6", + "secret": "8fc6c4f0d7772a1361dbc2fb02be22e8507311b63431ec2799106043a81b01a2" + }, + { + "public": "8ceabfe70b487633bdcd4ab3cde7129bc193bc32f699911f10f1ef9ed393bc86", + "secret": "186c826e2581f0d5932094f1211bcbe43532976ade9200e8f4d5694582f0a054" + }, + { + "public": "1a8f79d3e38c3c91a67ca4959f84b6f1416cb0667bb7128f81c9191baa9de0bc", + "secret": "4e80a45f57b66608951dcb9a18d0c31ba8ba8c691904b58b26b040646e7eb8ce" + }, + { + "public": "2d9aa629d138396fdd85cdfbf54fa9a03d1d4d9c7362b8d5dc8f613acd6a1e08", + "secret": "eb51c594fc1e3e2108ae0e1c35d7f2515f2f77377e3f05a9bd19c7b73ac1076f" + }, + { + "public": "f5a2699aced98ca20675a386a3b23c51baea941d6e73588e5e9cd8be55252101", + "secret": "6ea021cdb91a051562d909de3a626878363758058fcf4b088a857a6b54bc08c2" + }, + { + "public": "983f5486af410b230abe73d3ff30bb6a61aa4a9c7fd0f5ecd88ec731734adfd8", + "secret": "67d2b516bbb0b74530d1fc42b7f647c5c65d08617274c0172352aa7570cc6598" + }, + { + "public": "a763d6977e93aa1fdd522cf529153f98be4e92a5627f7ddf93a777889cda9764", + "secret": "95008e91e514ec3270a915d7e4193569a0f60fd7c879781b9a3de8fb70a63840" + }, + { + "public": "67a1527c699de15c96a6d3ebda2f26df275d70da91167b341a3b90672703ad65", + "secret": "4d81db4fe699e8af2424b8de0aa3978d3a1e79852d26127451ce528d8eeb2b34" + }, + { + "public": "c3339e06699f44b8f7557da81b119720da94d640a05223e44bbeec639de357f8", + "secret": "852090448cc70d5f85b20ad418b4ad3fa9b09be3b53f115bc3827035e6888369" + }, + { + "public": "41086c085f17961e30ca951c5f0b84aa12320ce216919e7a7c8e0f03e4251d84", + "secret": "e2bdb1cf7e6c70cdeb3cca86db1c1469ab9afc9f325d2db8264d66bec54190e7" + }, + { + "public": "19b1627dfd4e1ecf7692cc39899c32bf1f4ec245194053b30326eb1f94bbfa8c", + "secret": "b742739b3f71e1a2fab131ab45f60d66c761b10219c8ed1b390fab37b2482b59" + }, + { + "public": "53abfcc91bbc7814bcc959cb0e6d756e2bfa2161bac411d34b9577bb471d1756", + "secret": "5e0ce24dd053515dbeaa1878b44f42236dd473b9eb9e63d86dcb2d9460c45341" + }, + { + "public": "ee95fbab3ec78caec349e5e643b7f2ed332af01626bdbe47f59ccb67ff315593", + "secret": "adeb85ac33201e21a3984656b7be9e01bd23ab9abb4acbcd6e7638b11202b689" + }, + { + "public": "57850e5c08687662693942c6d421182e769f453216f6a21a3deb46d72aee0530", + "secret": "625a4f14479b8be40d9ab08edad23c18b55d23c1d0ae4fc93751a20c110e8c47" + }, + { + "public": "94c7e9bcad48d4d7c9af0658011672c187e3bffabc0c1e8bb295900cebf3aa42", + "secret": "4001db50c3a9affee761f2bf2a5915e80797ea80da2f6de7ad9f40d650bc6542" + }, + { + "public": "7bf756c31296975924daba47fb5c112452f6e1ce502bca9f4e2438a0189beee2", + "secret": "637bc973557303ee32e585fd420828f4850307c7db0a608aa76db2b0e44d95ad" + }, + { + "public": "e6c82e8618f5cbb96a28723d4fb93cebc98d31166bd099b0c81d36bd52709df0", + "secret": "e6c3824eb48e58544f93e09a7eb21f2185680b6067d336b9f720f07402a0f07b" + }, + { + "public": "b52d61b8c8bf194a583d2d5483c36a3829a7c415190fe8a17719db1dba895634", + "secret": "dd001fcbc596f38af088cc7ed6859b36f1b423e1c9b8803e5a47b3ef6f5d7c94" + }, + { + "public": "96f33b90b1ad6dd7e67e26fd8830c48e4cf055b1cf73c02d90114256d2a76650", + "secret": "e3b75abd96734abb36e8631e0004369c05a2ef21e5e2b3064466e1be376c22c5" + }, + { + "public": "87da366d523c91a072c0ad288fab3981499c0c54cf5ce5304e76f30393d6f8ef", + "secret": "6f01fe42df2001e5478d542c8fb58b1d83a91a716faa9c5cf709d3796f3fb0fc" + }, + { + "public": "7bd1b46df87e8dd298cf983bfc0d0a59ea69883cbb319f6f86342349cc6ce83c", + "secret": "40aa01a315ca34decfd4ee6aea159e28ee2f9df33c0109a8f10b93762e9e38fa" + }, + { + "public": "43d704b0f1e3c8c773d36b589366e7e535fbd3767431e8cfe9ae90177e857506", + "secret": "75369e2d4cebe8baeeef67f2c45945f70e9d6b6b2789e9ff68f18c41c0c39aec" + }, + { + "public": "057ae7396ad478e798807498a283c38f7dbecf319fdef2fcb3e3c439ee2f54ea", + "secret": "1ea015f99d700ffa4322def1cf7528ec5395eb7c27e3bb000e34a2f4d248d14d" + }, + { + "public": "591a0960727c16ae7c5a5fca343cb795555c2c44463c59f2dc05830478a6c51e", + "secret": "51bc74a870c2e9da8461fd4dd2d89222e5f97f29bcb414b4e0e9fc705fb5a48c" + }, + { + "public": "9b5417d424302f973dcda1db9e35012c688f93af4440a0c6260b7f8894566de9", + "secret": "54b30150cc1d8a7a02e443bc0b5df80a8defad0d8d35d04d77d3bc3f948469f3" + }, + { + "public": "c86278b1e4e768e1ba0accadee1932e55cf49fd7efdaed521ce8cbfe1a341a10", + "secret": "d17e4fd6be883dcb59a5de8681758459ac38a0aa1a14a4b2a1b21fd417512785" + }, + { + "public": "0abf4a96c3ec75bd72734045f6b8c5431231a271166a09d7797cfede60993f39", + "secret": "f230791d9fa71b25a63faa0b184921187196ab6766b08e86e7df67bdd5f06349" + }, + { + "public": "048250b263aff93aea4c3808c140f443cff094bb7fc8f9684b2b48951ebb8cb5", + "secret": "12cfe01fea4c12d67bf265f5230281751a731811cfe13d4cc0988221aa03227a" + }, + { + "public": "ddc95ffba741677cc3d4f7f8d8fac35985356a90591695ae198b5fbbb0b4f727", + "secret": "28685af2dc1ffcdd2fc79e38660d162d5a5e5cb4d80cc2cb34952ce97e190534" + }, + { + "public": "0fce9d961492e624ca2d4cc2d576a17325cae409ae21970e3a7b74dcdec94614", + "secret": "e2a3da34ddd8ce1a6bf86263cab4717d190dde5a4f19d29790d6a1e075fb6cdb" + }, + { + "public": "ef19db113a4277575dfc95bb3ed188e6acefe3d4c88fa7e5236313bbe937152e", + "secret": "c7e6325e02350efb0c9d7eaefcc452020574d955844d35f4d78b37891942010a" + }, + { + "public": "5b802bd732bf0ed0d98cfb32ec40ca4631997a66e75b2e160f2207f9748e12a6", + "secret": "8770b482a5bb6d2a031efc9de6d262f44cb1029a3307dfa1376f5408ba06f3a6" + }, + { + "public": "120e33eb146b2f923db3f189b762ef0f4d9140d488e963347147c5578d31732d", + "secret": "75986569e290131acbf0fddc8a3b5f43ff641aff4810e91191612e189f0bb974" + }, + { + "public": "acad50c1996590abb86142326fafc550cd9acd9c4f4770e44aebf5bff37002ef", + "secret": "04c96b627e91e5aa137f22d8ab05f05bd5a4a366a00bba579d34f71436fa8c5d" + }, + { + "public": "aa3869cfb327cdc86d500c54e9fa8a6291cf3524cb17f5db75336120811112c4", + "secret": "284d5f59be2810274296b1b7f7a04bcb745b23993617fd923537c7753ce3bef7" + }, + { + "public": "400fc613204daf2ab083c0f16e6b24f4f862be3d4f5912d2662a7d5cbd2d6a61", + "secret": "21c72972cd929c4cf8e5f24c0cf043b2903646eec04ef986d6a73dbf1a152ea0" + }, + { + "public": "a2cbad8ed72f4f450e738ac4893cc40980f1a076a9b63bea78d10202b2743214", + "secret": "c2119be02acfc779cdf76ad5a820e433878c5bdbf5573233cd2689c18546cca8" + }, + { + "public": "00e31d97420b0da03e7d510dc20c7fa539cb8ad31f81f01eb8da2f06e8662bda", + "secret": "7b060d03dd03ab52e268dab04ce9dd3e5a31351d11d8e841e3beee562a4c15ae" + }, + { + "public": "2f2c64df73fb8c21e21f6866fa46283d6d48e1d6284b20f8c8b9434e7d05f50c", + "secret": "ed230e2ced38caaac4fae98075d35cb0bb8959daa2c371ebc2f42342e439cad0" + }, + { + "public": "824bdcb6f88c35a124a7074ce5790961c47245a78e07342450a8588378a4f5eb", + "secret": "0e78a90e210e18870c21f057d253178375a182a40d2452934ee8cfdcfbc9afb6" + }, + { + "public": "d793349ef0b5bc0404658706c014fffe49256dd83bd297ffda10e7b0dc650050", + "secret": "33ca66d1db746f709fd7a5538f1045d0633ea41e8a007fe31cb6d4bb2a595f0f" + }, + { + "public": "d514f360d2060a08fcc5b9f185ef77df2eb0e677cd1d3a509ac811f286b11870", + "secret": "a99d1cab98448ed7ff1754c4eee2272de5585446746841620ac6b9870929285d" + }, + { + "public": "78d778406068d98e8c2e726151e0d834cdace5e10dd1bbd7e37690a29fd51041", + "secret": "a4309c020262f11016cea49a6abb80e1f3c98580862676698a4366e7ffc14e4a" + }, + { + "public": "6ba2ae3a8f27f1f7d2ad927825f1db4d71846534d679d4035ee1cfc81b21e61b", + "secret": "9cae8aeef9a0ef5775e4682c0a544545f0ee7835d71addb5394a43097feb4627" + }, + { + "public": "ad6f9324f571dd3f3ee5565b48fd8a1118e1ca437881794f8606974e5457d810", + "secret": "7579fd8c239d03e7cf2d26fc032058c11f4a2f599e5a1f134a2fb5fa6af5c0e3" + }, + { + "public": "85f88b42183e14d07a049c7d172258741a96376e648b2b44f5721348ba798d7e", + "secret": "5c1a94f456e5213c34edf44278992100d82568ff875f5a2f9ed94abce96fde0c" + }, + { + "public": "0e5e25e9a46f00a3bf91e4a2162bf2c36e83a621d83297fc01ec1c419017d2ba", + "secret": "eaa714fcc14e804968e748c98883262d0d899032dd9579be6cb1a3f1f1324eca" + }, + { + "public": "36bf532425a003d1720b39353094c9d7bd1c6592a7a64d4774fb3793fedb36d2", + "secret": "a976add6afb11fb96788fd8431d1d973bb8a02dc211942b0192f360407d7c60a" + }, + { + "public": "634d9f2fe5a7086f6b498e1a5e0569a9dd05e93ea6923555960568f51599bdcf", + "secret": "0249977a3d9e61e27990dc69d2042e08dc1684d642abbe0f2d7a5e441d127d2c" + }, + { + "public": "a6f61540bb2516934c275e8c9c35fba7cf2db95aeb98052a647def13dfeb1789", + "secret": "0b46f03c5230ba7001b10da617ef00f52d4eaa30b207fb9d3cd9085587e29cb4" + }, + { + "public": "9fc37e05528017d01838a199f51789dace693de7696eab23b288bd64dba15b60", + "secret": "f25953d06d2a442a5b9bbbd33f58ef76bdec8af43f9d17f33589eb30ff99c062" + }, + { + "public": "25cf2ba3a63568b67b008ca939768f77c4e6620a3dc9030ce76f5057f77084ae", + "secret": "f32f49b1ab1f6770d393e035dfa4625400b9ca17d25d98c64607927ef70840e1" + }, + { + "public": "3a887e859deaaaacfc9ff7034ac8c235dd76dabc12d08e37a281a4d115f03f5c", + "secret": "469b215a43147de7b3a7715d78a3a3c1a2c407b7c3b822130104cfdae02dc39e" + }, + { + "public": "1a3b2a6b43566639914a38d7bc95a557e999acb2d03202f2c3d4f7fd8fa93a23", + "secret": "e2468f03021978cb814568b515f294558bf124e261168c32a5925baf214939eb" + }, + { + "public": "558384341aee91cf9b3ef0488928f747dcb167e63222700f0fccca4fc2e7eb8c", + "secret": "23370a4877fb453aa48ab833653e8bf27e6a974f67267b71d752294ab459c762" + }, + { + "public": "c2fcb49b7730b577b08238a42407327436bb5ff1812602c6439f187708b46d1f", + "secret": "b5f5065f3e6e657d8e7ae8f5a7cbbb7129552741d0c435d141d68c881da3af68" + }, + { + "public": "ae7a555cf333e642d303fbdbf03815123ecd6705d97811d3e4e674efc8343846", + "secret": "d1d62631d5728bc22c631cbce44b47a29df07757e59760a13723bb940b088422" + }, + { + "public": "4def94818656a8c080dabd06a72a3741ab4a4e716d1ed5a6625526ad5a266f53", + "secret": "e4452c65ac06aa06ceea30eff7a6db8276e809dfc0d3c5453cfab9dc301396d9" + }, + { + "public": "97a805d3b12649acbf9c1db69ed91e7f20b669cb917200826e91d1ee3d2bf47f", + "secret": "eda240f74f23d56ec1cb3e0158ec94f19eeb86e6050f751b0c4a32c2ad4f8234" + }, + { + "public": "447d1d75b850c2c7f02058ef38c868ffe494df3f2fb00fd67eb7f38ba7d776f5", + "secret": "e183b14c9b6c3250c7ae892c21513bde73819443deff2c72f81cbd71a5c8a4f4" + }, + { + "public": "f01ff315c36ba40d32616da88659aca0c43c52aa69cb9b4a1cce2894f781db21", + "secret": "8be6cfa6ebf4f73b4148ca36bfe904e930001ff7e2e06b5ba9ed71733aca7467" + }, + { + "public": "2b211f5ef3526b0395a793a7f7b7917f7fb20958a95e54d0f53e197d882528c9", + "secret": "2e9fdfdfa885c5acd2411e0f0f86ff70e693d41af60b85aa28f0b73a665334c1" + }, + { + "public": "132230301b41467209c3d9272e5b2019c65d6cd392886f01ed20cce131d283cd", + "secret": "c66cc2f557cc33e185b154496ac2bc2bff696fbf317b6e00254fe23fae3cd297" + }, + { + "public": "8213f91ef030df32815deb30ddb9c01beaf8909176754ba77da2a5d6bce1d5fc", + "secret": "74e64481d61a7e59cf6e82b558abe69e83494bd39b9f86410f4cd2540eb5efe9" + }, + { + "public": "d14214184e172154353d2d93fa3d6b226afff9c1912b40b72c7deedfe3c449ac", + "secret": "2344182b9f37c2c42342984ac5b8f8881debf2e19eb0ee7dcfa9986c8f814d0c" + }, + { + "public": "7dffec32d1e4c56caa3c300498a5e934b8a210ac781b3734eb7caf47bfef4240", + "secret": "792547f88f4cb70eace09bb5c7b2b5bf49bccc8ad6f6755772032c886d294a35" + }, + { + "public": "9fe266d2c934871500bb08dd3eea440c1c803d2759211993bf109ba2462b1189", + "secret": "2671bbd49bebdfa23c3a9d8ecacafb42988dec55a03d86a88f25107c86f3f651" + }, + { + "public": "eb2fe1f4f76a6ed5725597342a7572c84e60587ce3f072ac403c149945fed10f", + "secret": "d3fbde35d3a6be5e5788a4f014f4d0cc2013a1105cb54cdea7a887a92bcc6e03" + }, + { + "public": "bee7e5b9c22a51dcb65ac7ef717670152c38ed4b34a3ca47cfdcbc0f4679ede7", + "secret": "7aaffa3eb13da5baa30a4fe7ac3c1bec161393e50ae146ac7851af3628459109" + }, + { + "public": "dacbed0fbd4f5bffffa6be6b650991a0351791034f3faf586eefcb3bc921cd7d", + "secret": "9e66966a404afde2f4c20747928f55d2831acd9d72019904afa9fbf9c5342068" + }, + { + "public": "28d7821b3a6727d7449a80f471a4977c148e57e50a378bda32d45c72d7752c9c", + "secret": "538e34a667b7bfd00247d80cdad4a7de6472cacc890005c5503f940f185ed774" + }, + { + "public": "373c2ac636467bc378a6346a1509459f06c0408bcc6129cdc0c6d0f397d9e1f1", + "secret": "d372c764f5319a2744da40dcfcc30b9a1dfa6f834832c4dcd6c4e166303bd977" + }, + { + "public": "ad156eef21e06d1b281ec2f9fcdd393e0abf58573691f4993dc0166a9f9ac46c", + "secret": "124f23e95836085c389002af07bc5edc76f229a3cccd13cb2d5209c95f690de0" + }, + { + "public": "b6370d0f1e56be7dd38e9eb587068e1d34a2cf8dbc138b53864604e8e5094c34", + "secret": "56fde18c6bfdf0a1b097e93d786257130dedd3a82f203fb77218b1256f482bfa" + }, + { + "public": "3b9bba6e86205e128879ec09973af4494783597ad2941d838b66822879111d13", + "secret": "e77404b19332e7821f353497b2cc7af0e2130c674d4b82d74bfcfa408adfa9d2" + }, + { + "public": "e7a7651f5cf13c46abd9775c3000dc51a2f39760ebc01075b06ddb4b5d83df59", + "secret": "5002745c441abc76d31f3fc90ebcde150986246ce6faa51af49d49331756df3d" + }, + { + "public": "18ebffac8e39ca00ead3eb6c91769f56ed473c3c3f64685042c11a70538b3ea3", + "secret": "3a59bc523bdc002ce8b263bb7132bf0902f7c1b69e2101adbf198226a59bb180" + }, + { + "public": "31d6df214c6244d944534b751c973555a63bceecb832a21eaf91f607d1322e27", + "secret": "60661d2351f05e9fd6b827df3034b8844a9b123d0a949b21bcb766bdae742141" + }, + { + "public": "d85eaf57d00db006e571c1a3f24c53a3c8c4efc8698eda48f80a937358e519cf", + "secret": "65462b84a66b9899a04e2db4753acf7b84d35957336f43e72fc4a389a9a19ac2" + }, + { + "public": "6138af0e8ec5042e5fca8ba8107224d6e8fc490285c779881c15440b813f8a0f", + "secret": "ae215bc331aaf9a9bf3079ca74d234079d45fc194b6e6fd5eb2ef0a5555d1f45" + }, + { + "public": "2697cc15cb923cede7bdc396469cba1726bce780d9957dacde152a419eed6054", + "secret": "67f90959b2a6336c39573a170878e9a8545aa968bb2b3daf95e250243742494f" + }, + { + "public": "ee52ac2741a8fd71dd0a281e4a59ab15bedfe2fd061c2dab268fd3a14f261780", + "secret": "3341891b2725236c33054dcad9060b5acdd4a3beb72f1f688b2eff2e5858a955" + }, + { + "public": "38ccf066d0516c99a0f9c516b25873a3c45ae5a28c0b4ef5b0615ceeb2c689f6", + "secret": "f06d6baacff05f0f5bde61f9b7d4f1656f1ebf0d4f1e84e13e1b3cbcd12cf010" + }, + { + "public": "d3c8e270a639970842c0014c371b44b6d09c723a64ae42fd6db66e9856fe2d6c", + "secret": "2b2d0a4764ef784f8a8040aea9bee908a54d7d325d496a887b7bb384bfd52edc" + }, + { + "public": "0d4adae5ab23aea35c43ade2da1de0a5ec468c437608c432616748f7615a7585", + "secret": "c2a9d1aeb664dd70de3018932fe3bc94f67c27e6515e092fda191eff7a65898e" + }, + { + "public": "9f60716a627a065ca68e9b262b8fdd0dd1dbae0e360e254514631b680c1060d6", + "secret": "dfedad30b5402125e14d50d1868b22212c93a0330e2e25647a10603acfe87ed4" + }, + { + "public": "ed0cb6fc572386232c10dad68a480811e78b73b6f7c489a8a88e574f06790f71", + "secret": "748694b9b3607680e7e1fa09a2d17a3a8c21911a9c6e5c734bb54d835d5f6c56" + }, + { + "public": "f173b30fa8784e006925b0a3a58efaecc08fe5bcb6c1f009f14986156d766aab", + "secret": "271e2e8e13f5767bec74a4bdb6e6f707ac841917a3c3396463048803abc4c481" + }, + { + "public": "c70e6db7a78148d4bbdb164d5f212049f020980edfae8ccde7ecb984459fc977", + "secret": "baf1550d2ce100bc9c62992c61c37455f15b596216c30b24d92bca4ad26ec8c0" + }, + { + "public": "35da7c7fd7e297fad2992c4151a41d2d7c78d4cb42b94389f55a0c53daad65ae", + "secret": "6d59306d9cf83ad3099254a805686ef429c3b8e5607ccd40852936a329612aa2" + }, + { + "public": "4c7d78df12c3fbf0688ee25ad685dbdc9d704250b23d5bd1c8f38e87cb59e276", + "secret": "e6a00bc388abffba63f10be2c02f2f9ae52baaae54e8bfaba491171b0dd203f1" + }, + { + "public": "e7dbb340d0e18e555a7c59d40109280daafad40aa4f8547d51c2c6540eb68bce", + "secret": "1814cc7e982a46970d9701c9746665de5169b97550b4b42344205b5ded696097" + }, + { + "public": "7423f7279af918eed9199d366e5e5c2e49c8729b8f6128648a7b9bf7ab1fd1c5", + "secret": "5247298819104301d628ce09061371198e2b1b66024f2da83cee2870a5485570" + }, + { + "public": "1667a59051edc3e646fc75f5ad6db0d415471820f289d99dc8610835e8d8fcf8", + "secret": "78d01cbfbb52ddebeebb2000c72ae37ad873302e845cf4da442947c8ba16a723" + }, + { + "public": "e1265bfb04ef05a30bdca580593a26318ed688e6e29d382a3ef6dc421b225e51", + "secret": "1468c77a70daf4baf22ac1b1eb02b388fa5de8232d22934a7a87d373889ac0dc" + }, + { + "public": "516f78ca725241f5447dabe1f34f58415ccced4bf721a8ee16c62b16b13d94d8", + "secret": "da877a57fa764ffc92acc5a3d7c6076bcdb6d82003410e2a800f0e2acc588176" + }, + { + "public": "402e58ea563a3d085817603915b4222af11355678de231b0633026d51e1fef7c", + "secret": "6834344429bc6fb308d8b16f1e7f19d90aab1e80ad5beeeceb76394e62b02f10" + }, + { + "public": "7ce0caf21aca02e21507dd3efefd906d68a9d4fe6e239a76064c8961bf46ef41", + "secret": "f406a50d4dd68c8cff6cf494c86687e3640a735bb3d06595bdd293fe90940db6" + }, + { + "public": "9f2aad5b2add509e3318445a9b9385a5c1c0a647fb364b5820012759201e919e", + "secret": "506c0e1925dd5011d3d8e7f1e64159364c3aa5efb02e3d0a910e7c67d299d242" + }, + { + "public": "09428832a3c0c2b977dc01698a7c657d1190c0de5ef43ba7206d8598b9cd5402", + "secret": "c4201d56481f8eec017e2c84bb24f328ccf89f38cdeca4356831dd34c5c870e4" + }, + { + "public": "dddbe6eb344ddbdb5b60815de7f69ca367b2666e341ad02c5238a46eda04291a", + "secret": "c30cfe188d325b1a92f9e7ce0c951e0b6a068ac23f0fa2c6a602f6c386c9c964" + }, + { + "public": "e3642b4e1ca24da5cb16e17c8bb89c6c05b219db9f25ea283be039dcdd63bed9", + "secret": "95eb9218e4879b514c33b7e869fae42658a04ec7e63ca771fd04479c66e6f99c" + }, + { + "public": "5a3fb4e516e81bded9fea3b9f006c7f9fb13bf0d4c19b194315f7462c0faf3cc", + "secret": "af1a1cf89ebf817d7ec833e7313fd9eea947520f8ad407fbac61209a96ce9d39" + }, + { + "public": "16de4cfb4c9e68578a03493ad66b12aa1f0ccac6edc1905872aad49706478226", + "secret": "99362261f0ac8557423b6f00406be0f25dff1b843ff4c95a21f9bc447b1fb4f9" + }, + { + "public": "8993fffd074302047af1509ccb42759c2492508795cf511d064d9ab972ea9ff6", + "secret": "1972505da5cbb6442131f7c5a5818c9a59611145bc5266ac68de02e63917b3f0" + }, + { + "public": "18ceba81b155310d6169d3a9ab49edcc212f0d8bd93ee97a542f6a097d382b1c", + "secret": "641b5ddbb40cdea25d28ea37e2eb18a21f5419c648a532ada23e2afcbfe4e6ce" + }, + { + "public": "c6c50337a056d60a7fa9c75f4da3a456ce828cb3e4d8b9bc6ddbd2db2e0076ec", + "secret": "867b7709c14325aeac55f546f977829e886cd0b03f06eba629efbb7e9352ffef" + }, + { + "public": "317d2c77afcbf2c8501614f04b7879599df0da7d14a2bbd223840eb4b0c4fcb4", + "secret": "7eed86bb9b3b6f9a351eaeb550b87ad09b2ce639d7b51cf042e66db216cd475c" + }, + { + "public": "d56140b8655564de89cc5d781710258ba6d95a8c0abb3bf27f06e50b9506aed9", + "secret": "18e208ad29461dfa6eeb3c779080c0d1c7494f66e87758b3010edb0e58ce02da" + }, + { + "public": "c87aa520a5d90ace080ccce15ccc34f76a680f78a349cff58dac21e0a8d3fb18", + "secret": "f440602261e9312c70460a57d81cbd6e88ab1d582b2b00847083aab2f92bdded" + }, + { + "public": "99d0bbe1b1e6bbadd8412880b062c9ea08a508f2a02e68185971e467f1020518", + "secret": "f42517fee107adeac3a582b11c96fe35fb579f750d77d2c9f19d9a414efa4242" + }, + { + "public": "114f2f9eda2f99f53540a9f70011e3e94769c3afea73668dbe6bc73d3c9e0f30", + "secret": "ed8d154ad87d0dedac8455d749651bca0d47260798778453b934cffd072ea4a5" + }, + { + "public": "25ffd0cbd71564f731adf80459dd338854295e0c02d71348d754bf287736676d", + "secret": "7aaa1e190026d32628c48e662e7f3080a61f99f2e7988b255683f2e92be76e74" + }, + { + "public": "8d024712ebccc2c332e5138a0803514d5e7663b24c7d387e8a57b73c9cf24a50", + "secret": "ddaf235b0c0d42927536d02f2d48ba47f5c26c546ffd7884d697ac4b88d111ca" + }, + { + "public": "3b653ab6d944ca26e46bb7425a90dc26406f0c4f096831f649aae9745798c16a", + "secret": "d156418fe6e1c06617c90b6d7a2709622f9a1827dd5f3fa03913898cf65b9866" + }, + { + "public": "71af084cc1e8c378a7956aaf770ca9f25da3cf225adf8aca58eb25501404742f", + "secret": "79c0ca726c41380d5945a308839cb0c70a12dc6dbe180f107bd1b7beb7bede78" + }, + { + "public": "62c5dd699019628b6ca3fd9f7bda1bede4d44752335adc5c75c190579a66e7c2", + "secret": "fc30a39b8a88a5b79fdf904a43e7c181f01418f6cfe7fad14700f0293c5fa1b3" + }, + { + "public": "7dbb48ad1d91afdbd119a141660a801686ff52ba6a61d5e37e3ab016adac74b4", + "secret": "bfc2a182920503713783ac2a73fe8f443baac7a8649e18e01a57e3593b3638f7" + }, + { + "public": "2bc6827cf9dddbc5ca3074cce1d590d08f3d6de9c2e7bfde4bdc23ed854a0e2b", + "secret": "92ab80d16180f29b84d5076130c527a2551b54bfc6c3d884b71f106a682d6092" + }, + { + "public": "17e8f51360445938f02441cc09e7ce7d3a9c6278643d68c1ffd446228bdc9aac", + "secret": "8fe20d8d6dff9c90515d268c8ee9a7cbcee70d1146dd2e9b3eb02d86362b2272" + }, + { + "public": "3b4c5b8e662c3024248f19721ed19abe10c10b7eb1d8ef19f5a32d5634830ea0", + "secret": "27f8e6b04da3fee0a522cdd9761a31dcfb7f58fee470cf387010c5daa1a36183" + }, + { + "public": "524e5a7181b41bb76797ff4bdd16a3850244080fc1a36ecee7950225b62d797f", + "secret": "d22b5b6e173a1121c832e9093240251066b33d18bc106f1284d27b561f58cfd3" + }, + { + "public": "a1d458360090a16a8f43036a5e1c1e0774e75f906ee9f53afda140056abfebec", + "secret": "740418c953c6301e77071026fcf2ffa42407eb82fec80b514b2e13ae96eb5420" + }, + { + "public": "aa688df90590dce696b84072074e1be9b6de33aac71123af0c60424f3c13eefa", + "secret": "8c070a6e6f217f0bf0e87c7b190475b7a5227ff0439b7962570a687bb57d87a4" + }, + { + "public": "04c630ed2816c28b70ea87a5d164bddb4af81b4fb03c845abaf4f6d6970e3b59", + "secret": "23daa24d3bfb234f6198502ee8dbd0592137024e6b04183a781903b362cf9982" + }, + { + "public": "715787e51b5bae42f7cf2990a0386a12f2d91cd02ef89207ad280a542ac6c9a5", + "secret": "51fdf3b693ae28feed83914ff4c1ee746d28738de191a40d13fd4e88708c7b49" + }, + { + "public": "ab8322504c1f8c9fd3e70d180a66c13d6dd5cf10693e195eae6339545baf3316", + "secret": "180fc0ed844c79893a839bb122945534f26578d788f2a6b84ac7b9280c92ebe3" + }, + { + "public": "8fe3769c35c4b63a5cc087f43cd401c8fb00959c8d454371b8de3f07795c2859", + "secret": "d16312938d77ed68377c30649cdff719a9b89cff1c1f0be12d2086041dd099b4" + }, + { + "public": "4d2a4ec8635a13be9ed8b7de349ec40bca6a17a78582121d616949a070aa8b04", + "secret": "7e4f9be376dcbe4fc85e4ad55a874ce720a62e5423dee8f3474dd71329fb1d53" + }, + { + "public": "920a458e17ad78f74dac7bc4ac3fa4bc8e1ef1e695d6e109128d657e49a0d619", + "secret": "6c7edca17f1e189d48d90b65572ecd63f43a3086c758c3bbf2e2aa228f5ca06b" + }, + { + "public": "bd4a4d9b36ad4be451f1e4adcc46877e4cb5561ce9502fdcda94f9082e37ecd2", + "secret": "c90200b2f0aabe656007f58f665792439d467c7bffa244cfc76e015b21e16b7f" + }, + { + "public": "b14b684ccbd84a93b10bd19ad693261de4b1baf56b3ba6860f885da2d2fbb9fc", + "secret": "69fe8aaa522e120c908cc60936ebe58e2f0727f72320afd334d4d71451fe632d" + }, + { + "public": "c593ce514c50fc4fb277c565863ee1afeb2fb5ebaecd249e1b0679babaa459c9", + "secret": "a3463822104c9b9573a9403955eed1cc8309147b73d29f582410ef7351a31692" + }, + { + "public": "21dcfd4c20a9824532f0f1ed27ff380946ef4378b0775f25f4449d23c6cf1fd6", + "secret": "48d50682911774b7fef95584af3a9560a68494a122b7ef2d97a96c1c6602585d" + }, + { + "public": "25cf5d62b7d871b81d1111f853494d7ec822e86899e80f6917d083aec1484c9f", + "secret": "61b1157529b8e68e3195132aa30cdb636d7ac5af7381ef1830b19a2547a059ad" + }, + { + "public": "4505d7ce2ae54e394aae10a1d49883166b5f80c59d865b561054d897473ff369", + "secret": "6ee6d6037a507752e3c453e8f06f0e3ab6646829711750727ca84b09e97f1a59" + }, + { + "public": "9133e4ce8bdfdedf59d055808fd50a69e2a0dc69575fb13ecb4e5c245861dbc8", + "secret": "a768890be5cf76413108c760f398f08a41173d4108bb6f9a5f86f5da38b0c09d" + }, + { + "public": "f0a4ea8fe6204fcb7be3e07f9c4dee94569807594f6c2d7396f4f3aa70132d27", + "secret": "9e186e47a8c11a2cb84006a48a4a469771aa846e0502d5330edb0f4c87e2da44" + }, + { + "public": "16bb5c190cd009559020f9a35ee0a569cd934c4a18b14656559292c35b29a2f2", + "secret": "5629910b4799ac429aff93512462c6fe9e2f73d5fc5882dbad34b2bb90f599cb" + }, + { + "public": "985106d3966ab9616620dad9d72cf0d57b0f3e4a7ecab51b16bdaf54e01cb59e", + "secret": "c73ec8267ee884a754b16ec368c45fb028d5e3b761167440e9948a8c93312dac" + }, + { + "public": "c5dbe23a2f91fe93feae57ab60dd604af802254b2d8636197a0d0d0e0cebc0ed", + "secret": "d89932efd5254a105795c879f98fe657ce5fb689dd92e544739f7d27e61d8e0c" + }, + { + "public": "1fde65fe328dd6ebb61d139548f465ce1de0ec5161c64bd32dac4d12a625a6e0", + "secret": "52721f640b55a539dfc7ef36126523cbc99a720a596024309ebccc9848a75bf4" + }, + { + "public": "2b51d861a5dbfbe8b3aa566342f2391c3349a7a45da8d1df3e8109f774251e76", + "secret": "ab077f222429b2e62720fd43b471d727bb2a5b9a974f686d3bc97cf3b3dfc453" + }, + { + "public": "f9f18b9eb56be945cd17149e9a95f9578daab1771d24ce7d96daf9343ee01bb0", + "secret": "0106d344362098cccc5396bd0f62b6b6719ae2973da3213882e915295ae2285d" + }, + { + "public": "8d6db5e59cd29a8468ffedc544ad4aa98205a18ee2764ad3a03e6d8ac35bb624", + "secret": "89bafb93eee78ae1a01f67ad0701ca1ab51fbcccb5db62c11c0d3972e2a63cb2" + }, + { + "public": "12076f7e321a2c37bd4622ac5c96733e45df983f5cdec136e755eb97d0504f6c", + "secret": "3b1cbbe4f041c7514caf2e62e3ac112b900c63eaf63580b20ff7029fa6ffbdd4" + }, + { + "public": "7a993733184be08185226fd08432cabf7689fb065611bcc961ab1d8409d4930b", + "secret": "249bc0476998e9b9a1b176ff216aa750602b03e3433b98fe2f9b98dff3d389b9" + }, + { + "public": "e448cb68dac457fa2970f89100ce00069b258f6c71867ea64123e316447eb2e8", + "secret": "ab390f49c1358cc201f49bada8cf8ea9079ee6df117849b40050e78c8cc79e4d" + }, + { + "public": "2df97543f8c2546b9ec854be5f7f2e5518a153d6a0af739c9c987f411573c098", + "secret": "593292995510a4b2b3c5ee3df9ed6acc7c968ad52916d2d7193bdc825be32ea2" + }, + { + "public": "7f84571e7dcafa9a062b91048821fb839739c46472a0a4583acc9834de49f939", + "secret": "125975aab2dbb1a3907d7bbc0a72b434fb658886a51e86b23aae57f148edfa28" + }, + { + "public": "e39deee109f2c43fb80d028c50a70f77ae3fc00679df1c8144dce4fe95b7f157", + "secret": "7cc504de1eeab29e0cb891f2e0387874833fdb2a6a2e31113237effb6c91c85a" + }, + { + "public": "bb5f2f5dacf0d1316d4b1a71d2818a165ad6dbb5afa7a1c40afa2f6f951769c6", + "secret": "ad7a7eb98d0bf9e2c9f780125b21c397c3d0e6ca0fabc52f4dbbac3eb8a605f9" + }, + { + "public": "f9a043c6d20db193fbdb42e5cee3b79e8571f0c83ec62ddeb40dd0e8537b6b97", + "secret": "0ba287f9665b8e46ae129f718f25a2ba05c94ec038c13e2fc81cec953b477277" + }, + { + "public": "e4cefffb2675b9e60b976af71ad775ec951e2ba94c613266bd61a809c1363e76", + "secret": "2c8280a87797e86d7196a9e56a214b538bee0e5ef22c694d2d7fb040f65aadcc" + }, + { + "public": "0d3622a499ba3335184b68a271ec78f2599fb6d2edfa4dfd7b1f703a8c745402", + "secret": "0360ae4552301b295ce899d38602ae750052697fb900cecf8e99312bf113bca8" + }, + { + "public": "dc36963d8cbbb99de07a86253c084ecb4be0ac0554e2d4b37d0eee43027b87ae", + "secret": "8138a79a9100ce550b41f77dfcf93dbaac26db94ac052b396c33bcf69891c190" + }, + { + "public": "9ce33ac203f9a20c11cab1b054722886df325a9fa40717bed452b12d8ce561c5", + "secret": "0a1139a866885e681c73fa4bf320ca3db08e7cbdc8e70be3d395dfff90cd6c81" + }, + { + "public": "6a4c30efa3318e23b712c6b641b7c6314d3856ae465a7c34c296cf76654b8dfc", + "secret": "fc241685830e450de334e644f6589d0efea6d1b59c5e6c70783146728550b112" + }, + { + "public": "15fd03fdb91b2b2e5522eabe49f4178483630045ac5dc1096d38fac6a0216941", + "secret": "40038258cf50cc27c535a18ca0774db01fbcf56da5095f4f047cd64131b72def" + }, + { + "public": "632dc8276dedf3b9b5e2353637ecfc69f6c450a72b0f196970fb0e44ad31afbd", + "secret": "201c51f68aa46d01bb06b6422028464faa063c789624c85df263b9c9ce33b0be" + }, + { + "public": "9b8ace5447d582ce799635f8398d5afbaf3ed4b18ca4f87487a53407e149e7bf", + "secret": "2030d4dee699461bafd90028eb7c7a2a589ccf0fd27222ab5a5cff0b065f5251" + }, + { + "public": "1b8e88cba351f15b29c8087c5d693ddaffa18972939970d77de2ade5882bea32", + "secret": "01afc4eb1fcef0dda07fdad833771ec708b60cdc115902d7162a2f3c4cbc6933" + }, + { + "public": "c873b1e67fbf90fbead47faf86bb3b8eb2a3953c33339f1646b2dc7696eee51b", + "secret": "f686b88039bf48f07563d9441437ea4c63df333c0bd09e2b745f99b10cd33c1e" + }, + { + "public": "33aad64b2ed6f8e5ea96245c88d78d342878d75463ed8891f37390cb6e48977f", + "secret": "e49f1acd8cdf7e96bcc509cd6fe1cfc151b9f2d54be5b047a23260807f4ac315" + }, + { + "public": "592c2547b711047da89892e7423f293d8389d0bddc796436bab48f854b553084", + "secret": "34f73faa5d6219e4efa92020be4a914a2e963398972ebc985a4fc5731321ebe0" + }, + { + "public": "6573f68aa7a06d2d70de392e13474e9f81d0a91a589a3d27b21426a0de8225b2", + "secret": "0bf78dbb40b68f2ac3095792ff12681627e432df3672413d30f0ca350cb040aa" + }, + { + "public": "69131d5322b58fd023c3955057e68bfecf6ec758223906aeb8020ff5ca4bf9b4", + "secret": "672d7c4cab6a10af1aeec49a90ab1e8ca78d42bc3d36211c2cfa8d0bd73fcd94" + }, + { + "public": "3ada6236e05f3b92526a8a45685d737d1b22ad551f96d32d80562917feb2fa24", + "secret": "3c8ec6d12dfb03cd9797d1e2b9d9981c9451fc2b280e4d846fb5856242f476ab" + }, + { + "public": "384707d0e33cb614edb057200cb85c805eafad81ef0c879ca993da27df20e8af", + "secret": "009dcb17503d25a3f0b9334b87b280f14c5acd69f68aac2f7e42399526ab1025" + }, + { + "public": "f61cc3c3ba9e91350bbf1fcc7cbefa56bcd4554ef46bd99ffa7b71b98f707d03", + "secret": "969017d4bb2f91a3009f6156ac43485524f78ecd1ccdfff33e470825e28fca72" + }, + { + "public": "73aa7fda4687aa9397d3bf1d3fcd050734e5e30934a8587a8bf75c25ecce6849", + "secret": "37978e52bc8b7247d40390d07cb77e47cae8f88cdb6ce43c228b32b3f8113112" + }, + { + "public": "6efb662d33ea0d347c6f2679d9976d92453d5c168e90477f57c654fd173da8b9", + "secret": "aec2ff545b6dfdbfb5e7fe98567652ca6032278b395068fdb15422aa82f3783c" + }, + { + "public": "1832553df6c596990dead79409861ab3e428a45f6ec23eb39a3a1ead3a39bd3d", + "secret": "b8559cfa4a253ed6d08f9cd563587627d6c9696eb1ed2e5b23af8745dc0959e7" + }, + { + "public": "0b2a803982a55bd02ecce3d32821b20b622c5ade803048d51f0df831589bb9a7", + "secret": "ab3fc4f30d6da9caeca5ec7bec1ac87661e982b066fa671c35056305b02fb4d1" + }, + { + "public": "b7144bf18f85e69981ecb6243a27e25e8a10f79179ed04d1f67fab2f1ebd87ae", + "secret": "b1bd56e41045e0bead491e990c2db563349e615ca84122c2880c1a1da41b7c3a" + }, + { + "public": "5fc5b6ad9cbe68eb522f080a634e4417fc48908c466001d84b725a976ef27764", + "secret": "ff9b1cd83392940ed2734d5fd0c1eeef30745fc365abd25aa7956a8f70819ad8" + }, + { + "public": "3e81bf7f9b9e787f8e0e7142fd7be9a4cc732079f79d49bdfab735cf55d1e27c", + "secret": "fcd367665b9e3f05a56dfb67d9ae7d107cd5f68ffa2fdfcd222bc639bc6af959" + }, + { + "public": "b8ba75a68a63bfd503e6fd2f4f83c232ace4bb37ad373bdb9353e24aa7705173", + "secret": "7606170a9a1fbb000ffba7257ce8903dd477621305fca1ccb84deda626570481" + }, + { + "public": "6ad6eef3e6a471247a09047a7c977cb366c2263d90ed8fb55e5e2440f2d337cc", + "secret": "f7e4bb73e177325571d1151b9d791216294b69bdfad075b462a3a931dabd5bee" + }, + { + "public": "77a0065d754c203bcc1fc83ed4efdc3afd680994a4dd713d9055c7046adfb1ef", + "secret": "becf3bed33187b9ae3a05c3f4dda8756e6d0246a5cff67c94d430daef768d910" + }, + { + "public": "18137b3017ec776ec700e79c94c994344e603cd12ea216464b6f7dd474bc8b01", + "secret": "a3ff2cafb4bfd1265e83f8a7c2a344ea2db4b36bef4f244a7587c99302ed4b38" + }, + { + "public": "ba61930621cf04e0a07cfffc49f6281d65dd8f5cabbb183e0c7cadc906b8fb09", + "secret": "4d0fc2a9daff402fe046b028cc7904b60705e41dcb9e34980bc2ec0663033466" + }, + { + "public": "c418d4d95c972063acf51cd2bf89ab4b7d2df46d060517de0d81a2e7444f6af5", + "secret": "b497d108c70078f2bf2a4b56406d67141721f298f3488f6758c7caf00692da17" + }, + { + "public": "95401ce76a364f9a6380c27fc16928dce9a3d4672aaffa6fd5c144c0c15c82b0", + "secret": "e69d584025ac87cbf7d81ae45910a669caf38917892d434647ef6fb018f21bb6" + }, + { + "public": "99fa2e7c4205ac501548d147b623801cc194dc49ba4e512e9f833cee17001fd0", + "secret": "de4b12e94dc76f6316ca4cc344b7bc2b23f69ffd787195102d01d9310a06a7e3" + }, + { + "public": "1cac9bd35b5a23ebbbc4201289c15cca2c437555002c9899aa1cd1e2b4bcc204", + "secret": "cbfc70c77012dc2a7f07260399ad8133d113063190457633c393c091cb606983" + }, + { + "public": "7f47c5a09c679e00d7efcfe6f3d8d7dc27f2ed365c2ee905bf9879a0b02bde04", + "secret": "090b86b598882051b4123c262cb69c3b560a9e836c41827e6aecb92934306ec5" + }, + { + "public": "2db06504fbcb7185f5476653d26108b91eeafe53563795a2e5f04d7f975c2689", + "secret": "d709f8a8858297de4c014a7b8a1143d93d3e9754c6db274ec67bedf1ecaef09c" + }, + { + "public": "62891267f89c34d20939d0bf5f977cd0b12641677252126d1f6daf7d34f5bbfb", + "secret": "0bb037adb88f5e7f70fefd7546395038d9866c8ad6a7afb45f6aedbf302e244c" + }, + { + "public": "c4a62613c2818aed6ffefb1115493f47786d71a5f6c0e13f4f5fbd2f2025ef95", + "secret": "5869846fe2dd0652a466650f0449373b3cbac9497d76bf0981f9958af05b5ea0" + }, + { + "public": "bcbe517b5ca9250eb97f4b6111fff2e4c4ff023ec4eebca36c7b6ccecbef6039", + "secret": "5d2da9990b10c1bba3aed940246061d32d5923a076740d2f3195c754758d66b0" + }, + { + "public": "b18b89236d5ed30f79b423ff03b1eaa3ef5754c6f0a6eb2027c3f6cdeeeb8d92", + "secret": "f2bc7855c992a72e83ff631065ced58a11384e8f22b5fe1d67867c76276ca9d2" + }, + { + "public": "5e653eb275cf899cd1c32bffcc81f7e6e487c41c39dd2aafa6cdbf9d54fbde73", + "secret": "c2da33a82e88ca2a0a2243a791b21423136189efbbe77aa8ee46e893ce7a2061" + }, + { + "public": "b8eb68b72cbebbc7f6b2a37791f4d6efa33ac6bf690e1e4164850ee215600688", + "secret": "b30541e29054e9573a573ada37414f06e84530276ef7407dbde033e54a9a1b0b" + }, + { + "public": "9b30a715a1f80c1744d2391d6866437a155eb5615db02dc2aeb28c9c7298a65d", + "secret": "f07d46bccdebf7460a67a9a879b7254cb535fa641a98f58938abf52d1e81c8ae" + }, + { + "public": "649b05fba0e159b5ffbff381e29e36a4222659664971f3b5b8d454aa4d8744c9", + "secret": "0489931b7dd8803dc974cd35ca092bfd8e2e0bb85bf88cec7b3d92e59100fb04" + }, + { + "public": "069b9f8d37c8cfba2c7194e1eb366dce9fb103bb42eb630c6bc87b1f11146c50", + "secret": "a8d94d0f1032670fc995390c6c221033c50742bd715921853efc0c0800a015dd" + }, + { + "public": "1a4f71ab1d3bc19e2398726de56c1e79e3600ae714266f517a89ca15d61a5478", + "secret": "ddae22e6453e3e7bcec475f72f412c28b79300f51e75f2609c375ea133c205b8" + }, + { + "public": "67c05dad419879ccb18d42cad0f47dc4fd55aa567954fbe4231482a296f2a487", + "secret": "0a2588d17a432d575defbb484e2fed7c60ec9dbbdfefe516d9b2f16150d3a5bb" + }, + { + "public": "caaa4731e4b800e187c87a9e9554cec86dd10dd7a7e9ef423208aa4a674ba6e3", + "secret": "29ad964e0e4c92c83b622651274b2e2d9e5cb7b4bc5d3e5886fb7ab2a27ecc63" + }, + { + "public": "25d4e4d16d9ce1bf6a4cba50ef6e7544c34ef989628d63550199ebb5d0f5b6a3", + "secret": "30d2db194bf9b07a0dd0e8e4bd1e1ae7c44d936c1b0afd6f651b68ea4bd5f586" + }, + { + "public": "f1a8cbed753f83a3cb3f3d261f58d62d08395cd60f3ea243919ad6207cdee44c", + "secret": "867282881315b031dcb910e56350ea18bf01e0705133c103695ea3b0fc189e05" + }, + { + "public": "c5c62663210abbf436ef190afd7494f213d9ebd7059e0546eec0af4cf28d2db3", + "secret": "e5f55a19fb539dd37c86ffd63c8211ece504d64cfc751bfba6a9953c6a2c10f9" + }, + { + "public": "e485234a319b7a12b16f61f120d79966ea7f6ccc755f787f85952dcbd4b87510", + "secret": "62d5934a91d32b0985ef7c3a1b9c75f84052fca55da0f8ae74f11023d4101eb7" + }, + { + "public": "accdfb9ce40e7e5e8daa7cfeb768810eb1f1445bca1cc8ec26b28e72ec1f3eaf", + "secret": "88bd96b4dfbb2e2a4848e859056f21c4175437f41de749d1764d04d002596af4" + }, + { + "public": "0e20b9d5309dc1310da053d550af3a8cdbaaadfda7db83180ecaaeca8361d8a9", + "secret": "3cd5067c2dab34e91b3c5d26d2028e45fe94f45c28b2847c4a100db3b9415476" + }, + { + "public": "3f16dc2d08df21180663c083ccfb9ef74b1ff890aa11c838b44e0a483ea08693", + "secret": "a59785103d367ddb3b2f03bbe24637242ea7f1409d4d7be454816a0a59b1dbfc" + }, + { + "public": "a3b3d38bbc8156559d0d534a6030fcf3e60ed721b1edaa14ce70916146203efa", + "secret": "f355cde485825e2d0ee771daa9cde8da584ffd09a62b7299b0523e7e7cbd857d" + }, + { + "public": "229567a1730a8aeb5f01562c46f4e0d5bfb38329cefa3fe1f15f93a48010bd9f", + "secret": "b608f262ead0aa509a4a23ff760d5a51652931883973bee10c711921b9114423" + }, + { + "public": "24a9a03b3e67fd479513ad9c2e610ed7909537130c05fc74cb2cb51732e2aec6", + "secret": "6de6966b730baa4480b26931c92063d675cbd13a8c5e7adc343fe3452de55e72" + }, + { + "public": "5e36e18e7795e388f8109a01e2f8755c710000f08f4a20391c921b0ecaed0e81", + "secret": "c884e52f898baef4c64b6be05f88b768932ba3cbfa703f02538dd0a01910d44f" + }, + { + "public": "f42b0d0bd6baa9b5cee60d91397ebc536df5d0f587b472163b96d00c45eaef54", + "secret": "c2ae4d1abbda1a7faf9c6eca50d3ed9b5dde173f48202cb831639902c0bffcf0" + }, + { + "public": "99feec1184922c5125a5b04192a8bfffa9aa3934d3094961845044541e700b80", + "secret": "6a7e4af1b84823bffebf0589bc992d9404fa29a666ffd8ac548518cccd802375" + }, + { + "public": "9c7a1ea3116379c3878d45d36918ad3bfbd17043f717bd6ef070beedb3bdd6d5", + "secret": "03d8fb7884232a6d074adc9bf69fe5c2d7e0b396ccef4a7bf81dea7ea9ffeb6c" + }, + { + "public": "fb15fce868eb34a666f6f6e3f7df3c0cba3bb8243a327eb1eb7ed4b1a50a2306", + "secret": "9797b9ae444926aff4f4268193f4c5e650046d59e7b41e8997cd46f4da02d642" + }, + { + "public": "59bcb84c869fe1a4f7056a72a9452774a4e4329d71b79daf25e561cc2e0a7012", + "secret": "816e88786a55ce7dc3b716b9de16c75b5d5c0f60bd3786b22d470407b3a04d03" + }, + { + "public": "c14cf76ff42eb7546a618c10d6f982ffe16988992b420c0afb750a61b8f15137", + "secret": "56e4db4860ae4e25ba27c841f4de3179fe31befe8a716311d423db3e4f081070" + }, + { + "public": "70248044a78c7e971885d193c0aef604307d81dd1aec9a9aff4a64ded4170aad", + "secret": "d4fb6d2277b6dd9be427e37fd17f8a0b6174bb709a7c4278d0c68ee2c648df1a" + }, + { + "public": "8ecbd7aeb3d4c13d6c13db2c1d20d443b752fab510b84c67da285bc333ca43ce", + "secret": "2a1a49adaa428aa013ed78793310c52d32f6453211a3852b729ab54e383389c2" + }, + { + "public": "f36f9ead2aa2a27ec8568d39cc601cb4c4bc6b4d85d7c12ea3eb0aa191fa13dd", + "secret": "be78469027e2720b5b13250535c616b4017306f7c94d085cbf00ea5941e59108" + }, + { + "public": "bd84ebddad55176cbaf0a08edc54c6d57db77aec7ad54ca3f8c7e143fe7e9915", + "secret": "b23606554dd43a9569409c4b9b61a4c9f69347b82bb0b1355dc4b8345c7ba60a" + }, + { + "public": "2c3a248a9453394bd4c7ac079133073c30b14bf6894bca0e4ecae72b88e105d8", + "secret": "b6fd1903ac7870e7fa19166cd04a79e6c58ad2f57a0907eff0e3f74e26fe8da3" + }, + { + "public": "503694f324b6b44fb149eeeb8356c02bcc8dcbf6ef009a5c29d4c3ca9bddfa47", + "secret": "ec9339bd7e01c42a501c80181b537523f4deeab946e8d4efa0235f8a14dae8f0" + }, + { + "public": "7902af447e250886d068313a63b41b661d7318b99558658112cf08693b74fe16", + "secret": "c4e53ac84088bb58ba05369ef811cae70d01b99c0108c33b3da6f786cf295d24" + }, + { + "public": "c41c6be6b4d6d9f291e6ecce6ab7809582083b1bc1ebd7ad7b1dfd4394dad4f0", + "secret": "569d86d046480a0df1629add7179113dfc9f3889ccf72e697890b0f5c3bf400c" + }, + { + "public": "a02c199004ba615cbd0c4f77263fb8b6cda9dbc1c24383fb4138ef39dc0a97b0", + "secret": "f74324aa36568253b771205de391d6cfa476cfb147ba3933b74126c104a4d9f1" + }, + { + "public": "d0a8fe3b76432f15ca329ccbe1c9b0d4c58bebd2189c471459d810357cb4efd4", + "secret": "f2ef3158a6e49ebff0a4140e3a80c97b0085ec39c876a46e5b39c7522fc97d64" + }, + { + "public": "e2a7631047223a3521210ce90537db258a7010be7494862d70a43d6a956bc005", + "secret": "c411b989dbd95cd371cfc7b332f585a48e9e0bd780d358b35c57163ac416d7a9" + }, + { + "public": "ade328febc4105d21b4e5febe877dfebe6d9fc52f77c704fe4d81262ff45f3db", + "secret": "4bf73f355743152a0cb720c7e701c049900daf3cfae0f1f4443edfefa5fe0c90" + }, + { + "public": "a3f1b75157a408a073d79300cf65679db1b928b902480e5768b2e8e79a03aea3", + "secret": "4b53fd422005ed4b5928583f125f1cd3a559832029ae8dd2a80e0ecec58d65f0" + }, + { + "public": "650230d064dbceb328f798fa65804f29964beca010c3453129ded1eec8438503", + "secret": "d8287f21a4351065b8ab93691669360355049fd664b660630fa0311f8a262988" + }, + { + "public": "661f3698409184c344bd7827deb6d307d6f9d353eb4934f46be974ee016212b4", + "secret": "e6d77209ea3b1663156c1b0bf17ab807354a4dc476301f8c739b274ec886e4d1" + }, + { + "public": "f17f14feb44b525e046e9bda922d57fa86e1a84e8d2238c2b61b242d433bdf39", + "secret": "6972da9451a59222c6530bcfb0646758a3f59711c9220cce49535f63d518a7c6" + }, + { + "public": "5b473895f24bab06d6ceb3383bedb42b9886bc193e2635f7f3efb6fd33c14cef", + "secret": "4e2a607cb334233052b856d441aafe608314526efbbfd41dde967e912c195ed3" + }, + { + "public": "9711a56ac5c6097a71edbe13612d07e44fe671af31edfc4f7891d814dca49997", + "secret": "2cb88f5d63f3a2839d110e5171619f6d8ff4d1e0c51c2c7eca7138d646968764" + }, + { + "public": "216fbb0e63aba856d85bf94a8d10c06f56066e9719aec860b2710fe7caac3163", + "secret": "a6cb5c8271d285b425e9fc208a14ab3338fa285155f6022def10f34072850b23" + }, + { + "public": "05dc1ed27679e0417ada244adc262847be09f46010dd6fe3cb166373f8363716", + "secret": "912fc21c4291628006acb903716d0c87190ff4d32fb1115a78d0abb3403820c0" + }, + { + "public": "de8546e1e80051d5db299d854529a01ee4f69734991efccb9713858485d39988", + "secret": "9eca54d7effa2d0554b0286bb14502e36d6b6503bd491f39e8eee2916723cb28" + }, + { + "public": "941f191fae823dd1c242998d4c5db40c8afb49f32632f5081ea6e1e1e75177c7", + "secret": "994b29b96c8900fdf128b471e378201e835b70c45842941deefe4554a2c39e9e" + }, + { + "public": "7f804cc37e31520038abceeb9cc3021b67374854e117d61af27532f7bb2fca6f", + "secret": "1d9672eb9d907e581932e90b56fb00db757ffaf26377c3ee6c7a0e1a4112f9bd" + }, + { + "public": "ed1cd8e28140b0b54acb895832a360526be675f27905dee4b2748807d3087f1e", + "secret": "b733b37267a3851b0b07faedfda52c8f7c2c43662165356e63c92687a3de0da4" + }, + { + "public": "916485b3eef865acf818bb9345fda749f7875245a7dc6f8e91cb0b71e090e0f4", + "secret": "6ee65789b0cbf0db72d925df0c95836a2c48475f916e56a5646e0a00d66ce92d" + }, + { + "public": "26542938ed069a269a9aeace3d307e35358d292b32625dd254e3d210cd8f6047", + "secret": "bab61e87306722c5c2eee3af69f9fdba701c4c2d2ff98587b3f3949cae22f5a4" + }, + { + "public": "20ddd63f58d86369330ad4f3f51ff50cc45d550eb3b1577ac350b4cb1e39d4a7", + "secret": "af8ecd3c99c1ceb26e53ef2355c771ee7326941a68971bea6d95e11be4ec30f4" + }, + { + "public": "ab8e0beee090ba4ddb44d43b639ffc10941031d0388d184b42d47478d265f33c", + "secret": "3d9a15dddd6f69627c396341ede923679d665a84d979a9e4ddfe5da2530f1efc" + }, + { + "public": "0fd27324574ef684a4529317fed63d64f18890251343a5f6a38876b955ddafa8", + "secret": "6856fe2b15e0a89315461b22b72835fd59a5b3015ea032d16a8c1f7a513073c0" + }, + { + "public": "baada2d1bdbb5a1042e6440a7a13f66549763c35ceeefd502d05448fdd3bdbbf", + "secret": "85a21b5a8ad44365e83fadced92c89e2408c2ada17df17aed55371ddd1045ca4" + }, + { + "public": "6af227a4dc2ac0d3c178f29f22395df46632f0d4036d01e122653a4014bae92b", + "secret": "a452af611572c509414df39e76259d05d40e4979e70e30b7d16fe1cf812c4b28" + }, + { + "public": "adc6dafaaa23e975d763194d483f0359299e96bcb70bd44fe92a1801112e187b", + "secret": "8aa9e563c01a8251b8eacbc7f9bdc3c22a97b41c77a23cc08fe5b64fe9e6f663" + }, + { + "public": "bc6c77ee3f0a625fbfb1012442305fa575a4d52ee0fe82e3ccbcfc1aa97bc917", + "secret": "9eff385b8907e357419c3bd49f2e6ba94c67fc53ca00ec9ad85c573d83a68005" + }, + { + "public": "0c1d220e688c84238e55c6d166f4debe9a5edb26c8e784b3c18af273a51aaea6", + "secret": "5899192504ffe085bbca2562dc0cc18386da793cea45eedcd1d8d2f004ddc817" + }, + { + "public": "3d8bdf8162d642140f9870cb84fd2c82cc706e83f5100123e31169381bae5a8c", + "secret": "ac40fef174c84f2cfb93bf7f59b9123c3ed2b2ec819b7554a7531984b8d2949d" + }, + { + "public": "a3b38619c0982ef40b03a5da4d77411ae7532abb75133c184b3ac99ea58d46d6", + "secret": "d6ecb90b0f6400e20dce9c3bf7e704d0e4b0f64d89c77af27fff62bc43865d56" + }, + { + "public": "219eaf6364ed6face38727b6eefd50336ae8b1c9f5817db20487b53dca1f50bf", + "secret": "df939a62031e205a510a33b3eb86e3af51b6bc7dd986b9eca4aa9f344eb94e3b" + }, + { + "public": "691d869dbdf9c792cea1587b9b58155a68606f91f78f98003460cf103bcb6d5e", + "secret": "81b185d84419f263fa50d079badab8030febc3c7b7622c24977e166e3164c477" + }, + { + "public": "9e34be21592e9260798252507d7f3cf27c57d1978cc432c25a7a8b8025758840", + "secret": "471244b7b426f1f4d4c4871e81d822c1a6177fbe6c477f6b72757699d21bc580" + }, + { + "public": "efc778f02d2ab0e9932bcd4b9a30d439aa6764db066ac87ada8f096857d23f73", + "secret": "a8f28e766e3a0b61bcff3ea7f823b90e56549a8bf3dabd7157548e529c1fcc90" + }, + { + "public": "3d4f4d61d3abff3e929098776160165bbaf966d15cb485457afdc2e32c6023a7", + "secret": "76bcbec85f0cc44d7cc0abe0c7f179770653ea15692516e4ab453e4676470951" + }, + { + "public": "6052442f41bed488c2898579b86732b7d5a0aff77553bc2d45cf81e43f7194e0", + "secret": "1e463ab3315e8976e94b6e9459af6b803e1218e6b5c39a49d732ec37b8493403" + }, + { + "public": "e472a6b87b00cb064838ffb4ebecdeeca0cd5e02e6fbe0c048945d86857bf583", + "secret": "316de265eb21cf51f04ac8490c23edd1d6140660b0192ed9dff2f4db50085d45" + }, + { + "public": "2273f214bf665ab5ee37261530ccdc555f7325f9014ed8b4dffeafdda9859a7a", + "secret": "88c9a40af22e126d8943984b351d87024e33702381de16e3c10318a00a906923" + }, + { + "public": "42a415be42e665a76fa0743bccddb8c916a7b0e06665f397a1be37c0e0520dd9", + "secret": "dc5b1998d9a67e7de464ff4a12b8e19102e5003cef3db5eae315eec416439348" + }, + { + "public": "92649af7eac7639edc56bae7be728ee52e70c7586acceeefd57b3de9cd25e5d9", + "secret": "5a12a5249bccb4cacdc9a5f25d9d5b6205f2f6b80d312d4c25b18f690e8a7d45" + }, + { + "public": "c4265d4031952b681a3c1a585b062b8b53a7cc496cd73fb638a59d2a434bf414", + "secret": "36c6adfc208da1534d915a458050a33aa428f50f73e4063b3beb4436d37bb612" + }, + { + "public": "0a23bc24e4c7a372b369d8f70563a55a3f08a95c09e95d461bf10404d998ce37", + "secret": "bdb6595db6e28882b2bf6b3ebb5da67c95e8a1870ce7a36f79e3d3efbe349bbc" + }, + { + "public": "dc9c0004e2c130e376420aa751fffcb379059f02df572f23b34e6868937408c8", + "secret": "88bd15ace8a28cec402892cf8276835be2e543ca32d7482d771e9c47dc99df8f" + }, + { + "public": "3bd55135521f62c900b412be71d74012e92aba32ce4d13e15f66324d33e20951", + "secret": "8f3d49f968feaebff1fce647db5c854f6b08f5886c3d4e20dab250b40504c92c" + }, + { + "public": "a187cf67ef7cfb12b4a395271e4391074ff8bfa3719a706514d0d56e8ba896ec", + "secret": "c2ec3e1c9bc9cbde66241aeed92122b71996ab5697603c390f7513dcf7b0574c" + }, + { + "public": "0c0b9aeb2a9a5b3acacbc5d61621124cfc16855b353d747b3c7bf28eb87cbce1", + "secret": "05a39f10c36bf83d820951dd0e5b8eb9ee5ffae0f5e4687792afb44b5b8843fa" + }, + { + "public": "d13e5d5da9b499ea62eef6c7034933a0fa53a231ae4cdd32e708d2520614fde6", + "secret": "4a43dcf616dff641f189e41b1390acd2309b99a950adef70a0777021e5ded448" + }, + { + "public": "725197c7f63d344d8c1652817a3aa02be0a6de9d7a6309c4e3178761831f89cb", + "secret": "694439eb266485f424c90014d82d8ebb6ae166a1e9f33f2c7bbafea038a8fca6" + }, + { + "public": "749c7c430527675e897c09a1fdb0bc4074476091f4532cf845fcaf031281cd52", + "secret": "33506e72242f4a773512475bce43228a5c50208fddb91aa41f423f46c513b481" + }, + { + "public": "2e2198dc0ec511f983dcf1b85c725bc93c03d9c2c31381e693e1e8fe345740ed", + "secret": "4db9dcd015d5c5fd462edf8f02cae68e6c0c1c151bb47e1cfe3e351f6e982789" + }, + { + "public": "cad95d73c528ad101398e30c01af612b08d4b432e6587be82da11ce02878fde3", + "secret": "e9864f1d63ca2c4063f99dbb23a29e808faf967b9bc3153ac451fc8ecfbadd49" + }, + { + "public": "8854855d3e1f9908095554f16ec7f66c25a1623d28e0cc2d97a9c0732d084c12", + "secret": "247eda425f8ddd54e615e709422d4fb6f2eb60e80c6c8f5f208d705f917ff138" + }, + { + "public": "7e38868aa49dfd9eff9d5a1aa2d679571271d92c306bd3c4daf5e2e1f97db212", + "secret": "7ef5f4d8d3c53d60983922c9e25f9c3b200f5e07fc624a6ed60133588e663abd" + }, + { + "public": "3f7db925730d342c2f731ed1fbe0631952629c39ce2fac2fec2a566399e9e9a7", + "secret": "e7a1479a3b082e26ae12b6a1cdd6ebf373493955707ba6f80e6d346688ca1560" + }, + { + "public": "8e53f71115d5e09ce8260412f5c16dad0c5fb0dbec251b96202540f6f013e25e", + "secret": "309ebb1527aa45a1f25b1b13dced51b6b85896f96ddd11c5ca34af7cdee67bd8" + }, + { + "public": "08673563b3767053dc2e65e58dab959813af5c4801b9810bef27bbee95d471ba", + "secret": "8637903955725beb0aadd21a35014321b6014bf42704bcb9d5e67de9d7b13364" + }, + { + "public": "10be59337f7508e5efd854e5f335591ea4d51a6393193f626607b71fad009df9", + "secret": "0b37ae35fe0d943e51f753b35daf3cfd1d63e56a37f2ed124947fd79a4cd77e2" + }, + { + "public": "d9bbd9338603b8812d09c21adc7c0c9387515d92d70187831adc22e8f0555797", + "secret": "fbb0869bcaf70dc9aaebd2ec58e26a39ee21abff118f714d87e3fa483d34a853" + }, + { + "public": "dec676e8e948f19990ebf4a4166c17ca89d2e08cc7d61e8e2641a5c929509cbe", + "secret": "26d96cf5df2939cbbbdadc2ffe25abe903770d99be789da6e751415c35abe981" + }, + { + "public": "cdc790038d304cd52fddf9deddadf918c3f15dc2e64854cd761fc76e1b87738d", + "secret": "f557f040edf96277f1422933859cb5f2da6452c0c4af062dce804604243b843f" + }, + { + "public": "3a15f9f968186c54daad69d6b270252d4c6e164301d82103a636a3e7f4070a6e", + "secret": "e65eb12789920d93f36b489656f063a955280f28f998fbad5ac3bf82f7735c35" + }, + { + "public": "612cab795cb054b435b775461a5bba6e1081a25f7ab449c01cb5d97a2ccb19d3", + "secret": "c315e82e0cccc6f37574ded32e071409e77e6d849ca756995d78c01184f59a65" + }, + { + "public": "ec66eb8d31950fa7f41a8d244a552e3f5fc57642cb838ffa200f8c0b6f3f9e84", + "secret": "750c6d0d0728cad57ca686b439729ac4237298c240bafd4060e0e4050f46a7c0" + }, + { + "public": "174a3d6427661e491cd4be6060c884160de8bb759431996a20ac9a468bb6ab65", + "secret": "a3aee9bfebb11cbb1322155b1fa3529317a14ef24083a40ba6015f37d02cebff" + }, + { + "public": "53dcca690add64af228d173545e74623423c65c08402f3fbb5c64b33f8c239c5", + "secret": "dd5c041ce81d9f243914b631a24ed4c016f7000a1b1cc4950c724302b0087d78" + }, + { + "public": "3a15574985d9c060e023dca8592865f511db3d3fe8279591113e431340fb0474", + "secret": "1290044224e4f2dd35969b6beeb7d514a77b66c961c8885b8842a5df002f9835" + }, + { + "public": "385cd08332f594078a7d970bd0700f9b1889e37c827cc3dea520ab4c4b8c4bd9", + "secret": "27d402cf67593c9021b391678c3bbb306f234c2cad2239ced2109ca29851161b" + }, + { + "public": "2061291d9a0c41e4261dedd9a695b5a90d4b172bd1c0ab9f7f74caf6f75eb705", + "secret": "efbb306fb1f5855cb230718103118de1b6422ae36c3ffd2e1ef58323bd920e7b" + }, + { + "public": "9a8e483f46224aaa0bf97975663b64e6c3c25c6e1f6712a408804067fe24363c", + "secret": "b27fa2c64fb388bb0dc824b758c4b6119bd1eaf117c7e4bdd621e6a7565c362e" + }, + { + "public": "9492790b21ccb957ec1b5ddb2d4be27febc05dbf9c389bc2bc279287d91a19dc", + "secret": "5ba0c29a61cd2780201cc3dc4adad4b88f697f165397d40cab4478be23db103d" + }, + { + "public": "5e0eaa499ad1e2425fd7cda4283adfc84f50aa7d474985597536689efcdece21", + "secret": "0db699da1da5c518850ba078cc2ad9c3d1d561f251fea7ffc1b30a40185be5bd" + }, + { + "public": "e54faf50a0caf1d363247a45352182b516f6a126b584f024dfde8ab65b6716b4", + "secret": "e188fe3a436f9ad1dc58da31266778fac8f1db611578c200e2d32ddc0ba5db4c" + }, + { + "public": "762de634fff79b996fdd726ec1d7a207d5298c151d83cb417d90ecce08b10e0e", + "secret": "6167236dfb7fdbcd67ad47f7819a315a3dbfdfa70215a8283b344cd4f58d814e" + }, + { + "public": "657dae0d11b4c7694e4ba35a2fb0245afa0fa508bf9420272778379759c9fe53", + "secret": "3490e4a468f501778103196d564c04127b8a4aa7bf364afedd227e4b17a12fd9" + }, + { + "public": "d5e4e34a002e3db3ee3f1c3cb9db7ce1350422c7744e310cb18b755b38e622f4", + "secret": "5ccb0dc602621510540c142e7a8f0d8def5cc41668c08f3a38fa5e31da182e3b" + }, + { + "public": "6632fe5081b8d046b80017501bbd792db7c2eeeb746c1c8cce9120a1146b6c96", + "secret": "56f9cc1ea1f9e4d3c56cddcc6d06ad4527d80685ae3cdb29111b2a298642c92d" + }, + { + "public": "a641986fbdfe5d14ffc507e15290d41dc5c6eedf39ab4751a1a65e4df4501da0", + "secret": "fc619ef5f19cc0eae6430c1fd1901854692f7b1933c448a01f014bca58bd8c2c" + }, + { + "public": "955b4eaf92da8a354190d861a6c91d9ef5fdeca8353ce2cbdda853f517a79523", + "secret": "a7cc30d14fd676c7c2d7a76dde52203d212358f14f4b9d18b39103756e9452e4" + }, + { + "public": "79aa94afbc52874a65c01dd8e66df803258e61d706c083fab0e1cb5f0be26377", + "secret": "fdc55dc028b1c0b507734c4cdc407d12f395c356f5f2f802661a026b42c01d6a" + }, + { + "public": "c349eb27006d0ce9dd2d5df17cd8ddb6820d62689b3d1a888a1e48349366e3b4", + "secret": "dee7f34f55ba8c124bf2b7b9e2c2fb9c0600163b668a417511cbcc29fe620ed6" + }, + { + "public": "f0631a6b85c7f40cfc2bd94f9f688618b4abb689cc7f6842cbcf08fdde669a34", + "secret": "2d5612093bde516e6b5262df51d42097d201bac9ce033318fc7b1a0c32c95b3f" + }, + { + "public": "659cd6506da0ac4df50aee674bd776daf8363801f5da7e9bb512e609fb7dfb9d", + "secret": "572667ef63e0a13386fd8b5f5a25e654ae987c87900fef5e65ecb169ae5c958c" + }, + { + "public": "269c4a800a1d818e125d63c5c498f6d1071e6304de1ca007e6549dc89e723c00", + "secret": "62dee6bff6a1c62a01cf8f260e47432c26b08c3c66188761770c844515f44477" + }, + { + "public": "590b06990dbdf8421d7916fea9f1f4a438cbedbadc64ead8c92d3b332c32221a", + "secret": "37ff0d01480976a6775a366b2a1643c926ab497d27b2433520ce5f8159f4cd1a" + }, + { + "public": "23a8872845111e9285acba5c69de5c6f53b16f982aa73e7a9e88c3a6c7199d86", + "secret": "5b62ab858fdd26ff73822b2e5a76d3d256ee03ccfc5ef72a7036d0e5e418be18" + }, + { + "public": "5f13dd1dbcaac90a1fd9689afb7b54fe13ae87bc554bea89676248f7a36ad58c", + "secret": "87c2b1d348c6ba3576d1901ed1b86b086426db2314fd029948a9d8f25ac0f6e0" + }, + { + "public": "623aaa8c4469e5e142f69404c371db31d0dd4275616c227d36651081eb83f981", + "secret": "f92e4d1d9e8037664430d8b05f3b2b0cf3f61f9104b551654a299f1f5e288182" + }, + { + "public": "b37b5179f36731bd48fd20626d31b9d14622670e6a98a526a300a9cacfc81faf", + "secret": "4d18c1733a974d9e32c1cbeb2c2baf7ac8213e7a18d89ca7c6a77aa10a5d9ab0" + }, + { + "public": "ff24838729e164a7a6e9282d4ddc7f8f9345b9a929e7b8b3df2d93dd455f7626", + "secret": "f8cd0f5c3a33fe6b84f1edda8c4d039a01861321981c002e95413069d1df06a5" + }, + { + "public": "0e61cd26bc5b1fdf6d5b75dc8c3694cfe08df7b737b42a5fab00651fc95a2c35", + "secret": "70e36aca7e399bd2f8dccf1319637922e80a8a2d84893c5dc105ebfd680e825f" + }, + { + "public": "9cd0d5f9b88560423c66867c3d832e0e82f708c893eca7a27b3488f65b9f2a9a", + "secret": "75bbc56f911a2dc0e31d96dd56c026c7986ee091f730099e4458ef24859a2582" + }, + { + "public": "7e8c1eb6fa3c14d77d5967fe3d5bcdba5a9e7a6aa3411056a7593494cfd62166", + "secret": "7aee0e45ca6373522df27884ca79e00639829e40d3c039abedda019f8e0e441b" + }, + { + "public": "65de9b14aad24e47dd8e5656b99cc207d23805ec1b2a594755b1a85620ca59aa", + "secret": "458c4599a5df8893d71932ab362faae0d2b3f737811c83e337c1aabef7b91f80" + }, + { + "public": "89d9003228431cf666cd018a6a960eefe0222ff97114f532ad397530f38a5e78", + "secret": "a8adb637aaa6904c740a5c16cac4245e2a527245b94d45edad22a740d28f0b7a" + }, + { + "public": "1073400df9f97549be6831c132e023112f611ce950b2fe9e16426abcbb351ad8", + "secret": "ea3d86cd44651aa2112ba6a789e12d60b35890c5e454cd59236e984277f8d8c8" + }, + { + "public": "86dc06618c6a255d06fe2a48e6762a01aa2bef66ec6133c27a63e44ec7751121", + "secret": "529298fc9e0b84fdfbb47027721d35ca1cc943eadef1524fe16cc70a06098afb" + }, + { + "public": "fcc74ddda3e6f256a2c9b12d1c8f373fb45601f6b05e417da966c798463524b1", + "secret": "76d01cfdf95d427f4b391d893a8c78267314754bc1755f8809e507c910db1dd3" + }, + { + "public": "d48d626693774b0cea47f83abce88dc42286375a19c32fe71d7a9fafbacf7471", + "secret": "df84812be98a2e1804c61b8e472517615f67bdacd7685eb8c8c323c0dce93f40" + }, + { + "public": "fe9e17edca76c43c0e7ecfb264dcdeb1fe97446d8b96f177f693d143f5302cc9", + "secret": "f26a662f0c70648334a8c841810c8f3b3fc95fe019a84d5bc88fb82a56f45b20" + }, + { + "public": "cc5dc005be3e65ca700f45b7ca893749c35041f661f1845f1bfca29298ed0214", + "secret": "98dcfcf43648adb156b72307f63f9a54acf538272eb877e3e52a9fd867bbdf82" + }, + { + "public": "0f2d5446416aa793cbc92ac25df1f7eb65b36ec3c0ac168eaba08c88488fcab1", + "secret": "1d7c38777560d42f8409436f961a69e3cb6aa858a0619750dedc3a123941f569" + }, + { + "public": "215c881736c1fda44ff0995b9400af93f79b34cd11c70702651282c214c6b189", + "secret": "7cb77240ca7ef84e004af650868472c2faf405c79b4a3ef4f5eaabdede980671" + }, + { + "public": "ecadbd70cecd262156606bf672188a703267ad0325482648be4891867b220826", + "secret": "98a5d709bf169fd1aa04e3b756188d7726726525bfe047b8cbff9b26114ba522" + }, + { + "public": "bb61bd676e8a5d4c4b69b43de6b5e3bf716299ffa97d227ed1ea787c68c2293a", + "secret": "50182b75c8a930a452894856a741441454df515b21eb599ed99b623b56a941d7" + }, + { + "public": "5c05ce4b0bbf47a909b48ad5001feeaebcea6238e5956a9a1eb9c886b68b8b72", + "secret": "5582ba833697b947735a104b06776bf8d32f19ff3b6ae693d5bdaa07359cfbdc" + }, + { + "public": "a19c851db427a7aee055d633383178cc825fe005330361f74d42a85501fd3e88", + "secret": "e0f1be1213cbecd33a6131fb1b3cb392c18f30db59e9db99cd4e9534f5d0c1bf" + }, + { + "public": "c1710ad6a09e3343526ca83084d5111af2ac89c965325782a2e4df7aa88f3c75", + "secret": "ff36b642c084749f123322cb3aa77e95f09a07bf968669de8edc38cb8eda52a9" + }, + { + "public": "7f9e7de517cda6ef53f1ddcdecfbe5d6e04c4dbfa97515f6aa9c6fc2d3054280", + "secret": "67ab25c2476f1ab7cca69a8cc80380d7a78a54b4c2f19e4b0268a01e0d2a70be" + }, + { + "public": "72b016a933e2b62a870f56778a277d6810f02a752bce7fe134e0c9840d9a37fa", + "secret": "034d74ebffc97db58c8c7e64439dc9b4142fb629bd4199456db885c719ddb67b" + }, + { + "public": "07616e2c2eadc9be597ed9a1c823fb11f6fc4c59513cea0f414b4348b995d971", + "secret": "737d7614d0e2d2b5ad3f140d471c5fec21a779fe76c52c85bbf622e5e538cfc0" + }, + { + "public": "b5214b37f2d3e055dcf72eb97fdee249e6db923f9f2317f280f7339b672ff0a4", + "secret": "535ac9dedbf881cae53ed59362140ad05c8297f0695f67cdc305ea8e87d725b5" + }, + { + "public": "d4589033c919d9e04dc2d2e3e140c98f794136ff44c97006bd299b2b70508269", + "secret": "d55fe333822691cc6277ae49e70f19ef0c37ea6352a6417105574959f291231f" + }, + { + "public": "bdd539a53e6d894be01cadbcefa28b0ae953768542e4f076573005bdd884b1ed", + "secret": "1f1b4690bb6d1896ba0e3bf2682a3555a42163fac5eb3f1415071a9a3745a4ed" + }, + { + "public": "7729c6729fc864309b8d5d9cb80b3ce8eebfd48df349c52acb52bac1722da756", + "secret": "816a637d72df8636abf613969c16bf7593c7c1d91d5f0f2d616cc52114fb4dc2" + }, + { + "public": "4c457da03f3522db8c1692884e0c49a79c51e5dcd685525dee5fa2c2cde6470e", + "secret": "a0ae1ced08213ef4ea6b758b1879f1eba4672a0162c3442e2d852807083607c3" + }, + { + "public": "81bbd9552c7ef759d05a295fc4a1c9a3c863605737e34b038d937bef09f91416", + "secret": "b54370543db068d55e5b3764291ce888e24d24263e06669c56a1827e4e236892" + }, + { + "public": "143e8929a85aa4e2864b15657115be9f930a34cc67a983833335a7dcac4185f0", + "secret": "a8fa71c66d3b4e2868a7b4f95bace9642563c30cf62560d930ce5fc752b3ec2d" + }, + { + "public": "32f7d06090ab43bcf577722ea58b824df72f6c9ab79ac89e42232994654e506a", + "secret": "fdbcb3ebb07c872b992571a2c37d4d1db67b78860a5a630f41420eba58ced932" + }, + { + "public": "84a9fa5405f6701df920e0e7d4e55504492661c41d901dab50471e047356a2fa", + "secret": "81f51462e0b1d5af397fee4c6cb432f063239b9276770fcab09d724a0e858167" + }, + { + "public": "53517a27ac7bd44f02e3a374c4d0b8cc9d948b5def4f8c46de5c2545717a3171", + "secret": "37d0b134925c9a4eb102c796e56d032af95c986e76be421119324098c85b5efd" + }, + { + "public": "8aad015568568fb4ddb0fe49e8b65f104397e353cab65ceee5148439fa1d5cfe", + "secret": "b971a1839a4cf5671f3e7eabd00c45f0d1c0de1f11127afa695c495c97678228" + }, + { + "public": "e6fc3ac2a6b28ef75848ff53a84b3d0afc85f7d6c40aecf176469346b51aaa58", + "secret": "5a9dada3cdff05d10503128d29c0e827493b015c14a0fc792e91c9cafbe070a5" + }, + { + "public": "b6ff7eb76a42ae86beaee6188fc3932aa231e67b49dda95016e411c996ca55f2", + "secret": "6e7fde96a0923df9ca412ecba5ba658d955639d64b9a8096e9c3b7229e7fd88d" + }, + { + "public": "7a8a061e9d2fd6aafb1af68d7329d85242a5235ab32d3a26cf5b6fcd222e10f5", + "secret": "545e59e1edf5a931ff7353f393f0fd57ca9f4c76cb1171f09876a92737f5fc7b" + }, + { + "public": "29754531b26081e612e3d427fcac713f328b3932eb1d6574a5ddc3c5f5ba6143", + "secret": "2b06680e7bec811659e93e5a0fcd62d39cafcd744fecc20e164d34c9c67a0c3f" + }, + { + "public": "0a8a1b53025a6eea20b5d38cef87643f69a907f766c8397683ab96ac3da10bf4", + "secret": "f6d5fddf3399947b2b8b2f09065cde767eb4170af1f41335ce6b2311dda9ca76" + }, + { + "public": "27a663691bbfea3745af32ac5d86c663f4256662cf9da72ded3c5d00915e53ea", + "secret": "b93a2749d4f51fc21fc53a03daf7ef0f0b8b24491e3e8887f2de2e5c429a992f" + }, + { + "public": "54a4ea8c08bbdd2c835ff578e4971f9f910eddb4469392d8f45e8b09c459e8cb", + "secret": "26247e136924cc13743a59e702f264bf7e2edc107122e15f262d770ff5443ae1" + }, + { + "public": "1d586be202b68595bbf506202c3a6982a97cb0a4b677be338af197f7ff9048e8", + "secret": "65bfb8e8c4f732076a2c28ca456b72faa43b14709f178270ff34cd7fa6c8dc5a" + }, + { + "public": "e9eb4f552668f35c3a9851029b18b9fc531c2359fc802fd93e35e395387d7179", + "secret": "7f11095a5e5fd02b1c26c1472a286f54c2d3ee7d4cd831313c7f74d76dca0f7b" + }, + { + "public": "a5faa44e1561305f9847b90b8fb8c939885733182fb6d726e6a7cd1c27afe968", + "secret": "552b7328447e74d07f2bb14f47f8f084164ef25e7845734c54da52f4a9d11aff" + }, + { + "public": "93b9bc50d87e3acfba8590f55ed58533582481820e282dd7826be649aa4ba6f0", + "secret": "96b7d0fb4c5b7491602324f9ee0e669c36994edb9ecdfd9fc56318744d93fe9f" + }, + { + "public": "427859a789f46bfc4824154b49ce4d15b1742b9175ad4fb5ceb6ae5ce631ced0", + "secret": "9cea6ae10d152ed554383915ee168a41f1a3b31e7857ee34c58e606928adeeee" + }, + { + "public": "af53a7255dd5d1e10f7e51af077af640ceee32a24b027121e8493bdf30e7fd6e", + "secret": "157bd78ebbbae3e94ffb79d3b8a93a241a968b42345e8b180b6d4deb53d372ef" + }, + { + "public": "321bc403a607bb4842c07bb78377ae8a2330ec0c39255762f146ed1ed7a62804", + "secret": "a1e2c8dd68a05925730ed382046933557588e14ea89648b80ad18b121b182b2c" + }, + { + "public": "19c194a1a535aba2e9551bbb869f7359141a6c855f2d8183828af1b46153844f", + "secret": "0fcb84a191f5856632fcefbd709b4d5399fffcef97c480146b69ccfe9b88a8e4" + }, + { + "public": "312f9319753a6fe544065ed1ca032d82bd655d63ed475f0ea3b83d1a06dae5dc", + "secret": "46239685c86b67b34e7dca94bf31a1fb974ae0c798f2976689df57ed4868c76e" + }, + { + "public": "68a09f0a0dc98562e729ba456f8b4fd2611c3e9f96f3d45d5d77bb1578be02d7", + "secret": "535fd8b156af1ae4d0ac541b8fbb185f5c21389bc525154acc1f8062b6dbd71f" + }, + { + "public": "90ffe82d532aceb9120d7e914e3bc86e6fa6b6172b24eb16ff36b2c61f826a39", + "secret": "4bdf76d07e424ded8d6e1b47e967a174eb03cc1b57d375d2679ef51923510a72" + }, + { + "public": "b18ef56b9a7a56e975f708195243827eff3461552bc577c5e242eebd8aa56c84", + "secret": "296cf25998b7e71de5d949031f4937c62d84318ebe4d52e36b578ab100dbcfbb" + }, + { + "public": "b0385b247948549ade2f48d9951c3356a1409d3e3a4a3cc8ad5476b72f3cbd20", + "secret": "f5808c0578c7af26f99f6ba2e184b5581b445fc05d01ba095275fe8aa9bee815" + }, + { + "public": "1726faf7b152e20bbb82b7bfd9a9cabf31e936e9f07187fe4c9193e66258f5f8", + "secret": "c3e09f4a97f474464a1e40555189190cbc06902a17615028e82380728bdfd518" + }, + { + "public": "6eb477cbe8ad162d1aa37ef2a8c980df69b8bfd6673ef075ec7f89a04c200e8f", + "secret": "564bf0d44703456742438b3fd6fafbe318891fdc387aa13c8cb316d0fe245be2" + }, + { + "public": "24ae039379d51892227b045c9e9291899a577601a6fff5e61e7f400026e57b8d", + "secret": "46cdbc0de3be944cc06de5ea656a1768c2aea7bf80a91d1ae98f2dfd368a1117" + }, + { + "public": "864d8bb3053f9df9d87b01c87a718a60ffdc90457176653c8961cfe948c3debf", + "secret": "8d2847c5fb86c059207143fcd5ab3245c2a22107ae023f9a720659f6524d176d" + }, + { + "public": "15acd3556e6793867e51195de9edd612e9732f5215482c92d485124a49b20828", + "secret": "8e501459e7cf2cf4d14f6b210875273c20eeb21bcf56037c9f60d267c9f1e0c1" + }, + { + "public": "42b5febf065ecfae1cf0a8a3344a31c1cbae5508de5e0b708bb958b1c592db98", + "secret": "0e6a9ae145bd5468526b4def913ede0d9a08e765274ce136e1b4cbfdf76e3ba1" + }, + { + "public": "3ff8640d2f2c5842cdd5aa4d5726a1d30880957db814d61a11fe655ed416cf68", + "secret": "70ced9095b0b12bfdca34625325de783b00b0517ccbdf82f6f71ecc2afac092b" + }, + { + "public": "87d56d004cb2baca4e5e643def333433fb21ddb363b8040fe909b9ee208734b8", + "secret": "54264e2b9a0e8096677d75dc9e5fe0def60c9340d18b59ff0bb931dbc166cbb5" + }, + { + "public": "5ba509f00e4306961601a6a698a4debd4786d440f551cd4f1c594c351c57a6fc", + "secret": "bf139c257498393d6ce33d2f7a34baf1261b67ccc0633867c8d6f9a6c9fbad67" + }, + { + "public": "82f1f0d2c125b35710a5c395b68db455063f3774a9ec67905e2c7f0c791258e2", + "secret": "836f14ecd683aae2ff76043ab36263f16e27b78fd9d55804ad024665b2aa21be" + }, + { + "public": "fc0bad12b139600eb1fe6b84ca66f6162af37f563c4f6a58f197e9c6e9286acf", + "secret": "1784eef782477d186815c05417912aba81494610c9318873abfae265b039ee35" + }, + { + "public": "dc70349adae6910642e54375f67531763cca61255a52301e9dc131d45e6bcce6", + "secret": "21a8f8f60e888b5c2f14f41ded7b2d38cc3b7c3f95eefb67effa489f72b1cad2" + }, + { + "public": "9837fd2b707204b712bd586489b7355e65851064e19a3818bba695d4e836bb8b", + "secret": "7445b8aaf8a6838f068ad738f3ac1a1c74d3f34967c4fb61ffdc944ea25bad47" + }, + { + "public": "f9ad6193ef3e28388b2b08b3a22eeff8bc559a7d4a94eefa0924e078b375a84d", + "secret": "c5da3bc44aa37ae74af5b7a8bf520c5dd7bd00655a1a3687bed3d2c1597973cf" + }, + { + "public": "4992d3db99569861f1f914f2a138f6e86affa43253fffb4565bba9f88d53ad76", + "secret": "710edcab75608186050aa12a69f1af7036ec7f6a422fde8cd44f29ec6364c745" + }, + { + "public": "b4000dfdcd793ec85d8deb3da7362b104506a8d378c72ec5af8690e22d071395", + "secret": "c79cb97b15480ed699de6081733c70842d87712d28564c70dce1d0c786ddbdf4" + }, + { + "public": "b2198b51a665dc64ee9d8e9d2d5b1f27dc22eb9860fbab0a2073fd4b74ca0da4", + "secret": "a32b2f730c60f4ff11f67d6d07c1b34ff3f5c26ff18d7ff4cca451e972c64c4c" + }, + { + "public": "fd9f10b09bdb1ad666d8a19806d802532d78c24f9c257a26a4aa6f2bebc63c9a", + "secret": "e343f1d9ca01685a4d2ae490852dbd658b11d399aa10affa16520647b222842f" + }, + { + "public": "d4acd5d34f2e77aa6eb82c6f6afe064e6a0fe66bc00738f3add069a7890d28de", + "secret": "c4dd7bfeda011e6f08880909755c2096b500c3fa1c6793bcca6028c67c0a68a5" + }, + { + "public": "bf6d5439446097c2a0642edb904bfdf57765f247677cc46f96b34e1665b1b62f", + "secret": "b32adad3f7faaddf4da5de27acffe2016a1aa8b839e96af5162742b360a7d045" + }, + { + "public": "425b648cd939b6c43105a6bd55cc5266733feb925d90a51a839160d0e9711f72", + "secret": "7e0e4826fd82d78e75138187fef31793234feb7f799ff5bcd13b86c870218731" + }, + { + "public": "1f7d3a2dfa397e800f84524f1181a9c5f5269e498219502373625a18de448680", + "secret": "9c4462d2c9129c7a102e88f44a46ba0c56cc3dc26680f43adb219ae5c31744ba" + }, + { + "public": "1aea40445636b16d573d713458c0923f64d231c756257feff4efbc8fd3c3f196", + "secret": "80cec854516a2ea3880e36dbe539814fcefa1f1149e312faff84dbbec101261d" + }, + { + "public": "1bf289a1ca52688365caa40828f035a155a0737c2da8c254412d411b0a219792", + "secret": "a701fd6f481db7ea75623097e9f01d936937cf5d400a161357a0188700037438" + }, + { + "public": "2d8806d3878ed2dd8f1fd4503888a75fba89b33caedd8d4048734f011a3669e6", + "secret": "6c73d33182b04ff8268327eccae137dd4ded374ba26299047f2dad847b75bee7" + }, + { + "public": "f152f38dcbe93d6c0c9a378555fb1d72ceae98e2c436c8de77aa50215df23362", + "secret": "07773316025c80ddd81d6f0ebdaf63add12bb6646329c87b866ba0f63a054f04" + }, + { + "public": "883254f5c4fdd34accc4f84a093f5a871447d11e641ad1ba9ac0be35cc496abd", + "secret": "db3035c8594adc1dfccb3b23a33ed5168ef3efe3ec9878841a71ef2936592674" + }, + { + "public": "402603e48ffca1449493b02ba742ead31935ed8007bae9a1ab1d7c96b474fe57", + "secret": "60083a3f4b5efb0e06bd80be5a7c202202320ef046f5326bd97a915e0ebdea8c" + }, + { + "public": "7f49b5b27befd46acb865f93fe490a8210ba65efb1040884429bd65896af3ce2", + "secret": "9d600e73ed6844d701bfcf8173cb31615c6e5eb736d05c5530d8b3e2d509d597" + }, + { + "public": "ace5807ee11b9b324d5ccebcf0e6098db465dad427481581ad9303767163eb41", + "secret": "3258fdd5e648ee3ba55ca34df3b8319244d43b2ab62e1f7436624d46a8c5bc2f" + }, + { + "public": "ede389d3d2b1f3ec6160237d86e97ee73b7cda760211c98edfabfea64a11a7a7", + "secret": "891f2436fdeff49eea25bb6efe5c4feed7bbf3dc9da50ccefce112974cd8648f" + }, + { + "public": "8b3338195c71962f67f5067adb4190cb1b569b501a841b43ac39720a0a918add", + "secret": "fe42e37aaa11ab4b4ad6f0b38b84b95517266e00340699a0778a38f628bdbb0d" + }, + { + "public": "fd1b496101413f52b7b18c69430b4c5f2ef883c4010acbce9cde74861968628c", + "secret": "ab725fc0fa61e672d215603e017cdd181d8d770e03ad2ee040abdeda452e9e0e" + }, + { + "public": "2ba11d1edc7e18f79a58aced050ec3e4e2f536be4cd6289fa96f3058b5c8c7a2", + "secret": "1d01a184af987ddc8d45bce815edbc5a879c3e76effcdfd90c1c50f1567da343" + }, + { + "public": "0d3fd2da74378ddc5204fa69acf203415d5cff2948ab650d47149c7e561931b6", + "secret": "f56075df62cd4600799cb1ad00292edddbc234d921945dc8f8ed180d01ea9d25" + }, + { + "public": "e0497ba379ca7fa0cd941d22b950f72e5471e1c03344e2c6b68bb78277e535e5", + "secret": "015dbb59bcff87b25c50e53c1b0c4a8790ca537f016fb876fb96ea1361a8595e" + }, + { + "public": "ec885c6d757a0020fd5e03604998930eb4e5a781642f05b8d8368568c4d806f9", + "secret": "ebdba5ca4e9ce4bdd15cb38af79ff4077defa95a121bb92c2b4d5ef51090a626" + }, + { + "public": "d9aa7e0fa9c7a3eeeec430976d1479ef31e6103a95245ee70089a338921626f6", + "secret": "a5ebdf2b20d6590982fa029df4f90ef527e7a2db100a54ae5b78fc0bf06851a6" + }, + { + "public": "7b489e641ba959acb50bcb5dbb850a7722ab019177472132fb7a63bb83b2f71c", + "secret": "1cc1f25a812968353d6dd73ecdad4dac5f87f65b4136269ca7a04a1cf0388677" + }, + { + "public": "95ee6338b6d4abcf5aee72fd691c4d9d1f422ad960bc9dacedfb2fe8ed6dd0de", + "secret": "3e06be7a28ff8c209e515d9cd8d738d3d89b167ecf70c11f79d4bc12cfce8ab8" + }, + { + "public": "62383a166f76c0f20e69b1bc4a813cfa7e2e83f51a788e66f866bb967de4267e", + "secret": "6aad85049a0f0acef95419d32d432ed06e8f32befb21d161660ce91edbb054e7" + } +] \ No newline at end of file diff --git a/rust/tw_keypair/tests/ed25519_sign.json b/rust/tw_keypair/tests/ed25519_sign.json new file mode 100644 index 00000000000..e621c8b71d5 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_sign.json @@ -0,0 +1,1002 @@ +[ + { + "msg": "", + "secret": "8dd6ef33da06e96c722b1713d8fc37c433757f0a8e0473298e3b47626d7b6484", + "signature": "2a95145e7a7e1a8770ff6dd1afa0d022a5778e63ab456d3f43cc51e124208bdbbc8558dc882e31987b7c92cdea56e3844d6034fd7b8a6d1db066f931ab0f9e00" + }, + { + "msg": "c6", + "secret": "cd2441869d0ec2861fbe9d4655c941d9f2af83d53397a12f48342d2e6edc9f6a", + "signature": "9ec01c5a24fcbdd97c1984493c5da4593efb8c70e1e404b44c7f7a5a6b6ada26f42a94d33dff3fad900520093001a00b2057acfe8f4ef946ee13830e62dd7a08" + }, + { + "msg": "d016", + "secret": "04b6ea7d9bff177901a0cd4bb942d7b60de4eee9bf7eb3e90b954c8baa189c59", + "signature": "93cbc208ab2cabcf74e3582fcd0a2c1b6c0e65291a031ecf155ce66323130dc4b7159160a6af95d2d8036e30d7bed56726dfc9872f283fe23a0fd7076ee2e50c" + }, + { + "msg": "25ea67", + "secret": "5b756ef685ebb266b8d12416cf7fb2b4e7059fe58c4acab14f350dc77edf0b69", + "signature": "88eba1f7022b62b18d796ac76f88747ca689871153f1889aa1058ce85a8c1def71e1f4239dcf235e126e5225deec1c348b120a6f9c9c93c7da287f7114e4a905" + }, + { + "msg": "82b059da", + "secret": "e29fb6866116f07c05498b16498ce46f27a95498fa0d39c18f437abf413cf852", + "signature": "cc55ca7d0348a0f6262764fa2fae852825dccbe2e2b84bec21d81397a90f914c35f62ff3e02da08946114498fe5e12a4e26a0afa8ea12f09938f9c0eb1d45b0f" + }, + { + "msg": "0a990a846e", + "secret": "7567121d546f346f1fe93af5bc07c0d49f3395227b09e745d96fe4d2c22f90db", + "signature": "80df17bed6eab0f86c7fce7cf1c5fa9e43175effb9a13433d7f5d5d39ca94f127ffb4f000f50f77b06a05e8b7170df7c0dc2054214438cfbe9bcb1c76125d602" + }, + { + "msg": "f0ce777de23b", + "secret": "a5f7954b491df49c06360c409dbfc05026cbe8f07818ce55213ece305724a628", + "signature": "2a9905b9b1be17f2be27a66f78867dadede437ac878cef5dabddd8c52e1f5cf2a8fe7626935f2353cfa610fd3d46289b83cb7f87ce2abafdbe83de7c10f91407" + }, + { + "msg": "62ee5f3e85697d", + "secret": "f4daa2ef4a3a3a9179b92fb707a0675bfa3a3791c0594159bdfa6040d7085dd7", + "signature": "ffe772729a0862b575368f48710b1b22d7936b9764f101ea3e236db236ba227169e808b758155bceeb3f1e8d96673bd8adfd0ff49b0447a54330059501816c0e" + }, + { + "msg": "6c8859c7c376c1b0", + "secret": "64a5c2122e53f543318493133fd94fabc8e0892065c4a4f998c368386ab7f15b", + "signature": "b259e9a976ef5057e1336e0a20a01f8dbaf973844b40498a7ee3fc51296faf2c106c93cdb8528c6f78b49ae5c8a9789aa74b84a58105e49036599e29693c7f03" + }, + { + "msg": "5c839bcb6e1d5554c8", + "secret": "319c5f0e55aeb30d0db40209a17cebea0b85e6e673dc3df48fbeeb0344c423a1", + "signature": "a6670b2fd23c712dcb2930b269cf8e2e0eb9c75c818cbf31c4ed08cf0a979a5d509939eddb7741606cc0d6ac8ccb5aa9370939837c10da1a16b44751a0663c02" + }, + { + "msg": "0d2e1753ef5fd124abf8", + "secret": "63ba7c1fb49733d076f71ae349c985885c89cfd94bb8db238c35ffb4c0ebfe68", + "signature": "fa4a3a9e5ebcea816d91a94e303815af987601f8cd9e5e8310a1757c22233f21f3096327087f3a58931c98f34125646075f39e976880b6f5db29203163315a00" + }, + { + "msg": "875f76161142c2b5f34a59", + "secret": "227ca4d42a516e15219f532f963d7ddeae54af93376c4834be00c26b9633af23", + "signature": "01927eebf7dd6726a010cb97ae7970b4e305e1375dba190a5b3e0c21553dcf348dc1a4706e94ce13d7ddb44a6e200cb6143ed4721400622137c9e215a2c0ff09" + }, + { + "msg": "f750ad069452b4c14e2bc1e5", + "secret": "840b1abe5c185b7c2dc0a203a382b8c9053974984a106861a250add2082813ec", + "signature": "b05d29217e1b3d7269bfd3d64495ed613c59e11b2c17235cef77f26cea51ae862322a62491dc5f6213a1ed0b07d4b065b4ac1b86be79b56745691f55a2f1d70e" + }, + { + "msg": "93c7676450a44f8524ff66ac7c", + "secret": "c3966cd28f6355945e663fbb24331ef6dd380db78118e183bd99107b7e3f755a", + "signature": "fb7855e6fd07ed15ad897fca00efeaf00469de5e48a02d7034ae945313323c7142405cbe53ffed871de7fc034e79694ddf6de9cb8fc7e54d53d4e7c856826a09" + }, + { + "msg": "5a6d4d3bd03e43d28eca2890a930", + "secret": "51088d26617f8c64e39f14a5df91e15514099193995096a5ccdf0e83ecf5fc3e", + "signature": "b70af6f932f8daa0a0090d1228c5f7da23a185d233f8c6642d59ded06d0a5bba7a94851763ae2b9c9c6ac333a9e4e030f0c5489001ffea58008d73ff5069df0f" + }, + { + "msg": "bf8ba4db0cb4af150912ea82711887", + "secret": "cdbe89fd5d96cee4ce468a103d9843dd8183aaebeac7d43b6fa872e1850086fa", + "signature": "0cff7eecf88b890368c5565d57c3d481f00f7744702bd517936423b297ce84713571d8032e41f3660eb8e0f032c369efab9ee61e6cba976952a112048ddf4101" + }, + { + "msg": "f142f7ba1a7ed1de48e13ef40f30f192", + "secret": "82794fb0711321f9127d14e12c9e32cd28184fba3133c20fea858f8e3587e5ba", + "signature": "3bc71a6f81e9757ee19abf288478424c2fce64c31a517e4cb30eae98bb48dfd14ef437e11817ba5fa420d88f50372a02a36919f819c190e0ec8985348f5afd03" + }, + { + "msg": "667b4898efef75fddf6d655ffcfadba0f4", + "secret": "bcf84795c05f515c4ff06605d9e28775c21ae83eccca11e80198de65fc54c0ad", + "signature": "cc1a8d06f196bd3c3bebaef63992b5dabc265114e92492e3d2b43da542ed9e9b620fc5948f1bd13a77ad364c9437b2df4fa34b9efcfa99a058030dc94ec40806" + }, + { + "msg": "6b918baf8a8b3dc33be3c46a107e2c9aa12b", + "secret": "081a0a2fd3f63ee8a2f73f25965995cadd85d0972910f8e35136e709f24e2c86", + "signature": "92cd1e04aebe877577cff27218eb4f7a1b0c8e2ea5c6e794e0968bb79be4771bd71d0056c6d369e53fa063115be313ff9e6c05fd263c9e4059b1edd4a980fd00" + }, + { + "msg": "4bf4b9ee844a1dcd330621a6a059312bce6a9a", + "secret": "e47b480a81a6c417ef4600f5fe34618f168db7a1c9fb1b6d5b08c02bac6ddc6d", + "signature": "af7aefc983c3c538bd831f85fba47266154c360ba05c7f4829f9c6bdbdbda78704f6a668c1ba14c96310ed06bb752d87ac2d994bef77b89ecbf9af478359f403" + }, + { + "msg": "7df06dfbe5ab9f88fb522a1c7afed1659e88835f", + "secret": "0a9e7cb7f9eba40a4a6b439166ddd14bdf66a8072391cd3c7a44acb4597fa8cc", + "signature": "f45b24a08e445b67f2f69f1d0e7d287dc3076780b53b3d494f00549c645f7ebc640bf7b2663608604a19d1ee94da5a0aec031cb6078c576de91cceee72919206" + }, + { + "msg": "2a51abf6178066a9260cf62342bf870fa73588286c", + "secret": "256652f61917d25d7d5724feadb1110c9bfddcdab3487e683ba1fc2dec4d1a1e", + "signature": "63a27b4dd63c5ef94dd44737e0d782c4fb58a4db68d3b4a966bc27eb72f493b4b529cb89f67145f98f4672b4746c9736fc8eefa853315b8383f6433f78dc260e" + }, + { + "msg": "81683774ac57f8b8b03ca9d8683e328d3460ba8715d0", + "secret": "71c5b75467ae24601ad94a524e8d0c4ff13516a8e9f05bedb968872e897402d8", + "signature": "f5b27b056cef1db2fe3cc7c24799c38502d97fce0526d2abc0d5009003683d0e998b0594dba11f9a5d2e327716648c1ff215eeb84312c1da40ec4b4cd00ffe0a" + }, + { + "msg": "eeba6aae127be29b546d39439a579058f64e0174a6430e", + "secret": "9fa9bcf42eb2c356bc961370505564bd1a4e918a642921c4e23af582c12dd341", + "signature": "c8a6bcf0384aa050b9f1b67ec08c5e8553beff7d6e512388c20e891878ecad23a1d29b90de4047d918d43379e6d3a6d93974ad2c0c8958fb9d2e52416f4bcc07" + }, + { + "msg": "c0da7aad36b7216e9935d1e9658ff69495d34ade2a8c5849", + "secret": "2605d1a0339841eb2900c700376ce747a90d1768bf8ae1d577c5f10e807dd413", + "signature": "329887e0efe871cf73f79c1dd75404053c173196bab76d9e62cb070a0891ec34550f5d63e92a033358974a241353e1dfd1df8c3877bef31a4c5c46e06658a60d" + }, + { + "msg": "8f6629b7de27da091032757831ae3dda4968e3b040bace8a87", + "secret": "3629760163454b0a0c04184e4c0d2f8a2cf25b60715e57593c240caae4cfa399", + "signature": "5a9a72a1d1cd467911afc9330769460fa6a8e586db33e7f10733b19c73ef0bc98a12dd29d719ea1101adc7f951ca2d255c7742b6656ec4b9b3e9d99edc7dab03" + }, + { + "msg": "8942095c16e6c7bab7e4e5e61d07c72e79384169e0ae9c36bc6a", + "secret": "b365dc7797c0f0728509f33988b8ed9e25cb5a00355354b41eb9efa71a3497ef", + "signature": "e717114f89d3046c90fa87e4615216a8b7b7c0b9c472f259bb3d02cfd3487777b3cf6200d72e98ea7c54f18f2324e84a89ecbfdf51fa6964593c4fab540a610e" + }, + { + "msg": "16d608210763c7bb39d3724cd35ff4b8fac44ed368403e5c6801be", + "secret": "0a295916f24c097a6b5d64e4269820a1b7ad1dad661d98d61095570c2db23ba9", + "signature": "b9690f5c96f1b7129ef9f472d99455c453f6398c5e5fe62f5a63942548dc55fe63a75070afa95addd001eaa15bddd34de7b20a83c4f2908b49d8fd359f152f0f" + }, + { + "msg": "2ebfa288a5ec465ce7cd1b52a999620bc08998e1f0db12b5c68d7612", + "secret": "6ea5f0bf609a40f579893334dd028ce212cec192553834ab6ccf24abf6af205e", + "signature": "3358467a8ae6aa8b4c3c0dd7032a2bb1d0b245363fa1de5b06f24d87eb74ea287686060eb423b4fc8e710c3c0239d67e0b48302b05b2d094720291552108b605" + }, + { + "msg": "7695f72447610e340471b13f65ee3b7d3b9c885d02ac7abcf3de551144", + "secret": "537ee6ccfabe4733404fb328e70c0088414a021b8972e3e6fb6656bc8dd7457c", + "signature": "35294b50523d9e5a2d67f56fa626ef91a5d19e376c2dbd5de5f7d3a61bc0045a02f2a5954040293b399b8125748919b1c3e78a239284b23701dc45655230b60c" + }, + { + "msg": "0828b5242506ee27fc35d15401651429c47b77514908c51b3860eeb7f75e", + "secret": "376e36bb1901ad83d3c56f2406b8e1b0989522fbfc37b52c63e59149d0c3a5f9", + "signature": "8dc3b15ab6a95106ad66eb15990146498ee5acc648a5e2c3a2c36f97e5653e28ba833e97d2eedecabd5b85d10c22029029c82fb3378136b6a2ba0dc5f0a7110e" + }, + { + "msg": "1cb41cc3b2afea37a6d2eece7e165a2637645465eab95621da83ed5bc2a55a", + "secret": "f3da9f295dc166e8e1246cff3433701de5a3a43f166935e474ccc9df008c4b72", + "signature": "b59b731561b0703826291e5d373c0eb19b65812d19866370defed730eb7b76b105061dcf3f2a92b4b7f70906d0f8278d4fac5f4df5a21246c7d0fa1e37bd790e" + }, + { + "msg": "06414d0c258bcd528dbf1b94d2d5c3eb74835f5bcb7c76ec266a7e4a73fafa5e", + "secret": "3a489cabea2821fe32fb7e742fa17969c08ee19863b799e6468441c9f9172eef", + "signature": "47ef5b0e4ffca143df7cbc49a6d408140dc6f19ebef6f87f5b33738c4600468c121a3ced5f721b68cf57929a28d8857a082add82e66ad41aa1ab60c7a272d90f" + }, + { + "msg": "8c0d0a5045dec601f8b43cdb01aab66a439b6061c8e4379aa160c54c5e67006285", + "secret": "2b2949d066b71c54e08a405f3600bd149bf7770ba938ea0e06f409e067b5fbd1", + "signature": "d2e4579a7eaf6620e36f789d521dc9c840a8326041a1b8c0d8db8892f8a25d8785f6e79ef867435314f28c2701d36a04ff75fb02ce15fb57589d602c5f0e8d00" + }, + { + "msg": "bcb13904e8e67309d0a9d3323228c4c9401616c3039b7a68c438b7a67de5dda186be", + "secret": "85bf6f184e7fd19cae6e732c096fe15163c88c7f81763db971fdc98c2f9236e4", + "signature": "efaf4ea3e499a822f00d89873ca5040efb432b07120a3ff54438e685f8c1e9c0a6ac4309a21da90339fcfc5b32a0f08636b5df6c6f9e61aaabb34cf82af8750c" + }, + { + "msg": "781bdaf0a6c9e715f92b89cdaa0452afaba3893196257eba17de9842eac2d7d8957e58", + "secret": "f97d5c2e8ee8504d1cc832d117bccb1551937bb154ce19e7ff58a7a9f56163da", + "signature": "22cf8194e6372b34aa0074edfb7cd615e25bc9e836a0485ad64520a1f377ce804aa7d37f5eb2cb90de065cbb5f453135da5eb2ed6735dce066d6a5ee888f6806" + }, + { + "msg": "37e2f56df27983a2dd29a985807c91cf95c2214acee163baeadcafbfda4ffee2d819b278", + "secret": "3cb50f9f5b533c9704e63e405e1628ed44c121cd460729d33916a799072bed86", + "signature": "4ba2165c827288a2c5240dc441a47dda5b2e647a85665d291551012ecf4c5b3fab15303401ae0633b53e55e7a29f52b6f36aff0d5b7cdd7bf12038e22f84120b" + }, + { + "msg": "73c5a4bfe16271284a47b824bbb3bdc6335b07e39129f2067be262a9386fa2b814bfeac0f0", + "secret": "fa3c3499a0302f5102ace36bedb0dbb0ddfd219a4dc4ab15d2e40c2db1249c95", + "signature": "8da03159ee33c146d2ab53d3d43772e888d4fa48f757894889ed3e1f72d822b093bfb03adb5b6c9ae24bfbb6df7f6164cdbe590e49f91c32f31b8df223b96c00" + }, + { + "msg": "41caa617505f42c1fe1c176872eee594842ea18e56736eaa456f1a4c10c0dc48e9c6b01c3856", + "secret": "05b20a53f66f57c09afcab2ee77fa847b09c7412c411c9861bc8a2658174e6b6", + "signature": "c4e8dc971bbe6acb940585e3e5710cea371ced6ec5944aaf0d63f59d982658fc5f913f3db3d3f2b9acf4992271bbb18f5a726172d39471fc6de35dda8cc55d0d" + }, + { + "msg": "011b110540b5fe4139111ad6ec80609c066f6cb5bda86edc90b0405afb90445368da7d93cdb556", + "secret": "18cf6cbc093a428f45ba21d1dd84895aa8a006cd81120e8fbb8268a535a44013", + "signature": "bd1a5f7826751f6aaa5988941f8b24c51a17411ad9f214ddf7430b0e7f4bc06ca88afc9fb6124384b4823a66d1b590519dbd96ed01fa2527da4064c97389770f" + }, + { + "msg": "2280f940fa3977a43a0ec79962c54abda3c385c53092fa96473ddec16135b1e95276ee83d7eff433", + "secret": "ca4040dff4fdeaf19227e5a8dac943ed903e82403fb34cefdcb5f22f92299ba3", + "signature": "67a7f3cc6bc85d9f6f8a9ab098e636bf67db25a6b332ee34b844fcf78fa3d36b5190d3ee1d8abe5bbc779746c874f3160057b5c8a92d4e35ad3a4a8845b97405" + }, + { + "msg": "c8a687a4c16b3f7ceb409ddb23266fb4e2b01c83fc749f46167a5b92060b13c96df5f210c9f1cf3018", + "secret": "a360f9fe7246ee3a7d77fb374d1354f09237ce7be187783b02c433fddada023f", + "signature": "9ca7fd0d092db2e787d3f1b1980cdaa76fd7c3b1bdc00d255bd5abb9e322a5eef97ff8a66089b4e52246ce8d253f5cf5e0dc79828f14b6da9650e733ca6ed00d" + }, + { + "msg": "66bcda10b96fadf1d5b296d61791822830255bd46733c2036fd97270054da098cfeb65bb706c9ddd7756", + "secret": "68dc1dea4daff83e3fa7fe391fc641c0c97877a7069ae429af1c099164a913b9", + "signature": "eec7efba92fbe8048413b1d30da8f1be96f5175f95384f384ba92458777484576f7e0f01ea0ccc94ca743a85c89d7dff158e1a768ef0765fc2ebba4298ac0200" + }, + { + "msg": "78098fae674987675e3cb14d8f99c1bc1be8337fddacc7beae03cd4afe8d096d6d507e12e4d1378d85e7df", + "secret": "8cf7fe80c7b47bf7314cb37758ecbae100e025accb727d0f3c13b0b4da857901", + "signature": "a3bc01f31c07d93fe5d9f83e9c1eac7b1c515c6f317b89bc8cf20809efa3539fc38325b11b4956e80a2c04018f44db5814ea3aa7f089b5633b8dd9b714d0170c" + }, + { + "msg": "7db45788457232e289926f233075fd58a9cc6259c869d9b7c8901c17ade364246b2b20c35084b67fade6974f", + "secret": "318c522d5d435e45697197ed84c370b6d736f64099d0799db226629f6825b099", + "signature": "28753cab1a0696e1d69aac69a506c61daee218bda03e68192456b4f2230ff5f6149d421b74a5cdc72733d4fa42e4eaad56dd956100e60dd13d5f5c933e02f407" + }, + { + "msg": "151ebf6e4db9ce55dbf57202be2336aa846927ea551659a8ebef2f558d47c8d6466255b21bc1345f1b0e6fea81", + "secret": "4b7208db8825c304c3c1f4b880321c8d71108fc1b959044caa626b75c8ad1274", + "signature": "ec867e996da0e0e75474b5e6e765786b05b0a735d00fbec0c3152f373f683064d8673a3fa48a2d87f1b5a24f1e269a9eb1b4982ea892b09855fcc2f1f306b50d" + }, + { + "msg": "c246712c79cc804213faab3942f7d82d800f6d987c1be494ce0da4a0e978ba7f828dbce039527c41dc4b48a644ec", + "secret": "39ece05d86a5f1afa50013fe7b8a4fd2a106d4bdb32225afa8d629f79a316fd8", + "signature": "125256f67663cee6e5abf2bb8735a0673f7730ea576922f014556a20d35f3ae87c2fac84edced17d0d59120c6f2490b6ae10228c0d5c8672f9ce78d05dc21301" + }, + { + "msg": "38badc92e0ad6d1f5e8e8ab18ba94bd780b241f732796df585f1845f53261840a8dcdcdc6563e8356f008209acf1e5", + "secret": "48f799a31d8ca682f716ca3761b85d778dab145e40b0dc0858f62f1ae7b6f6f5", + "signature": "09641c50f14e5485a843068159fae0ecf5bf12d0370309a2b1b173fc486004393f4b91229a14d8a359b1d14931d6219f487ceac38846982b0cb1222a8abced0d" + }, + { + "msg": "db31b85435d449c945f31c79e62a7aa411e1b3a8e689d5fea52c0a3031db5e97cffaae524475f787f9d2f57c0d4297d4", + "secret": "b55fde1435d14c89734d2fe337c1ed7e59bf1c9bc86ca427a7eba44b9a7eb1a9", + "signature": "d7498c00aacdf16cc650ed728c2f2264f38c16f2425b455a60a9edc866b3bafce2cc6c1b28600235919454a70cbe0c6da079e7420a6e6d89d0b3fd6295cf7a0b" + }, + { + "msg": "85c2d8d5cc2c2af471a43e4e748c1a101ff1b6304bd5ca9300b3f25a9745e4cb5975a954cb5867f759b1ae1aaf02b409e9", + "secret": "c3f0f18bc37abec83d8bd9440c214c87e550556c27f64e3a7a0d7c2153a979c5", + "signature": "8beb901d635fc81964858f9332294d42ed25da989024ba80f7e55e98c11a590fdfebd1827b0fcd3c9fb2636e71d2bd317c6c1fbf1468edfe7448d06db702020b" + }, + { + "msg": "75ccf68a025bfe06077b60736ea0e9e0d98db383b08ed73c31e27a0476e81ca5a683e7a24465aaffb5b1d18cc66729388c54", + "secret": "678be044bfbca878fe43ac3e4dea0a265c27b1c178a6f398ae1e5ebd31c0dbe2", + "signature": "cb67d9700e610851425b360ac4ad78db451d3cc90601a73ab5f897aed9cc41e4cbfbef7756c0d80b77105416efd4e75247e9f7764f54eae01932ea65e1a56004" + }, + { + "msg": "43", + "secret": "54d89ff3223995d76d1d798e50a81003d2c7dcc3c5e9bf508f1b77cd39e0e6a8", + "signature": "f0adb50c013eb43e44adc62bcd77a90951c7cdcabec08c51dc69477357fd98e372086ee3e5801a24b231a0e65cdeacf3ac7b609497b6dd68e0d3de06a7b57401" + }, + { + "msg": "0364", + "secret": "23416d7d29e191cf20c2dd470193708bf55824e595079cc45e0ed9d2cd450191", + "signature": "bcb7351cabe1eabc978929ea77b70d575cd1fc120dd3ad78461d37b651807a25c0a6e9e60a8e35194582fcf93e2ad63b0e3d5ae0d8f6b5e25a06a50342194206" + }, + { + "msg": "ca4bce", + "secret": "b63e2616056ae9b44f70e901acb2c4307e2f262c098102bd7a4713db856996b0", + "signature": "041d0864b49107406b0a5ce194260467098b69f04edfd23792f62ec02376d794d45b5adcc99f37a9fc269d182947350c3d5b8d05df1e101b616540324aaa1400" + }, + { + "msg": "94c48be2", + "secret": "694acd5f627f2d855340c52074a10ba2c8c7c53e75868cf52db85f437625b052", + "signature": "13a3c48af63d023cf8937385af036f8f3730b622f77a97cc4a71452d27932b6c62f710c34c5161773090cf5269ee8cf515f8b0f34e1952f8f624380e0c3f850b" + }, + { + "msg": "bfb861b909", + "secret": "a25499e66880ca20512ff445a6ca3cf16acd369b630196078cc75cf8e6cc9fe3", + "signature": "2bfbaea94bc1dee8a967a09df9e3fea87aab5ac1115ce1fd9a3890d5c2f3ce75359761b5740bf00bf613a42ca7b0cd93bf89dff9291e1bcbc1170103631dd501" + }, + { + "msg": "9b3dccac151e", + "secret": "3466d9e35bc3b1135a563892624d08b3f6ee86b8461bb1209651634612e752e4", + "signature": "8c2272bea4c80e68a2daa33e636c353eb5e11b2f6c7b32ba7163755b5c7a07ccbdf36cb35e8c058f69a17a0d0203137aef38f3f5af61417fcfb48fefc30d7103" + }, + { + "msg": "21e978916632c8", + "secret": "f2d9eef29244041afdea72c745d0ef18a440e4c7dc1b011a8532af0c701fbbb3", + "signature": "3509a8d235fa4c7160e439cfe7cf70f4b49a618a39902b49e6e19dc724bc7b3dae06e71ccfb72857e4461243d05b495fcc89cdac806c40d007bea9109bf6d60a" + }, + { + "msg": "25fe15e7c3690a7f", + "secret": "a1249e96eaffc4befe10161e6c74f86c7fbb3a8f534f26adc4ed99e28a8b0950", + "signature": "fcdaf8b18ef85b3d0a38eb211ebd10cc70d8e7a41eba00c1db856ff6daddf570c85d5a55eeceed45de90b2e40d9f1544c6b77f964b845c6c3cc9856a84357e01" + }, + { + "msg": "2dc63f311fd602e106", + "secret": "f8dd1ce11564789c3590560a7d48c01dd623ad5da81c19af2f2363fb75f9de5e", + "signature": "79b6db105a3190bc2c59c3c2c8a0cb035b68695667f71f7a660a8318d81eedd3d0680ad7ba0f14f1039dbfc3dfc3cfc4725bc061446464a560d9c091d7a1df06" + }, + { + "msg": "4a0f07c45df858eb4a1d", + "secret": "a2447f54efb6e2849dd26335a4596bfd8eaa796a281c59ec0d3ab5906437e33c", + "signature": "62b659006e7c99c05963942585bdf2cdd0dbb55237ca97eb64938d3a7f93c521b0267ee8213047095957ade55f064305f150980833f49dcd6230830c373d2c0d" + }, + { + "msg": "2a27f24b7df8a0a9a717dd", + "secret": "97d09512c54ab27a0164ee3a6b7f16280b1922b37a974cb5c247c71b549ad90e", + "signature": "aefcec59d7d79265e40ed9c8d039435b1e9562b986f03fa150d77bdde70ee820db5a1cd3ab781cf28aee95a57ddd183148e61d0c5830b998f2e13906929d1e01" + }, + { + "msg": "74a38e91890d5fb09f11a29a", + "secret": "79b323adaca0bc86c1f1725f3b9da970c419fbc40f4ef8cc1d4de7cb1415653c", + "signature": "528956fe25ce87f4b2b91d4d44e7f19a8402d75aa8863a80c66dec01eaa92350a3fb4ae5006e46de527ecb6383d772d44dd1a49572592948a80ec21d76c9a903" + }, + { + "msg": "709e7a14f9a3f4297adb78b6c1", + "secret": "011a262a6bd202363dad9f3fb901cdd474bce7c459b7c10c41330c35380e8612", + "signature": "5bcf214b4b18f08d9655d0f3a6daf708b28a18a68f882c52e85a9a13b3af26291acdda316dfb9c977007b648dfdbfd8675ebe41ac9e2ee01a3dcd1b15612e206" + }, + { + "msg": "ae2f4fe6a5f6d374665f7f3b3fb8", + "secret": "3b1a8d1f2c0fe6baf87217b5d8d7be2efdb3190507c32da0c2789b48c2df31a1", + "signature": "407dac6b76b005d7cd637f16cc6feae4d0a18db2de05c024a03cde993d7b8fe8f97dca27f7a6c7d93589fe0385650ea5487f34092735f124becb1b417e9fd901" + }, + { + "msg": "97c4063cb4ce9b3c09b780b8dbefbb", + "secret": "0c82fde63e277c4aa195779e372428d80bdab2f6a1f7bbd25cce0415f1530b9c", + "signature": "048597cff9613c1489b5276e2f84fc6e76a97a0be22d73a183ba61a5b089e11f578de0af29d638d44f52ed5e43dce4dbe52e2c3dfe9268700eae32f2003fcf0c" + }, + { + "msg": "5cdcc41bb1149f30e2b74243fccb38b8", + "secret": "a67c59e72448b689d70af45c67409bd8a3b6ad8e969f516b437b33009d851c0f", + "signature": "7781bf3546c1744fa4c45f58afdff98635acb4b7eaf7e7aad67702b026079f3a16100bcdead6ceb6bc35ef521c5280527345bd8e4bbfdb1b54119e79e435e50e" + }, + { + "msg": "c3c18779101e4eef980563fb6de790a391", + "secret": "44be64902fd6a48e6acabdc0a7c63a01a2a39835532be92e010dd07ab37dda7a", + "signature": "9dd86307d0babaa8c3a062ab9027d7f1a8443773428b554357cf618951738acd4e4cd373399b29d3efa9f8180bebe2484ae2184a2f70cd3da9af05c899742809" + }, + { + "msg": "1d855ea0f6a73dc545b6bc7203ac25bd7ffb", + "secret": "35a2c0b046a97dcabaedc46e5cfdf8e02cfa6c17fd09460e99488e3bd090ee6a", + "signature": "86235249830c540e618c4f477283542133b05596023b648000ed56d73fd96aeec42adb644b3b2defcb46d48492373accecbcb24656eea9f0b6232f71a05f2405" + }, + { + "msg": "a290dde5b399805ce944f23e19bda7296afdae", + "secret": "566c88432aa96d9418523afcde9522eb1cf6075e8fdb406ccf70247678951a73", + "signature": "e525d95a8cd4c8c75f78aae91d91c2c328137c044cb85d41715058186a89a0a090d76066d08d54a7f9f73d41ad3fe30df7425227a36b5aceb556a36180e55e00" + }, + { + "msg": "2241151f49e6bbfa60fce184fbb437143906b8fe", + "secret": "bba27895cf0a248dc8f5348da5c4fe6ba24b22e2097e76b6b8b2d6481a69b0ca", + "signature": "a4ec13e88d48c960640c2460e8e6806a6f746506452150883c1f53be4b6e3719280cbb5b959cfad48fac808af68210adc269349c5a03feaebce1ed798aad1903" + }, + { + "msg": "1b6c387dc3fab3a92e2355e202d901289d820a0876", + "secret": "08ffd4eafbbeedfd7e56132c3b7731f7808bf0cf622709427ec9c2c6509395fa", + "signature": "a4c02b88f62c81af9a60fa490a1c68cd06eb4e03a89c0b78d72d4bbf4766650662e05ac72f66ca62dfc1fb8246c0737f901453688e7566aef81ac8de7040ed0d" + }, + { + "msg": "0d82a964cd3f161473bebb3d8655dbde6c8aecf1c23c", + "secret": "91979109d10fe33d89f0d2b978b3e0b387bc5aa2ffba452e59a7a817a14a7a6f", + "signature": "7de8f085adfe9b97d7520cc9b37f5a2658ddefa0b634320c0610587ea0ac5c429f9f086122fc0307cf287a4f5f1dced5c10fb6e26b859cc34ce4a3232e48fb02" + }, + { + "msg": "1e1d6923cf04c20ac5a7860e8ca8a7116ed463f641be87", + "secret": "4b10236006c09a9d265d883c2f898afff749ec5ac9c9ac72196b5795f25d595e", + "signature": "a107b46ee77143259d1c39c8605376a37c12ed6282aed2e528dde2bad10ea690f48014a7fe1577a54f276e5d10288babba9989b7fe3eb4566e1642cee348560d" + }, + { + "msg": "17ce2985c575c34c23b40b1382b921f7e6479b687174cb47", + "secret": "6302d80aeb5a49ce7d94e9fc1b889a38be53e6acd3aff95e18cc6175a8797aa3", + "signature": "46ff959a44b2b94c6728107369e13b4c0a1b29e68a99f3a4a7aee52853caa3ac3aaf15079d811233ab0145b5c4cce2e7c840a5ad713932b681d8fdb4450d400b" + }, + { + "msg": "1649fc9f0aa3da92539b651fa296ecc2b6120a73cd1ec223eb", + "secret": "2ae3ab00387b456d7f60c59e9804346448b24d41628b268c817da82f67fdae17", + "signature": "8ebdf5eb775f604f58e5301c2b16e860b7283ec20d9d246ebedc024ffc6ab2f2e49901e29aefec015f23ba3db817b6a841c71ebfcff7c77ea94d8620b83ef80c" + }, + { + "msg": "d75f6e80e1769db59e2bacad0fc81220e129b1e42b33327acb39", + "secret": "b5c669157b5b429a84ede94c3a75a0b90ec983cccc2f853f7e6364f53be8c506", + "signature": "9b5ef713706aeb97b9db048ab0e74092eda548a39693b103e5e635997a084f15b61113119ea62d984f403c60b3b422b847c0fe32e961e4bd109e2f845204de05" + }, + { + "msg": "31abe8abcb2a78d6cf5517a51286274d391c988f181d11073686f0", + "secret": "f51c6e3008b764eb025de90e7882332c76dc69842af9e629c2e1afdfe64804ee", + "signature": "ed3d153c540b2d35bd6e64346e770ddd401adc634c1b953b4fdecad5fedcb2f79f0d7991e9d8e1d9698ae2e495ccb9f135df433b448a17f57aa5852382b9710d" + }, + { + "msg": "c8ba60396421e4e3d1d7612974bbe1b3269beecb4c5a4275a69687e9", + "secret": "aa8b83d0b25819dfab81eaab6bd70600e435a877c82157ffe8f3c6e54e516cd2", + "signature": "0813bfb057df53d3fab832c7be7176bacd0d9adc6286ac69b5df6f767215de303206bd1e3b08f120e988fdd3798ec31a096b4673bc328f8858a7ee9e41b3480f" + }, + { + "msg": "683dceca1a3c0344e8774c81011ddf069e1da61e45ac1fefc28f1ff1f3", + "secret": "18facbfc852be15e6aff08ea7f93457338c7413b4b943b42c5cb682b87760fbf", + "signature": "52227a344e114ad9ddae069a17236182ed52070979635d21063df95d69a204a8279bc9ac9887720ad219343896ba541ffc2a037e60a218c0a9735dc9ae74e702" + }, + { + "msg": "6084879360af5aebb199d482159fd9a3d2150e2b381ec15cf9db8a4c5972", + "secret": "f32d51b3634fe9000ba4e2ab90ab16d153f1005a520e040c4a51a7027ca84cf2", + "signature": "50335df038d2812a40c9e528576c661069fac654e84a6e5c3181d8eb33e027c60611a182fd90d22dbe7638147af0fe9209bb06f1f761234b08e8b5637740d30e" + }, + { + "msg": "4e20838ba703bf1502c7475e26ec14878797ef717ffb2d210353418bc8591a", + "secret": "14effd602de27a66a74ff7db1f6318f95dd75cae60f1fd93aaf83130d3c169cf", + "signature": "1f7b52f16f3d4195a266c50812048cd210d52846a3cbe81612f68e580b3e750075744df32ae436f2aab0c4a48f605d5ed69c7a1149d4df9a0ea4e6359c374007" + }, + { + "msg": "35584956de0e934f616168725330b58e5fbded673ef6712e1e903f1a7b035c35", + "secret": "3a8bb3c33f88b0cd7591febb01cd479ae9fb28da0962e7484cb04cdd1c5c8631", + "signature": "4b7c2420f4596dfc04392d3ef1978ae3b5c3eb16fd1edface32501e14554a7af48b31cd28e18dccaa5d6920e6aed500bd4bd6727999ff62080f9feb1eaf72e00" + }, + { + "msg": "1212a4547e1d6b339723cd767dcc8a0418a23fecd760e817c875702abe63511811", + "secret": "329973cd9426ec5600c2726449f49f9044555d20deb0d445e22e7afc6e45d6df", + "signature": "3d34d3f2b683c6ec21827168853aebd4edeadbf6b96a088436c6f3d373f292462aca7d40c5b3f32c488a4185e6afacebc9f277cd0208f7aa419421d516a99900" + }, + { + "msg": "4a5b6cc4be03b76dc1cd1c30191e37b20656c4b3928d699474c7fcd7a477f74edc7a", + "secret": "bc6d545751e23f3ea638b8b09026513bd528f751eca0f2275cabeb9321bc32b1", + "signature": "8dde04efd3963222e82ca08af356f290fa3f64ae17fdde5cee4edd48a9891291f96d092a4504f1ee3d70dcaa90b3f22f19b4b8e7d0694cd77163092579207f05" + }, + { + "msg": "5cc9a368fa85ac86f7acefcba10d101e9599d7500c952fce14000c698ff270ac16751c", + "secret": "2d03128f2c834a4a0d511acc63e262aba71da4a60a1ac5e21d7f5135bda323f2", + "signature": "901f95738693b10560c28801b1d8e53735f9379b1700adb3547cd9d3d5a0b7239c4d56bfcda5b198dda6112f4697f6013fe4e24de680ac8332ba25570e3bdd06" + }, + { + "msg": "e5fac091939c429e9e0d6b0b512b5224e0bd041f33d5b643466f1a5daa0f04c0a75ea2a9", + "secret": "609963efe10c2607e597161da11611316be1549373a3c536b5c3105d45dd22e4", + "signature": "275cb11e15dffce85958c60537a3c2b77647f56322b5e5a37d3127978082c4765eb074d3399616ce8380f5bb2101dd4706970bf2308695ee9675b87d724f4407" + }, + { + "msg": "8f5e527444f6178502f208038085a94fb2b5fbd1ff2080779ed75a6cba2eb00c4a38d90d8e", + "secret": "e2c35f95b722d20ecfe06d7ab354135d157f28674348808176b0f49e1e5f9c5c", + "signature": "611c756206df97e19f0a59c1baa3932488405fdff87a72927c03d48aa6ca87005220fd16d19b7d228926daabbdd0aa9c672027a14cee9ed33965175242fdde05" + }, + { + "msg": "933d46836b7c3054e19831f8f5d06f8fbac86ff4a1a382f74aafffa99ad83927268942df956f", + "secret": "ce2566156ccf317f0c4785448157be7a1c236206607c134b4bb4c9a782ed530e", + "signature": "375771c2439fc8ade1955f39a3d371a21f9192bd61b7df8bb2eeece0d7e16097f1cf0c9f1007b90f8a8a1278bdf017c90a9313648e5ee9f8b6e793bdc6956f03" + }, + { + "msg": "8892e4c591c168c0dc15b68e22448ebd39307f02e54b1069de807676f1e4df3c2b3faca58ee0c4", + "secret": "6eb5ca71acb24e7efa601ff2d4da6d717ac3c7b5198e31439ace31c51eba62a7", + "signature": "ef2465777d2409a8a34a7af709338956ca604a0c266b777a486a4f8f1786d1c56cb8732e83e9346ab8c42a7e4856020ee9d1d2d6e0c576bdb6f5be7c73f5ea03" + }, + { + "msg": "10c607f95a8a3ffa8279333795759b6968893f22c5111d09426b9d2677961657f4e8e48cd2409bb9", + "secret": "6a7ab95b292ebbbf46b4d8b48773cb5d3c0c3e504b765e96c160c316450e038c", + "signature": "78780903535fe0ec5e8ee5a238f51938df251d0d048ead166ae271862debdb8c13e9db174a7a73090ceef86d243998566b5818cb435c912f3fde123abb50ad0b" + }, + { + "msg": "b4f2255e6deafe7b9e1fcbc2876f76d8f620c245124d2624dc9d754cc631b2e173731f0c2a17d22b80", + "secret": "b7f1c7aabe8c8fb12038e2ab337ac1bac7fde354f80f68e55b2b6afde104913d", + "signature": "820ef0d1f67e7b103140f82f907d1095fc4ee1d48eb2fd273e66f8a8621daba538e0e9b4284b7cacf076f0e0b5cf9c44a7d4b8d732240a5bff5bc28244dea00e" + }, + { + "msg": "0d1200b034bc24d0899aee8435ab1de854ffd9463153db6c5a8b7773595d12ad4a9d0587a276412974ca", + "secret": "ef3ad04e9b994d7b1c8abb014a7f62299261759a0bfc9e4d5fab4baa3f146e0c", + "signature": "a8c0008d15926efa804940dd6a4abf5617c82d8d79c4fe2035a5d3c7e585385eb88e7e8377d0fa41c3da6645278840e737c1b160a29ee072eaef0678d887bf0f" + }, + { + "msg": "2a2b090f9ccdef34f35466e2bffa2a80a5a3b954775768710881bf69fcabd84a1ff0da325af2544d2ad101", + "secret": "9248804811f7610aa06dbae369f2dc4860c898ef53addff51b7eaea37f609551", + "signature": "379360498de7975a6d4f66dc2545d4018a2e358d7d869fc6386474d64645739888ceac07010e301b509d6b0976a4a1233db2bdaf280ad7153c8e8fe28ec20904" + }, + { + "msg": "40de0c774355a0adff3f588b131e0abc9e5f5442dd9d8ee94cf9a85fe4fef07497de3026bc9bf832527632b5", + "secret": "4bd28661270ac2a4e78e20585c639a16ff6503afaa7e131ebc96cc6d239ae711", + "signature": "26c98cde5036dc23d24be0e7530796b5e2119759f8479ad3c145c2890199e26a9fdfa598e6102500d5576ca2cd2394680919479b0643cd607717d726c41ec309" + }, + { + "msg": "49949d4610cdac321f71ebd62080edaa60ac4aecb2cfd84e0ccb584e1dd82ba4ea6b9591057e2460568c7aeb7f", + "secret": "874f509bfd0443301b8a23de7ac443d63273fcb87013d270aed8d1348fb04719", + "signature": "7745afadd5bca6150bf761c58ebd6bcd631bf9ad0e2043d785e430e68c4e5027a971e917e443875af78efac099a66f4e178ae733d7ab4332cd15852617fb2e05" + }, + { + "msg": "85e26518fef4429847aa8046c26b062abe0b8a7d0449285f4deada0bc4bf91a2ae734fbdef83c4d48f1ab3980eab", + "secret": "aeb4112a121d60eb2a206434fbed7ab9e12f782a1bb324779093bf1cae68d581", + "signature": "56b02332c1ba9ca513299115f6b4c8730025a19cea3f961045e6d642a3f9da0c9ec47f645071c1ce243b96196c3114a8bea91ec98843e1d20e218763d2d43506" + }, + { + "msg": "1ebe467f4ce2422a5bc59dd7ef90448715931f0ba7a82a95ebce48fe1009643dc468bb65a8723dfaa0b6926aad7185", + "secret": "58efc6b766abf761d7ab3bd30786f1ec6c6cdee534052c0bba38cff1339543a8", + "signature": "3e4e80ef141c1faf0898142dcd0a6d4e9b0bf9371ef9c7db48eaa80b8d7624d3f9330833c71476384890cbcd2c4e29d7cad67f16a772ccf33268d94cb6a4f40b" + }, + { + "msg": "408082da564a04a5876cc8cf70568f3301687153c67c634bfef6b807d6dc59b84eaee895a089fcbc922220219a0b57c9", + "secret": "39997c7cbf0f532851b2e71a8f1ec42507b711d51935f266dee533d131960816", + "signature": "5223dfcec31f8c1321a08610e5d4a4d361aa4ffaf03c6f0716753f2c6c50d1aff204df2623f277e901ebb23b832fd34ea733ae35d9c2584ad7fcdab9f1479c0d" + }, + { + "msg": "2170ded7931a6e696c76933e2425b6ec0132562425f7f237ea5c136903bc57b258830fd0631c981125eaa1dac38586c085", + "secret": "7bc957c383d940bfa3cb463c482c14561b74a8d653f89d3eb6d4b1bd3ef75919", + "signature": "f224aadc94343287db510698003466c8cf49d987ad5311670f23ad7c9b5938f396716157c22d4e43ee31f0b685364f053033808a717c2b6485b4f8795e823d04" + }, + { + "msg": "", + "secret": "111e14994c181857e462c8f5efd16017936ab20717b995719ac6047a00312871", + "signature": "eee7b35bf5bea2402f0fe88bcc1b9eae9c0356f3c3104b164d0b1d88cd32afdbba15da591cdd4f3572c7c6aee09cc00f9711da0023b0b3f63f4019c6f9109107" + }, + { + "msg": "b3", + "secret": "2e472b55c494ec05252562780ed642110e3317c59b04b18a325201bf28b97b05", + "signature": "ede536e013fc776896d185a95b6ce04bd6f523a7b46d78c4ca5253fe63028d6432678bb5e6cac6e8b2012676e6b1d6098c2f302838deee1fd19b18ff4aa51c0c" + }, + { + "msg": "c31a", + "secret": "6a7e8bd46e216820b10ee964a6019fa3a02563202bd3fe7364141165d7de61ec", + "signature": "9e776316dbead6692cdb10e77ae8430473f467acaaea5efc59731d1f5c92ea683571dfb5a4a88e9b44620d12ca89fc04bc5c05e008c872d96802b6769026d400" + }, + { + "msg": "dc61a1", + "secret": "dd2a8e9fb85e64271be4dd102d83d156c13225486ab92b1062af7929663f9113", + "signature": "1a7ff2756f51f4e040ae872526e628089da27bc5bd32fa23f9cf1a1546223c7f0e0bb0d8e209d569323d8ff31cea2b075b7ef19ed79789109d9d93e2eb2ff203" + }, + { + "msg": "3cf0d640", + "secret": "361c35ec4a355b8b2b31e26cc9f9fad3a4ee1ec4fdeaf5febe25ee6eeceef90c", + "signature": "93302aeb60c46cbd6933dc525d32a07144f7dffd94958dcf63991f922da2d89269a4efce92334bbbae24f8389270d653a3b92f851f9772592b84b9839efd4004" + }, + { + "msg": "d5b286e05a", + "secret": "577ce81849e0a697a83ee42107ee4243aaaa9f74aec2d5212711691d56e7ad3b", + "signature": "14ba3df8bad6285cc537e9f00e0463184bf6c523c0a4d0acf9c5fd89d516b59df7828957b97a5ef3b6b63ab9b7ee93388a08d50b33a52f1f6f1f970a46bb3c02" + }, + { + "msg": "2015dde9f8aa", + "secret": "99bdadf9698c7bcb053ca93aea3a495ca0ab258ccec6ccf1dd9702bafac612e8", + "signature": "839acadd26832838c4142c51fcea397ae43b2d0b7f3d1a9b571ddfde8ee430d99c35159fe2e0109d6b83dd3b4d414d3fab17f697cc3a5bb80a885ba7a4132d0c" + }, + { + "msg": "5aa1d370b4467e", + "secret": "682681f66af334085580a37467da20d83db3a904198a49f9f2ab527c5fca9bf5", + "signature": "ac7124dee38434c18a2d260bc0f2e88ba8a78a6ca82cae42ef8767c08bedaa666335c2bc78c11a66591babccead2e7937550cc759766ea57620c28ef1b325707" + }, + { + "msg": "1dc9596a692509b3", + "secret": "8d9c723772ebb871270c13b2fc48e30fb8391b16be03668f1e42cfbfea0ee082", + "signature": "cf197346d7c91b348627954c3e8904b496280e1cb2804e0c80aee544912aab8a523d87d7752ec49c623fe12d67fdccc7ee2e9660d1b8fc38c56df2b88962d80f" + }, + { + "msg": "adbb0c8eba5e1173ad", + "secret": "9ac5cc9df83e1aaa570e88fc70982b110a682d1e1ab45bc66242b9c789e96ade", + "signature": "f1916f31dfe5c06b61b4e22933cd8f1f2b1d65ae35f3e39c309647ee4520c0be94f0df9c9ea030b75447e02a6fed57299c01211a49096a60cd89cd7ae4f2c50d" + }, + { + "msg": "7bf9ed806e428f67bf9e", + "secret": "75cd64eb36030f6d518a4e56c2a3b5f1d2eb2b672011a3ed7b64f31852b730af", + "signature": "47586780ad3eef0b04270b4eb53c9d08890dd05a907d7f797e8eb378c7fb8b931f3f10e320335df91c727383424a2fd6fefec8a1460c9db71512e881d8460e08" + }, + { + "msg": "adefd075373818315bf8f8", + "secret": "5ec4a9d64464a98981e4ab957b07adc674961aee8da90656113e8234f1ade04f", + "signature": "f1abbd66d9beeb32b9e7b29f981af6dae339bb21d7223d0bacaccd63eb5a8d38940ba301243aa06baf631f355f2ab5423996cfac7a9dec86cc18329f243e8b03" + }, + { + "msg": "b6a59787c1b80e10c6001736", + "secret": "46e3dada84c8cbf57d53bc2e5dc1710599678f212e991cf69a927ec117b723ad", + "signature": "ef51abd3e57f5606cfc9f341dd54f8aaaa333c8e6e85b3f543cdcb0ea53feaecf6a484097b552c0fd86b1371ade1b7052d31f947321c4769b6575d7ad7637b0f" + }, + { + "msg": "735920eccd1ddd338c66862984", + "secret": "73a8e7f6c3666d9eef053f9f2f4951120e7c99e1ac455063fe5fc868bad96531", + "signature": "4380d306e2e78d3e2bd71c57c82533e58d456c901d0d092f78f21f191e3b2e695a2f81ce2fef0bcd17ee43c515871a55664c13ba1679558a3f042bb1b5777a00" + }, + { + "msg": "196b7d1c2778399044eccd241561", + "secret": "a974df2aba456d5e4074d2862a5ca0e0acfb06d74635c9ac375561bc85113eee", + "signature": "752890822e69026dec43c90f47fa5b09b1fdfe53903e6201a846cdd73509f6aa0a46b3d814c5061e5164e9fdfc5ff8ed704efd1adbffc8b609571164e17ed703" + }, + { + "msg": "fc4d37d6168e9bbd3ce1544bdcc9db", + "secret": "1a500a5562e709f78e01703fb486d24d6a0fb7ad7bc040d10087001840cead40", + "signature": "8e0c35ebdb270657224f6904e05856a537b908399642f0d869f54933be77cf0c7568f82b7292d7881c942c12e02c9d357063e1bbff2d98d8610a5e9b1d172b07" + }, + { + "msg": "fe8a6977369c57648ec679ea67702fa9", + "secret": "dbc2d1331b041e067b020298b0ee0fa14f6b64bd0bc56a67c428d8b954f221f0", + "signature": "8b7636eea4300e9203e38cc4ea5534803a4c21f3ee5f12cac5dae1a42207687102eb34a16b6cc375fce9e5d1753d2399d1392e17cbb8d8c76f76512b6bcdaf0e" + }, + { + "msg": "dc1f80b8761b435dacc1ffbbee6ad63a7c", + "secret": "1640b5cc5d85366d766e32ccf7745f8f33354fbd520d120d186bc07eb5c4663c", + "signature": "76eba95e2ce46b5e3291282883f547e0272e15786810857f58163b1c323c67de7417bb64f3d7d1f14b513e60527459ce47fadbb98aef5d20ff80c9fa2e659d0a" + }, + { + "msg": "0c6905b4879ac416e4da55c41aeeba83dbb3", + "secret": "d507980ff35132c96d39e39807844540fd22514e7b5d804f8b136b5bc0f663fc", + "signature": "373b42ee13becf2dbc8967648d760b76c8b39762153892a20afe278da26fbbf68e984c0937a59cbe62366aea9384e23e8ff99b7c36431860b36743247917360a" + }, + { + "msg": "66f715c904548ab07ff06408b55c1d1897460d", + "secret": "1c2ca1b98f5c0c78adc9b4b484d70ec13f51e51b936083501f73da9bb99fd0ce", + "signature": "0aa99f30dffea160a36c72f9a7018869958d6368bfedd40765d3b3dbff512d7811df838166532f76b82655fcccce2ee841da07ff076a824c2c269ec225f29305" + }, + { + "msg": "4be6355582da270d6531e0b87a143aeb5b3ab5ff", + "secret": "0cc2c49260d8497cff7d6dcecc454b08e4640e0831895be02bab1a63f885b222", + "signature": "8abcd55c90bffe2d6f95c86a69f53e89bc6d4a14a3866a13dce9060cc90e31b07e76066872fa7b0fa03461fd976c68e9ccad4835d3f0b2edb0b5d2ddb7563e0f" + }, + { + "msg": "5399e0c3585b53d6c99f19f5c9c0ac77a796cc86b2", + "secret": "f46f925de45b73214b4534d5bff2f2b5c46e8564cfb0f4e5d677567c18215bb5", + "signature": "78ef2ee42f36efd98485965b527b5b2d16703c730ab93b2bc1365e27f8574c3052db1cfa9f7aacad5707951783ffebb6c00a2ee1b964429cf4f44fa5192b5b0c" + }, + { + "msg": "cfbc784d980669c638a8cd1642f9661e42512fda9915", + "secret": "f7ccbf0edd0c8a872cee1d553abf10e9bdf6ee54f0911857b99f2b7b5dea5fc6", + "signature": "637028c23d2d1b4e6b019e7d3093a258c92ba4f3493825778e9c19be0ef551b1f65bacc1c5e95bfc02bb76528af0b3bd1feac755bc064383a511a2aa58cdfd01" + }, + { + "msg": "61e0b553b971c5d87994a451c5bc9934e0ba5f533e0f48", + "secret": "7c55c2aee1efa4373acfb1167bffb78f52d429718b974a8b105273f4be47a447", + "signature": "8b599eee24c9b80d8dead3f40b9130d3afb3d882154355f3dc00ee04f47c65397847b9ab288ca1bf3fbe9eb00b80d7a251a51617bd565223f4f0ad89b8b66904" + }, + { + "msg": "13881c81f68a32fd66f1935ddaccc757b094b4ae8ae20f06", + "secret": "69bad9aa8d10be8e104a8790dd4ac920a10a0e2bd98cebfea0d646eba203de16", + "signature": "da1de039ee6708d1f6e37b8401bfe9beb42f7c40cefaf9818ae2299c0a64cc19a849ae0fd45dcc461e980fcb9124dee5bad55ca4749897961b6ffbe058cd5002" + }, + { + "msg": "666b892681ea26b171dd8f42031a479c58a36789da03a7f292", + "secret": "b069a90b16d3bb83e6173ede0516d041e0c82573d6d854b70b16b334923a5ecb", + "signature": "80fac0d7bc15a44db4a60d3286d7b07f9798867063a13f65fc90350cf42f36102ee40c998b660f482b1aa67b2b962efd03bc9b003694d01e2d963134bb03a504" + }, + { + "msg": "36df157bc571ebe7458cf9b32b2afe651bcfc7f660a154cac618", + "secret": "81a847292e5500af0e3c12794224523a8a7084508e942300ac1501dcf570c10f", + "signature": "4f2997106951dafb31a5151fbcb48db043799515769e3c08433afa2f199abbe7a887d419f9002db74d92a63f50ca655302491e23d68a22a315ac584404332503" + }, + { + "msg": "307964ba0ecd089e65ab4b9c1ba389e480d70f21ba1abaa1505464", + "secret": "ff58c9c70f2645cfecbca7d8dd660f3ee96ac3d451e287c316a82b54ccc18f17", + "signature": "c9a8ad88afbca425d2cd7a320bb6eecac87b916ab4b81f6db367a37a9eb2da9ad23691252c6c7a5431a31a9530c05b8d8f853335d9ca3ae8c12126ddb86c7e0c" + }, + { + "msg": "d6f5a521cd9301abadb5615c00a14a9b6aa8719a078d801499e67929", + "secret": "652eb6de06b731750e1e27f06380346e8056e8df85f81af644b76f4e66ba4711", + "signature": "f7461c65b3029658ba566200f1d84f6d3a15949d3a8beac77b4b39b64fb1abd11d8351b4aefcd48f77ac50dd62cd34750ecb5ff584d9cedf2078aeb3f819ae0e" + }, + { + "msg": "25e6e178a86a8cfa1c1739343506873a070bf278509c0a326a632d1bdc", + "secret": "1f208ea1d3ea5da84c9055c646a796f8d143d0d0b10ca2ad2594677925403502", + "signature": "8d333f1a9bc22c9d0568e329e40c47ff8e05461fde1bf47f8f5ccc427fda9027e7da8abb7bebe752d2e78c0137cce21d03b7f4be9e647c61dd82ff5e4edc5b05" + }, + { + "msg": "a091210ee30aaf258738ce291d3b7569a45bff032e1b7739135917ca7ece", + "secret": "963a11cfb394accdad675920a83d0e372f2444b7b588cb80b8a929f65b0c23ba", + "signature": "b1d419f1c56996293354687bcec39cc014ed01853d53dc763511ce7e309f15d6c7ebb609b3c0c5a1437921896cc8a8775124e21f0f3b70ca23838bb6f7aa4f09" + }, + { + "msg": "8ee9f305d279c383e0eab81d25f816e77fb80fcb483aa637886093bf50e0b0", + "secret": "823d54aba73a9107f78ee65612639dfd7f6a33aab532340a3e5b0420f474df89", + "signature": "2c0a7a8ff3df926029d64df06620f05d5a57df3b2b1b9d140aed8879d1c2e4c20b1ef08ab103adc7bfc4af9dd9885b1a0ca2f146dcb0471b593d9a73c3628d01" + }, + { + "msg": "b0b135f62c6e9012da8e721ab188d2381c5ff6f6b17eb1d33e69a86e16572630", + "secret": "dd040fa05f52fab66a0000836a882bae9fe86216fb94f25830508fa8c9d1a0a9", + "signature": "6131074ae1626d7e072de76c727a01cdc56373d18522997fdc9e339308732eb3892ad8663071efccc627e3bebbad971378ada7e65fb2206be3b5f559ddf0f505" + }, + { + "msg": "b4c1485f42a28a996d2dfc16dbe2c6cfb3a5c2632f7a9ef9a961fecfe5cfb8e186", + "secret": "fd1a92681065c38a59895b3ff2c42d68a0da48741711eece4edc66c5185bfdbf", + "signature": "88581ef9758ca9514130411ff91a26e7b1d3c74e9e766f1919db93cad08c00ec20dc2f22e1d999c86f16cf005f6c45bbd0db2c47af988c42dfc94d74ba2a7700" + }, + { + "msg": "9aa03dcef50c6a3a09df89c2a8fa8104136072465b682162fde936b1e9e17698929e", + "secret": "31d97c437aa1831cd9a070d68441d572635d3705ee4e02cc233cd426ba535807", + "signature": "c55766e0291795615fc0a1e34c4441f0bb74200b959489b221cea714cc03bf050821e2ff81da9ab6397f93b89b4b89e75e2cc2d63039a890656b977e81873000" + }, + { + "msg": "7d72d3fab5e9438b0416ce6dfb99648215d52b8f7eab2354334870ef76972ff3d06de9", + "secret": "5e77c0b86b9e5fbd5a087585a2646ae609ef98c5919f11827cfe73a57534b557", + "signature": "ca5e1f1c81eb63b8cea12b2f02d7dde18342674ecf8010f154738a80b620e808aab5d1a592ff2a5299da59e219a417cc204dc63b5cac01ef2167046a2e034d04" + }, + { + "msg": "00b1699880865c453d82771f5c1eca56426ea0358312ed0f71f466531d4f2d813707e413", + "secret": "bffec493b33ede55e56fcccad0d870f4bf3355e5e7958771c975fe9ebf4d89c3", + "signature": "3411ed44c392bfe443eda5d00bec5172ef1dc95b471997e7c165297b8a95efc0d3fa5716873f16a7dc7c029c846a439a7675ede0eff6e102084019ecc01de30d" + }, + { + "msg": "c7674b2c3c05100af92e7ebc8177c1e0c38ac62f7950827779825585e46ae62366bb29c0ce", + "secret": "2766270285e01312cad1d6ad7abbdb4ed354b8d4cc2df3c9cc47a20e8ef9b041", + "signature": "058d85b408d6070aff0a36749a5c21c1a5828bc61cc6218a047c437bd1e7992f55bbccecd70a5e32aad6ff43a244e72009707ac99d337c43fe576e5367ed360d" + }, + { + "msg": "588fbfbe17c6dc18299f576822501827be4697329c35146c1c0d3b8775e14745b245f54bbef8", + "secret": "4687cbff444dcabd6a14256c7a7b56fa4db89acbbdbd53b453a8f470af8e06b0", + "signature": "88ac052b7cca7d474940f2537785882ed2a6f5222f1781e9c9cfd13bdc4d9a4dfa2bdbac5dd38ed214a4df124011f0e5b5cb6fa64f69824b3b6de92e66eeb30e" + }, + { + "msg": "1ad4bf84ffff40d90e74832abd1c163065d30f34862e7f844f2d730f67701fa1b0786922b93f54", + "secret": "3ff4d569d8b4370aa3ba00d6d106a4f84e5def4237ee7fe03cd6849d6986ecfa", + "signature": "5001f4f392aaf4a6458ea285b2bb6b9e90166fcb00ea097346bb4c50df73db788a934f9b8db4002705ace0a0b688974f5b769761f897554554e153327251730f" + }, + { + "msg": "8ab4450fdb5db74e67780f5528d3f8ab1d22cbeccd3f9cab5f08a6557fbf6fe726fb4082aef95640", + "secret": "cc1952f8ba1b227aa5b06f160c84de61756d21a170f49e90c19136e01f4ed8a6", + "signature": "099c53ac3d1dfb17ef5f0e5354f15da48009a2f6885e44a8ee9a1bc7c8a1a3bc0d8a267413731f989c9ef3aa43529c2485145839c42a60ac8939fb602540da0c" + }, + { + "msg": "a722206e79e288325083c6cabcec7103223311774378f9274b11bf314cc655b145ec126d33f2447ea9", + "secret": "531bd060f089f4a40b3e40d8104398dbd044c2eb1ccfa1eb53d20143e7a10eb8", + "signature": "764ff0fcf0e65389e3194531f2e30c274cbfab823f0533dab8b1043b64f7759ad0db0f27e3acbcb82a723d4ac26a85f4cb58a8eb31001b62d7348eeb9b4c3506" + }, + { + "msg": "e011eb12ee7cb9a10cc75af21e34cfdbe4617d7fe44105135a627c77cc4003f406d43e966aff1c904704", + "secret": "7969fe890c3b6c7633dfcd6c58cbe58a93013223d63453b1c8cac09ed23645ca", + "signature": "54065c1f261818f8f074a0f7d39e4d17f36a99ce861b77ada519dab4f9bb00f7dce800f6ed68077c718538dcc7111d0bb0b1e70f71ae5f7a03dc764d806a3900" + }, + { + "msg": "61d8da8b378a60f63fc0c398930492ad3a9d3995f4d7f7e95e91eb0741bb2e6e87d21988346a15b1a2640f", + "secret": "9711015a87a991d86d44995e176e29e76a31446695cd7b2c506f350bb0d3c19f", + "signature": "a29af183c2fa12c18496ab44d49b35b59461b3bc27e1bb3e85315f3957615ff0433410d5190723df1848f3e388f41673951ff4ec16743f863f218d9945eae701" + }, + { + "msg": "31366906a392df9ec8b9d31862916b1b134db23a5163310be29387174b0c302d7b9b5a3e3a0272fec4e45604", + "secret": "63bc40363ba057c49c10040f2a87e168c7d20c0fefddf576d614205c434287eb", + "signature": "64cd5fef6e677099b1c3a90be88c02a040624191e6ffa028d9f0a2bb4be69e8994b1b43abb66f2c2c74b3287dee9684075a717cd9ec7f12f8fa6d6b19e17b707" + }, + { + "msg": "e7f57d6f86d25186c37a33916c027aced8d7932f5c432125ce5d8519596c8da1b2dbe75e8d1b657a35e378d63a", + "secret": "a3e326b025a97d80fdc9c249ac2f14a7e2ffc8720b3293a2afb39d18acae9c8e", + "signature": "64819a237a51dae5b0ec1a9625a9008a7e408c8bb599163b0ecadd70f2048109708f6fcd0546f95428cdfe7cb6421612f4ecb25d570ae0f73c2fa5263aa7ac09" + }, + { + "msg": "7d3adaf3d27e5218e8ebfca79210ec2e651f4642375a172f0c4a0b389caae31b113a278608c9252c54c0dfe6a0e4", + "secret": "039422ef16ac0b06d51c15b5a178c16c1ce58ea4ce7508a24fb9072a36297501", + "signature": "d3d6370628d22efea31ade188cf2f58e13abc2eb43b31b05458b52ccfaa8b049d52d83af56982fcf22ebaf26cc5c0e1e46354f4430bbe279630955f84e2ab605" + }, + { + "msg": "aefaf5b2f7b797d2a7131a7b283d4dd8b22f5f21b92fa008c1cf7b75b63881a01ef01f0b4120da6a7d407f94218c1a", + "secret": "883f4dd9f31760e4ecd40240b0c5ddd31029960ae818b9c7896a5fe7f3ad38ff", + "signature": "aa299650226349efd2be147a684fe2791cff75cbfe4dd4bad7d85a370fc125f908da68ef3ca579f7bea47722280a95871b01d81369bc5e3ef7e8e3299a693f0d" + }, + { + "msg": "75339838acdb16c412b46739a7cd6c0ac6be73da1bee9058b90a1cc5bb60a09ded06aa479c8c2e799dafbd163f27cae3", + "secret": "bc23a39a0c811210e0d28aacf90f34ef71f686bf91d2c4ce55d89d9b6826145d", + "signature": "8acaf1a395d37d8179802fc4b59efd5548260e0678c79f53cadbc881f7236615e5d25262e33a313d0fec220ab519a1cbbce91e049f4f1ea12aa100c992aae003" + }, + { + "msg": "ceea301f4e4080cdde74bc22719fc8aa77026666efd4584c92c7f48317b4f7d46cd89dd683d61611f4c0a5d9c3b3f4607c", + "secret": "90bea9d16c860df2df45a2bcfe81a9af66c34871ff267de7ef907d7a9031b4bc", + "signature": "4f95738a5d93ab0965f4fa17a904a25e7af5e1a1903f3fef3263c6c08ef3a48be9f43030fe8eac29cb437ac7f4043fb9a0df50f734d3d123ccbae0e0ae86850e" + }, + { + "msg": "", + "secret": "c9a533078f0feef1272995854b6050eae263a0c6ee3675600f75ef612309031b", + "signature": "810de26b679f450151db09da62e0548178dcc0c2d98f3a21282403ba6f40e903586aa2e480194f2edfbf5092fbd6b9faf40ef289cc278f2b89443a85cffe4304" + }, + { + "msg": "8c", + "secret": "ce8b954763c8863201e4c7c5349082f4c601c0265c2e68d6acb35896764f7b95", + "signature": "cab7597b757154c22876a8ab557b1cd588352288cdf304ce7e71637bc9f1bfb7b22820f2f62340c74ff0b6eac95389a6f823a88f69c09e2526a67358f3f62e0d" + }, + { + "msg": "0683", + "secret": "d9e94b7976b9543104e33755133eb00b4ecb761c9a5e842a95f359b810e4d37a", + "signature": "864ed88bee12f3023ce17e2264104e5a8e9711c1088fc7af60d392625a265076ce4b6e2fd9e7887e6f816c82398de9e13f58d2e7604b1034e619157a4339b40d" + }, + { + "msg": "4e61f0", + "secret": "f53c664992310b11e8bb25e360937c525c9411f78c0a3cbd95c49e0cb7ec8cd4", + "signature": "cd75eec6ae69b093fc55d27e38e280fd2b73b661a83c1696a408eee8c984c23f4c2f31a49bbfcc0833d3253f27b63b1e66865762b05c7aac825bd6aa061d1f01" + }, + { + "msg": "ac4eba7a", + "secret": "200bc205694e85392e246c9f46c238fb2e1afea3dc10c889a7fb47a383269c18", + "signature": "7f1a7409ebcddc887b45d986ad8b2b0833d8f80e18bc5e4090ec5a6c3cefa3aacb3ff1f3172e2eb678a0f881f7ed5c0f6c5131fd54ba20e22bfdcfb7192a0b01" + }, + { + "msg": "62664890d0", + "secret": "6161f2beac0fe1d0e18709c46d4662228c5b8ea82185aa9e39a63dc0cbdde0ba", + "signature": "6ab711f7d7bfa2fd52838ec90fd9b766b61a49a1731980c804a704feee917d1e428612be0b9cb6e7771bf101558b9b7794b7d720a371fc9bb69d38d102973d0a" + }, + { + "msg": "07e566631ab3", + "secret": "3fd16dbd92bb951cf2f154710d32349f07076de1f0565ace7e62fa128c1b5e60", + "signature": "2b79b752bfb19e57fc7a6f7bf052718256c6fea4e1d4ff96cf1c46c36495fdf98ab7686314d44a9d491d3a3ad30df139bf81dcbd9fbc012dffd861bdc9bb1c05" + }, + { + "msg": "7f5aef1c8a729e", + "secret": "142b85a13da51034d2b377530bcdc8c5e68589f0e0fae1f91425bdfcfef84131", + "signature": "0865193203f895e7f053711839fa32214bc4b2c4e1860bc684574b8c2ab3b38b4a492ce202be6befdc870660c46479223805b9fbe61feba69b6f62a76e159d05" + }, + { + "msg": "4f3b634a3e060b1b", + "secret": "ae4513a092524ecc9486a3be8cf8d64d2799cb77528424a08e9f5beb5ef19269", + "signature": "bc10de1ba91a260f99a371bf435d8105830e3d70fea355e14cc6a9d22cbb28e779c431fda41eb855f2e25c51f41c2f41196d83e4c4100bd9059d882e52a5e20a" + }, + { + "msg": "ed00689338abea8824", + "secret": "46c7d6beb6b8d4c120ea85576b724812ed5f4bd79e55e51f93cace50177f2b64", + "signature": "47fd9343d17d937cb38539600f21437ab07989f0af345a9a0a073a70b6e37e5475be14c8d53d5227754f306c331066ae501de00793fa3871ec9342c286f5e107" + }, + { + "msg": "846e4d1d3785d9838680", + "secret": "957ff5758a213fd0bfe220490b5ebf487ce3c031119627d3371def432966a5c2", + "signature": "088b45c9d66a9158f2fe0eba4e0fdee4dce49e7da005b2aa3b5fb1e1f7100ea9073e4fa3a7443b4d5e21260f114ceddcdf5649e729daf861649840c5b009b403" + }, + { + "msg": "cf19050e08644291b50b2a", + "secret": "8e30c7290b0ae41d45da6339bb9f2fb443dcf347f4b92ffcb0b5c06d48a3fb3e", + "signature": "58f5998ca160a290edd16acad29056b402584c6c1a780a86e6dd2c7730e2443a32b395786af201087b98a9298f162519c8a33f37cb16624a619e40cfc9735a09" + }, + { + "msg": "e6ea80d07a7a675cfae2c267", + "secret": "b3ddae3790c128d309394904753cffb2a258adfec7c011f996989dec7a764224", + "signature": "989a28d607643b253ec51afe5997040f54a762915b568fa2951908f7622a1f53bd3977343675a4054f7080e4b2426fb2692955c0b61b4e7ca45d8222f7a3b90c" + }, + { + "msg": "4f9afff1da2d299b5a3d90a621", + "secret": "9369214336e9400cad431ac03b399e8654991b2f2a9ae06836e014c963005be5", + "signature": "495995f4f80c1d8ddc0925087564895e3fc6d81e02a8503d6a4f7534d5c00a82ac8e7a96711809662462ac944b56768de58834a16e79959f4d5686d92e3a9705" + }, + { + "msg": "6006656d9e019e7cdbe7ce1d8897", + "secret": "b099666bcbdc2c35aef052d7ecaa1b0e7ad1bb92b2909e91a81a34b49d286b31", + "signature": "5175ef09a0941502847ab4d5cb924758e4651886f1639dfbc4910f9f8f9d157538d1d17f40626355866fa33d58e8cfb72ace00288ca22608f7fac3fa3e50fb0f" + }, + { + "msg": "0434d9839209e3254afdb8d4c8b485", + "secret": "edb48f8e825ef2f98ad400d51254c15b73382d744444ac36a50acd8d924c8a75", + "signature": "e4c32a57f95fca87815a526d44ba7920a18fce8f2d7deec60f0068d765f1816c562c596766b7f22bd9ad65cbf724ce16db7b29db6ced1429845df528c55a2407" + }, + { + "msg": "fe8cc4ca2c69163bad3fded90fc8fbaa", + "secret": "fa4f5465f5f8bd36b609fb189a2712c9756589fd006847d3344c6981da00ea39", + "signature": "f7dee912efda0a5951c60b28fbe699ca3d75e4869431e9ccbbd827ecc8260c1b9bd47b5e4b557e5654038fadda45aed600aedbd628bf1cb92cfb97be7c826301" + }, + { + "msg": "71b4153374f6be9399697c9203570a3594", + "secret": "8f187d85e74a50166f52838df9a498d277f06b87977bc59b52cc5c4adb7c8678", + "signature": "02ff749205f81df9371ed24905835d264fe495508baca1e265494d3ba6b198fb4aba9ee09b03c2cab548c4fb9329f279902ce61834e3649b594b66f90517d30b" + }, + { + "msg": "06add6ab8000496b120476920f8b9bd24271", + "secret": "57e1be3f303a65a8ee9ea0b6d8c8ecace5598f2216ce477bcfb04b82f326fb13", + "signature": "9fbacb8128308e0f7a885d25f0cf449f8632b5807c452ec597f11136d669ffe1f3130247bc06ce3e9e689f97c9d6706963d7bb8cef7ab397ca225b9a39636505" + }, + { + "msg": "f05508bf660818e16f27aae5e0d61cade7238e", + "secret": "f7837de18068ba71896b1553d9c007b5abdf21b0091933dbf78eaf27b4c8e5c7", + "signature": "e1e3bca4a1dcfeb131e088bac0b24bf28069487e507d590ec943edb0924609558cdfb9dc5acfdbfb2848db164f7b656c85f32be71fc601a055ffdd25d646210a" + }, + { + "msg": "75fb4e7116da17cdc6c2ca0c770db4d958b1ed40", + "secret": "d9fcf91c92bff0838066f5d26e771b39cbe41d9f3db61fc2ed787a4fa3db2b5b", + "signature": "9db85ce4111a02ec06f180c9d20ce7a992b9925a4d6f70963c28890d8f3f4ba6d0c00fd462137967af9c7152e842a553737f0efcaca3727af21af2254e13700c" + }, + { + "msg": "07d177e86695059453c1d3b40f66270e38ac7cc9b7", + "secret": "7e6d753bf98fb728df4f6b811bba8c284d4f66941301fe6002e7479f182d8e4a", + "signature": "5fff8aa92cc2d046f28861dc2499accfc81ff5f75158e1c2ee2966d397f7892cabe4efc1e7b43978919be0f0febadd1c3089abbf3aa34bf4205cffbf59b1740f" + }, + { + "msg": "d82d088e7706a702b2563abecb48cef9c01745321bb4", + "secret": "5a90c03b7c4aec9c678da8fbf25e2a8428f8a9d238fc101f4eb411285feffee7", + "signature": "3f6a004f452f038bfc4ce503e23acdd4b5226da84f0a1ca593ac8e2b3e6ac7bf55be8d50c30f9bdc495eb02a4b0ab77c7e28b008ff106e8c9d2e0b74e0885106" + }, + { + "msg": "469c3f2b87ef6923cd3a83ef45ea97aa57f023bf6beee1", + "secret": "8086ca354b63792aedb65b954715fbb2af8280c379225f00fb5f8e8c897e5d7b", + "signature": "15d33c638c42713ff9291a7d463191f5adb9737ae03230f3ee26eb9b173c9a4c5916b6735f456fa094c75308bed6b73536fbc92b74b3ea9b8a982f2a6c529303" + }, + { + "msg": "bbdb03ac8d6e13cd0d2a92222525fe2128dec0d14337ee66", + "secret": "9baeea7a4032550ce57a7df7294c2f2d2e9e12d93720253de5f59f21773e87da", + "signature": "6bf42e4c9e39d32f3c4af82a39b4107fd488d8ecd97cc9d060ee5f2c1202d44f5f8b2d2d6fccad03e401027ad43060557bc557a6ee8400136959a48e14baee03" + }, + { + "msg": "f08b166c884da02b3a8b6c9cede812eaeb1286fcca9edd307c", + "secret": "3ba248f2589fc77874a5ec05b747a35cb378f1fb0c76724a0d755772354b638a", + "signature": "1e6b3c2a4dc28ef262076478dfb88b31471a973d1433d612684b3b5970ecb801a9f347ca091f62f0befc2d2709e1668df00179e204d8d70e06b8c859adb9d20e" + }, + { + "msg": "e4aeaf535b143a0007cfa8b79fc4faa07f5971415bccbe1c7957", + "secret": "bffc28defcb2ca0b3956fa12deb025fe4ccb2a75234b6275714c3945bf20c4ac", + "signature": "132a2dc4b34558d4ffcd2bc5d152494c1ea36f119bdedecf801ba7e0f8b491c92baa39d2e64ac24b9ddf71894c867ce561167cf571bc04ad1a16a95ba4ca9c0f" + }, + { + "msg": "6324af23179976ffe1322d915ec3778a888b1ccbfab233c2900456", + "secret": "895c7185f5822c0fee159a3c7a023caabf66f2b0c9d285da67f98dd565bb5c4e", + "signature": "4c0d00e8eb7c65b55d6d5e168bbddec40a52c684975dd8d71114d8006c0e0604d521a7b63c8d9e7c721e522225542d6c9faee1836cf6c79ef9eb0d768b511d00" + }, + { + "msg": "b3701905bd835b886adf475938f530ff62d8a181b713f20118462680", + "secret": "08f2c0404a20bb5b785a95105fdea1b5b9ca2e707a79d4f2ee8bce72cb14284c", + "signature": "4530dbefb413ec7c331868db99cc20b0030bac55fd213b7c31d91222a26237898641a5c6aa48907f64819e9a887ebe80abbbaf79657bbef2e1a1d4b446f62b0b" + }, + { + "msg": "f184f0833ecf1f949989653745ace851a331ead8087f83fb4992e2f795", + "secret": "efc4f432eb88cd31b87d1150da5bc44ee2aaee4b6978455818a8cb767b3b3a81", + "signature": "10bdc9a6bcdc4af71ec2757c9f7ddc5b48d4df50f2fef7e3657dcae13eb1f15951c6c899dcfef75cc824cfdaf6814dfbc12187258b408453dad6b13485c2040f" + }, + { + "msg": "fa52f74dcb1fe1c0d0777ce76afe4a2b4ddd26c3313766264ffb9584ca66", + "secret": "b856d51a510bfa009750fedd49768d24b98b7b78c158b461979bb2e34cd81849", + "signature": "7c6aa5e93c6b89e6fa774b2756de09d0816da50aab7ad5d1d5b3b4e7c80b573bc24bf0753cf78b2a25d8360eb9968cf247cc70f4f0887bae4b2c58f8bb44fe01" + }, + { + "msg": "a30fd00aa7313429a22a0fee7479527637aecc35828c154f2c10eb6dee789b", + "secret": "b03f0e36a6186276ac256a1a2f492251d5ba9f32189846c7df81703a9cc0c06d", + "signature": "6de719462d3a008c01dc23ba000254c3a6e960a577c428a100a5463010fa38f2a7faf4097dd04bf711e46d31fa038256089a8ef9a406e0c4178b76ac08d0a10b" + }, + { + "msg": "965454bd800ea7c1591a51b20448914db80ff8b926b00e2a94ce5882c7415f8b", + "secret": "49dcbc967ffd4e14ee5a1a4cf85d027866c16b0b32980a0e293247f886c87063", + "signature": "a9ee29c16d4d17566c0946cb4f3ad801ae45f7aeb5af5c4fe647ac0212d606406c84f8e29216489e9095c1578f4b6e6b29c3a6a6c294aa3edb6a29cbb3f6b307" + }, + { + "msg": "b12dfaeb884ffd8e2dcdf591a50b8ac5a1a305813924e5368a8a7b650ece600431", + "secret": "bf03d64b69634d5eedd4117e37e040280a6f10f1473bbdd72f742896bfe66492", + "signature": "2b3044f1ea12f52312710552c981f0468fdb686a7b7057a386220f5745b5d5b9409ed6b939d36c2816dcbb1dc3a47130708d6b04d4cc54efefc2f56e34143807" + }, + { + "msg": "2bfdb07a4e1bcb7d5b426d91ed42af4e462ba86bd96283954a09bcb646ed48c0eb05", + "secret": "604cdce3a1ed456a4715eb0d98a8145dba3c3713d28d7f24e38e1d704fd1a828", + "signature": "66ecaa62e1e67b4cb2e3c3cfe8263cd9cb6661c13d8747db466adff93e2ba5ebc99a0f3c37fd9198aa071fee6619edd4ba1a7f6fb6c63bb5e6a50c04f159a80c" + }, + { + "msg": "933de808cc094d50045ed34edf235bf3b3e9a4bbb790156e9e019c069e7a1321b65c0f", + "secret": "02c53f2f16b113f9b22410d45763ef5d472332ad05dbc12786348ccf53031cce", + "signature": "7c0cce151cce98ee6b445959b4e236669faf16494c353d456cafe7717d2f335eae392d5c1cdf63a95d51fc4138f2c2f4d123dafb44d70d50bae978788c22bc01" + }, + { + "msg": "93d6eb620c36032d711424f6110a64ef3f274314bcbd35d2185154b1cd1f5c87d0719e93", + "secret": "c376522be00c8b8ab009c39bbceaf00c4ddffccc4ebe63d05c7524bb4f2064af", + "signature": "531e205f462f73521171022beeb58ee26296617843607869755d9093d24dbc5c3c4b23272f1d5560c72451e88c131aa1cc4b548c40430b8ecc3683e93c4fa204" + }, + { + "msg": "eeba85768776b204335c36076f6b70eb204631dffb8dcb943d01d37fa890430ec50c0dbd8c", + "secret": "013946cd3d18c5b99791b357c79f69c2cf9e30789a2f31daa56da40e30b84a15", + "signature": "44c06aca33b853b7497fae449635c731b98b0872ca8c58679fa4c04f13af673d77a99e76205ae7b66e0de415417de6e31b417da4a1146cb60204bdd9e67dd701" + }, + { + "msg": "3557700c2ef949c94c0e1de2c2ed4603f5d6e656a8d7b815cd37d3e44653e4d3dd769a4281ca", + "secret": "0cd4c07105059feb9fc4ec07a465c197878bc3e2e36e401bd386c2684c569933", + "signature": "7f56faba502580670d7c471ef0265d298b27e3de2972a0e10eaa21a677e79ae4548e31ad7d1e15e209f72f7b023e88d283d9d0fcb175722d0fad117f79702107" + }, + { + "msg": "aceb03ee7e7047780bc3e7609a8df343b64dd60bf03540c23441831059807bd54d54b53aa7e7f7", + "secret": "68d8cfeeab816d61e22701dfc82efe9ccc176a25cda855d0190adec3fe4907f5", + "signature": "714c1d21cc511178ff5456141f3255f6de25d0e660ad5c87406e276356dacdbb58bff0e4361eaa0a2ca9dbc6a208308e9f778f6b4ab9507ec9fa457e36189f02" + }, + { + "msg": "b77cc43e4e7fa8702a54efff56aa2cda0f91d12a243b0ac1ecdd256e30d0010892013e264c4a3c9c", + "secret": "7362ef7f879954d4c4bf84191ebe3c7d5a97fbeff14554724058e633f1ad08cb", + "signature": "8d8f61f1c233a347f24e9ecbfa24e96a77bbdf0a36ea0b9de163e0de0c3dfad5611428580bf05475e701ae9eb65ccaa9a8a86888fd682c7c3cb50debf0a5ea05" + }, + { + "msg": "7bc681e6a5e413006963acfe0ee44f3001c4fc5aec726dff661542ae694e3783d4adc11b5551a4caca", + "secret": "0d89c70d537b96ecab7f88c6b5d75142e044cee54c5501a4c8dc07986f5fe0f4", + "signature": "3bdf04e326f8307876e88dcd816ce4723fb4b9c71b81cba82fc56d67f94de82e90f00e74d3b624cae611f727c7c5132abed7acdb31916a3b881893248bfe9003" + }, + { + "msg": "4cd5516fb67fd4d0d0000b38150602ca204d533931d4b21487248c745893daa8943794307579eef4d94d", + "secret": "b1496b69c3a012cdbab3d5b5d5e2b326157af8bee763b79868345d03fa916ffe", + "signature": "543b9db48257a34ef85fa2bb842eb37e85e420df62c92d76692dae8b13b951e7eedbc564066dcc41eb0702ef73658966f99f288710fd348399d021a4f200c507" + }, + { + "msg": "f899801f0dfaa82308b528012f4059129c1b6216202a2268dd9709a1d397de21dec766e978f1d1f446f012", + "secret": "98ba785268f53a49489b70a9084960344b2fac80df9c2a14e71d71430b94ed04", + "signature": "4f4476eab8fcf78fe6844dc42d4e43d6f368e0eb64353df66e73d05a3c331f0e1f3dc4afd3efe0ff0ed96c93b971f525ec99c62550bbd2a675377677fe1f7409" + }, + { + "msg": "626349b5445f511da6a85c9accd8946f49ffab1949060a4a101c4484ae51541c4f1bc00c2a04d9f473f68332", + "secret": "56057dc5541f68373c75f22c30a4d2e26d9f84d962b84fadc51229b212234284", + "signature": "d126de60beb7cf1947c8d141c0dfddf56545ee96c4464bfd3ff90f91715488fae0d96b40d8bc89018fbaad5ad0f1664a5fd279f15c26416796ee1f94cba7ab0e" + }, + { + "msg": "c42f493a8c96b2b4ea843eeac55597e339417aea1e6cef24ee12ea41a5abf97b55886025ce86f08040e2c51307", + "secret": "96afe869637a4b605b7cebf09db3f38bacb419457089e14e77ca002a1c14736c", + "signature": "7e3b0a8621c97e052570631238a6492900880ca46441ce9a2137ccda07d26194ffcf7f1e474b4cf7dc340ee64648eee03cd25ff74a9bd542d6e7112362b4350d" + }, + { + "msg": "66f5a28905101ffb5228b70048c78af8122d0077c4064d9872406de572aff5617ab5a5ad8efce63cafab776890c3", + "secret": "2a4d7e0bc00e4c901c323c09b7f794dbd54b8d6ad82dc930c0d444b9005356c2", + "signature": "0fed47c6194540ab444796b72d89fcc6897ee80fe758a95106e892211769e0361e07804955268aac79df51b4bc2357a0c3024730d6cf9fb4393bc1331b896b07" + }, + { + "msg": "64429319761032b34106b791f14b058fc95c386fe7292933b28e54591427eeeca77af43c71d29a6c8ab7c48899d06d", + "secret": "1ddefdbdd61f993799453467f39f650771898ff66c7f5e7c2be2d79486dd8380", + "signature": "6a28c677d63f940eac150f54fe6ed3a6b74bb0be9923a0d1cb8f1b4f042c32f658e87acd042291c043ea39ff3314d42737c3a4a14068eaa1916f604e600ef406" + }, + { + "msg": "b9a098de375566b892911f61382cc4889fb49bb097852368f54b6907152cd3b3b45f9a943cdb11af9a137dca98df9f20", + "secret": "ad17bf8fa2fd4eb62c5aceb625860a266c6d5464cf39b0103d3af595d924db51", + "signature": "616e227b2e8b65d237286a004ec9ea619f3bbbbb59adb241fb8908aa8166503848414794a8e39a7e92f610b4ced4e9d831d24771a7fbfd514950574d317ed603" + }, + { + "msg": "ded69414478078da94a0741416eeeead31308861ea18525ad8a5d81fe74dfbb8a25bc4390f366aa1f571f47efe3697cd90", + "secret": "e12553785d6e060ae57abad3f359b1bb14f378c16695d409b142dcaa0e13a202", + "signature": "b4b83f5e6ea1b3cde23fb63a09c58e6f8d24355854f3df5a6bdd73958b14198727ae3c1a60c82a0656399d30c8c2cee99657af3ff64e0e559502489110e14c0d" + } +] diff --git a/rust/tw_keypair/tests/ed25519_tests.rs b/rust/tw_keypair/tests/ed25519_tests.rs new file mode 100644 index 00000000000..de8d494b915 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_tests.rs @@ -0,0 +1,58 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; +use tw_encoding::hex; +use tw_hash::{H256, H512}; +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use tw_misc::traits::ToBytesZeroizing; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const ED25519_SIGN: &str = include_str!("ed25519_sign.json"); +const ED25519_PRIV_TO_PUB: &str = include_str!("ed25519_priv_to_pub.json"); + +#[derive(Deserialize)] +struct Ed255191SignTest { + secret: H256, + msg: String, + signature: H512, +} + +#[derive(Deserialize)] +struct Ed255191PrivToPubTest { + secret: H256, + public: H256, +} + +#[test] +fn test_ed25519_sign_verify() { + let tests: Vec = serde_json::from_str(ED25519_SIGN).unwrap(); + for test in tests { + let msg = hex::decode(&test.msg).unwrap(); + + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + let actual = keypair.sign(msg.clone()).unwrap(); + assert_eq!(actual.to_bytes(), test.signature); + + assert!(keypair.verify(actual, msg)); + } +} + +#[test] +fn test_ed25519_priv_to_pub() { + let tests: Vec = serde_json::from_str(ED25519_PRIV_TO_PUB).unwrap(); + for test in tests { + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + assert_eq!( + keypair.private().to_zeroizing_vec().as_slice(), + test.secret.as_slice() + ); + + let public = keypair.public(); + assert_eq!(public.to_bytes(), test.public); + } +} diff --git a/rust/tw_keypair/tests/ed25519_waves_priv_to_pub.json b/rust/tw_keypair/tests/ed25519_waves_priv_to_pub.json new file mode 100644 index 00000000000..40f25d9b378 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_waves_priv_to_pub.json @@ -0,0 +1,2002 @@ +[ + { + "public": "6994de65574377e0dc1c8812065ece52c540d67d1f7cab456f6220250f012b48", + "secret": "4a63e14a607a8a1d47b943b5979945d07241a45dd40406adf8ea2282148aba9e" + }, + { + "public": "eb031e07e28e28a4ea1a6cc39cc98fba52e1521636f208aadca0053944983468", + "secret": "146d40891effd3b8280899d032a958627d9160e8cc08896a1e6b40964618591d" + }, + { + "public": "b60076cc30ffff5c29c65af9a13ce01c3affc231d09fccbcd1277319c7911634", + "secret": "0778c7bf255eebced7a2e4fb0711f1c069ff4de1b888cc7274217d4aba93ac23" + }, + { + "public": "00080f30de13a7ae491abb680c6260aca51aeb1ae0b57638f866366d53439959", + "secret": "dd2d31ce9ee8110c2c5de4915326fd8c4a01a85043b4940a17c41414b956f072" + }, + { + "public": "d879de2b6efaf97f5df282ffc70eb4137b82830479d4371cf9d8de9870a4ba5a", + "secret": "f1177a06f3b1286dfc56cf5f3773b2bcadac8c059d9f609b6b25ad30233a6ddc" + }, + { + "public": "3555525c9928e4b21e77d88c3aff9f97b8c036b9ca2ce00f46893b73314d7d1a", + "secret": "b31bcb19f19e195ace8aeea14fdb36ef21528a01c0c44755993038a75aa41a2c" + }, + { + "public": "2364fac68900b071ba419e8ad166927ba2d213ee00e4ac909879453032530a5a", + "secret": "1eff1b1f7da431102d7630c4bebe8e27a8ce246d950f526ab0a13904035586cb" + }, + { + "public": "c4611be6d5aa861e81719a97788723ba02390a9d45933c40a50d26f83ede5679", + "secret": "756a71c86d56256a2e5429a4ac2049fb2cfe1a3113f359dca09a62e0b92e787a" + }, + { + "public": "a41a61a6535cd5159a859567520bd200c2bfa4a61a5c7e6d61c6ba43e7d52775", + "secret": "e5259d670828e56c62fcad7e227d98552e8b72e0de0e701013c1360217716200" + }, + { + "public": "8f25ee46a015c582d29b6cee37c8e920548c542a107b01568fb601704bd15458", + "secret": "0a61418013eb7b7a6deb3153d3310509a40bd427b1e51f85afa987617649665d" + }, + { + "public": "e071bc94bfba35614be26587a638f7d3fbd446a6f2505ca22b3e9ea015fa2107", + "secret": "327f248eb44940b3c3d6ea6c336718789554a25bb1a19d6d59b35083d151a1b2" + }, + { + "public": "f3331bb6813827c6b4c710d8f54b1f5b8ad049e9ee5543556c7b76648ad5ad67", + "secret": "217327f9e1a353f5a1a2719df337e65e2dff5e1c3750c112f6cc407cc13c1e51" + }, + { + "public": "e21fbe88b06356950f6a36ee015ea6442c00395d34b5c98e45b0b8143576891b", + "secret": "48ae7ed9b9d9224c6482f45b6c8bb0df6ad84d4b6c6dbd8abd7a52ca24c12b9c" + }, + { + "public": "c52011c7ce403a4240314d99ef2e0bf738b4d479bc534f3d3f09fc99e21b245e", + "secret": "12161bb407ab94f151cec53b807e95e83f7e51cb81c5db2f92a1ca783b7e3daa" + }, + { + "public": "378c133ee53dafb1938086a91399f4e500c575d9302d2a5d2ab88abe3bf59e0f", + "secret": "d844f6b866f95442c849809d5458b2f1ec87c45ae8bf1590aa857b72bd5f631c" + }, + { + "public": "22af2609904da3b40cbdf7996ae2dafb2654b976646301695ce325e4f36d6850", + "secret": "6717d8125252add4aea6868f028f06d803352675aeac9e93df26d6687cd2e594" + }, + { + "public": "a39a7e9e37e89090ac07e04c4d50072df9ab637a93e5d192cdb4dca5e9bae77d", + "secret": "7f7048ebb23194a2087bbb157e593bb9f6af60ef787a5d89287afe165b3332d8" + }, + { + "public": "b4faab7ded2c3b0c7ce91e9862df23055d2bc26025506da643d919aed4e22b04", + "secret": "7e78a13ae3882b2f9dad87ff0bc193365a2068d656c6f037e10d5a7a6dcc9b87" + }, + { + "public": "07f9d642dfc018fa2d91f5903ddf9a16e1b7f09a4d856496dcfae8d6ace4ce15", + "secret": "2fdbb4b3420104753e7589d11c7309f792cfc6b8cf4e9c9a04f0960d15868d7f" + }, + { + "public": "3222dfc6d20a9bd692f7e461977c13b78c613674cc20af92833c999392721400", + "secret": "9a155f1ef3d654091707feb8902e712c5e7c652d91a04a730daf1b72e6a19a6e" + }, + { + "public": "0fdc953e7980334d24a64afbf0a8a2d3005fdaf0233a2a4f3ce62c3e2ecb7e18", + "secret": "15cbe82dcb60918e0e1528fa13371cb39e908d3ab0d6b18652e7f246ab29a4bc" + }, + { + "public": "6e000c3a8b47691cc72ec6d78f62fd18a2a7b6509f2715d7f611b96e55969019", + "secret": "a091b34a3aa6284b40a2cebf126e545489b221e4524b2f4db76ef397d0bbb114" + }, + { + "public": "1b06509ea4abd77d40774c74741e529165ee1c036f0daf49edc91cbce31f1a28", + "secret": "a924417b82e6956a79bd5c51cfc5dd25615af884beb6c5a57c01a8e573918fb9" + }, + { + "public": "d533a0e3379f5725212561037abb2b1fa7cb912c10f9e446d1b834be38266338", + "secret": "08b5c72057d8ec47da8b1d961a1b848d6f2cd410798cad9be6c640c4df2d35ed" + }, + { + "public": "ef0b4b4893a58d595606328807888e7f76090e25a099b69eeafd126b073dd674", + "secret": "f80d075e27bbad3f2ceffb937fe8de5c1e594e5dbf21401fd6050f16386cc6fb" + }, + { + "public": "359ad6381ad14c3957f3f6a124206b0d9667ca940f61c39551b8fb2c548c0d72", + "secret": "8e62b4cb288fed2bc4a6d3c64a854e9f2809c274247e6005cf6028d24d1f240e" + }, + { + "public": "25ce79a6b088366d6967c10666d434699ce3990ccb06fceb5364bec1f3691926", + "secret": "5ea106e68780fbd264b1bf136fc2ec95f2c8cf1075907072133f1b5cefe51891" + }, + { + "public": "1b53425f9151fd2bb1a7850c534059b49b0d220783bd1e8bd254c2b503d31e74", + "secret": "869fe363275776ce4b5df236c7563dc7cf237db74afe84babf1f8ef83d548858" + }, + { + "public": "baef1c25a7190fa98ebd23cd4b359ba2246cfc2ab03bb88a885e7e9601ed9e14", + "secret": "d0fae9281ea5beb59108786398f8217b584a4d97b06b38b2fa340007a3f853cd" + }, + { + "public": "43d37cc7e03f61b4e71a8afdb339d62d31c1b5c7b48a25bbbe15a9925cfe4e76", + "secret": "5670f149475f48790df6e0eba38a6252529bb71dfbfeb0f940389d9cf8fdc62d" + }, + { + "public": "6080127306f6fa30ab8b027a1b66e8c87930303aea56dc18b8a72ae9b341ba36", + "secret": "5e2c241cf3cb68e8c8174e7b709ddc6728190d7506143da9af0a4468cd2930f2" + }, + { + "public": "570b4211b5a40b9ade1f1b4d97614ad7646794482ffd2f9b7f251a44a890a003", + "secret": "be8914df5f3b353ac60c6afe19d5b91baab869f34550df053ff41372fe6ccaef" + }, + { + "public": "c2729d6a22350ddf132224fc95e296bdc9c3e67a2434ec0669451e1d7870f200", + "secret": "0439d1e7e5a81c50d81b9ca935a9f68aa065abcaad4f285e8f5763f2c73af7fe" + }, + { + "public": "d3a43c727fa5205ec1d9e97f8b3c7af93b671f59e6ca9620c918f9c310e6a36b", + "secret": "68a93a0bfadb63c68390ca92cdff51d96b221c93a7eec06432b007515facc1c9" + }, + { + "public": "08cb5bc15edbdef8ebd108275193b32335d7ea217976449ebcb5f878cc950d2f", + "secret": "cde5864cfc4e494c582eae6410c156485d5e51469a27b0f0706b00ed19db277b" + }, + { + "public": "fa6c653085fc1cf740e03419e9524fea51b237e822a56838d50c524f7abe1559", + "secret": "c32984d99bc2e1ef591f2e35197c0716628f2a0b994862f766188a83a04c3e2b" + }, + { + "public": "5375f02b1a8643ff50dbb2f64ef29747e1ba1e19dd859459faac65c67854db37", + "secret": "5347ed6e4e9ca752f79126fb5f7097ace9f35bc11c4109e8ea677f44dfd0c5b8" + }, + { + "public": "f7cf051dfb23d652ca73d5b25dba5a46e98534bdee51ad149a27f6e851754a22", + "secret": "0e3b3ae617869422fa58470cbfe7d24a1a055a064bec56d45e85aab6d31bc298" + }, + { + "public": "f9e5953ecb3e231488f8580de0cf5d7f0191c2dd8a3eee9ee48ca63b174afa7d", + "secret": "dd34761880e0178c9e15c12b132dbe5779363b4a32a9aa40f1d6e29e9244a790" + }, + { + "public": "056ca43fb3957363f6c8fbd4b76cb824af102bacadad4e247adba92409268937", + "secret": "c48fe13d525b3e3fd4f301d638230465300bcc97dc1b9fb7a5edf2ea5b6759a9" + }, + { + "public": "fb8af5ac9f47ffed680d37d5a6ff17066d21eb5a5b08090ee6519600e00edc13", + "secret": "31a193031d5be1bb0a2a315d9d75712a9e65128d2869a7ec047df7a20431fec7" + }, + { + "public": "c177a05bb3083acd81c2d6647c9fdad2788f32ae7d112fa44e5c808eaf54ec7a", + "secret": "64525189a50db2b9e83e6228dbf4d4425e8522063f3abda2c794f77ed5ba1304" + }, + { + "public": "b8eea5eefe7f26cc8a99ce4d154119ef0c9526e5b60188d4fbdc8eaf4227e671", + "secret": "acf65da635b934e4e11e54d2123e85cf495e4eb1d80b51840ac93114e78d818f" + }, + { + "public": "ca6a003239df6258f06be9a0b46474a6fc28d87a4722f97c98d275edadfab66e", + "secret": "b527b314156dbbe7feb67da76bfbbae1359def0ec9283487c0c59df0e66862aa" + }, + { + "public": "4a23451208a2c9ed82c5fac741a75bf1395c401386ebb1adff36a72902f83873", + "secret": "d034964780b37264860ff98bc78fd6542e2cc1e9c8ce8ba8d4b83c6383696d6c" + }, + { + "public": "59398ece5bc9a03267f218fa0849202426b4be1c185e95c3cebf5858bc1c042a", + "secret": "f13f614624dd1547df9127dc8c1d2c31ad81557eac71fb87d3a4878d7c1d4f90" + }, + { + "public": "db32fd9235e3dcfd26d375b4462c3a76973db17c11fc5324f400118a79ad2a01", + "secret": "92da4b9bd167233323952ea95644ab0bd6c20e33c94da14c8c81a8e3ba27fd2b" + }, + { + "public": "ab0428e769186de40ac24745ff56e8496cb77ba8c41ddf30924bd711b8cdd151", + "secret": "cc17419f1cf7ea1db93c338e3c061329e19de882479b0505703794af83d23587" + }, + { + "public": "e6eca9ef0ce8d4f8306ddc2b513c40a72e38b5c6c0438a2dd4cf96765e42be03", + "secret": "443e7d230bae77f7ec9b3645ce79f5cbf5c1989aa33dfa83960ce593bf60ae67" + }, + { + "public": "20174615d596e89d28be204a7f2dc6eb0b03e0abfc0f30ca2646f5d432aeb72f", + "secret": "a9b8cb1118ab181be9601aa9055e9223a09f43670639f0c401c21102fb6b3292" + }, + { + "public": "05098ec559b11e4deee227e96d356b3104072cf62212c669c434dd988d1e8a5c", + "secret": "b12681e6a607b842cd2588e4ea0ad7efc7337192a8ea4ff8e06078d30fb2235c" + }, + { + "public": "61124880383c36dd02352d14451965740a1fdae33e3df0dc073de133a7d6737b", + "secret": "76d5004df0acac33e1e2fe9807a9efcdbee48c7a61dab8fb10e2248083c4f75e" + }, + { + "public": "2846826a02e01ff6903efbe47733a5d718b7293fba201a533fb7308793953b54", + "secret": "a4c5df999bc6bc4a8e077bce3b1aaaf48485a329b381b23820bfd0ca19fe87d7" + }, + { + "public": "d4963b557f32d189507e26ad48b0f2f837dacdcfd67fffbf2af0928a5d5df447", + "secret": "0e3ef340811ec1d14f9a7ce8b4d4aa58144f2ebceddb58d97dfafa1621f0302e" + }, + { + "public": "1d57a18696da3a8e9bcd580e2bb6098df79fd6cb89a3ba8b1d60cb9959d49027", + "secret": "8a70dba215618735397e62b4b0187b989e45ed4b43bb44b0a0a03b8e376b613b" + }, + { + "public": "a59d0c2270a6eafa455e2b7bffbfbd49066d3c1959f26149a7edb41679b1cb0d", + "secret": "4ce2b583a2ba7c2acd59e236eac959a45eb137140eb9365ddc56fe060998fe8d" + }, + { + "public": "7343ddc3a9d5de18513fd23fccd0c325805797c3d0bd648e83c97bbd99270a15", + "secret": "1dd790c74a9309502a5fe40e98009aff321b719f2972f57d3389c400b8ae143c" + }, + { + "public": "d5d7f74b2881b7e8b2d96ff32e7576f56264bb83e22bd1e6f5d42940d5a29c12", + "secret": "132ee003a80ea46b698a0e26e6ff316ba5a516daba2694b52c87935fd44dc064" + }, + { + "public": "5015354d15bde26e3255fee0b691fdf908908198412098f841a467587abfe93b", + "secret": "035d47b3461653309da0e10c4e8d5355b13ea946de9a7bfe9c8dd456583a2f1e" + }, + { + "public": "e41b542dbed9cae38ff49f8318a35c19eae3473809563677cd07929f57284774", + "secret": "965f939df073ff64d393ea08654ad87c851adf5aebe790056bbc2d6c963cff19" + }, + { + "public": "7d901e0e3c04a5f43c798bdf3f7ade4937caa3dba1de47926c70979b5ce3f12d", + "secret": "24e53c34a6c58ac662624aa64754fdcf8bbdc7c02faeb4067dcf534c35e97006" + }, + { + "public": "adaa357d9536187bf7fb240ab05c4be28fdb07dd6cfebcf09b0840a853272b43", + "secret": "18096a25180f01fedd68f2925446d81af327cdf92781b7c5e073fc4e3c52a421" + }, + { + "public": "f96a2cc551ab9829b8d18823a059fe433d194a28b6ca8305abd41f9022697e42", + "secret": "872e2d7a638c4c6d0bf35581cce8877cf6a44b7e2fab912f1719d56fc33fd5d4" + }, + { + "public": "1bd5d20d362c845dcd84fbacdaa8accaab50b1683143b312d183b8a445766b14", + "secret": "db2a2b37b879401117f31584e8570089a6c0e50c94aaf6993cdd6e9dba8f6f66" + }, + { + "public": "bf1df3934a0ca7b07e545c7669d9b6fe9dad431afab8826327dc422bd2cb7e5b", + "secret": "e0a661cfa2da179af2c9d217f99d92ace8f23e25d349a53a0e0603fc0b9e2357" + }, + { + "public": "d6eae08d4e0c706a134c99ac66c09e5ae895a680349f7bf7248f240d3bddbe7d", + "secret": "0bc188e3d253c6ab33e850a4891d181ef781f43e84b6ce668fc67f8858b9ee3f" + }, + { + "public": "cb3384aeb26dbeaacd1d342e34260e5e6bce02ed411ced681ee24bb9a581b322", + "secret": "2bae4828adfa1289916b9cc46b76d198ed18344e71d19fb8e54e328a307148da" + }, + { + "public": "55a533610c16951c2859cb333ae19a011928183b996780e904f2540684d2660e", + "secret": "4605c62aa16d47dc96030435217e707be59b13181447171f5b2181d241009683" + }, + { + "public": "20f84bd24dbf0546a07836a54a1d1262539793d27a423e6e122721054768d738", + "secret": "34e2de953abf5d6269ab686adce78dabf245de8d0cf9d368c35394c824885eec" + }, + { + "public": "14ca2cd9f41da25069352068f8436d5619f3b0db0336c2fbf102a3abd158df24", + "secret": "7e41f3f288b809a204ca1845c78f6ca33ab806445b2181b30094bf8662121d0b" + }, + { + "public": "0ca2a8c6e1204624f59b76a6fc126f1aa1007a62b9f7c2feadb08695c0d24265", + "secret": "2778f0e3ede0848807cf1bb4d002cbe31a063e6687f7325c3c9de9cb9d153156" + }, + { + "public": "6d3709af567dea674296f4390d8b641801e5daaa0835f8983f9f22657bc4186b", + "secret": "3196df92dc5598113678f1699543c3aef7aef299a4bc8fb261b62e0aa5b15774" + }, + { + "public": "4f32c9f4353ef981c7c5a9607cdba44e1e9aae3ac910c4c4471cf3fa2f0c0941", + "secret": "8b82fd5f8c72d0774bc2eba80466f9f07fc86d31948c9d3da5b37dd979d8c37b" + }, + { + "public": "a3693d7b0e475cf1593da3d62ed047cec9f34e3f0ef42b9322dc77f96c7e1d73", + "secret": "ed830539c84deafea948832de39940a1335244b5e6887126d74ccbadb260b8ae" + }, + { + "public": "02d4bf680cdf16bb26d91e0a6f666746e9222ba24f27735396f9831441d2b74a", + "secret": "6d9fd49aeac72b20a582089d24b901267e429338101efa67674ec4b0d41b0e27" + }, + { + "public": "078c7216244ebe0a724bb3690d12a5af8e53ccc70e975484928916b9ddf7fe79", + "secret": "6240b9c4464fb01f3a4d3b1ca94d834deabaa02940584985680056d39c43fed2" + }, + { + "public": "aba1f1255abc39f08d2748363bc3d8da7c678d9bba35d4c03ebf5553c2989811", + "secret": "6ca1ef32bb69e837a98e93fdb87b01ed3369bad33216dcdf68c874f38c9f648c" + }, + { + "public": "cbff7bf5854ac68e3919dedbc39d964023c56ea85cecee1be8760d5033d07d2a", + "secret": "f1f025401266988bb31997bf0a84b2166526d19118fbe3986f01d3aff7ad1c30" + }, + { + "public": "ef759fc660e65264b7dc1d8d179e29d556caf7285aa9156c3333679965ae565d", + "secret": "6f2a1c2fa336fa19c1089144c2dae8208277809675d46337e8b9d9e742c67436" + }, + { + "public": "3709227e6374adca96a5e076866bf61cb1e6f3741cc3d125f770cad596875917", + "secret": "f6a163f9a3f9754e02e4418bb5bd5de0842709bb1af22d571e3a63cd258a73f5" + }, + { + "public": "f0ed71083ce9c7d35576d74ad5236eed2e5f3924f364b4473a6f05b8b8fb5715", + "secret": "e6d6341893a1418e2f4dcd0664b5b95308f7a907592339bbb719c1c2db0ed819" + }, + { + "public": "6a710148dbac02889cbb205805fc4ac59bd5bab0d03d86b3dc0705a4a26afd55", + "secret": "5a2ac4d1cb7797f493f621e6f3edd3bbfc47178c272598a6e206e553b5c6b4cf" + }, + { + "public": "7a0c4baabcf16c4ef380bebb539f73431f4917aa62c2ac4fc655c9d4efeb7f07", + "secret": "f03ebafff8587eb9c153d6091263a40e9c459001af0a673bf337afba65931c96" + }, + { + "public": "d8f9398472d39682c1940c2d0d6a6d5c50f82c1ba8e7f28cf015b24c95811b32", + "secret": "2dead0874da930142b011a1f8497d9630d8c5041ab9e93f558f29b44dc92f9b3" + }, + { + "public": "e9a33d7a3162a388c91217880784da105c19cf1ec3fd6b39b029ef0179895833", + "secret": "db459e207f3d8fbe4690eff956530d4b85790dd11b0b86fdf20d08a8d2603939" + }, + { + "public": "784a6e8727582e2968c1acaaad46df379622d2b03f182907ef847c39dfb2510b", + "secret": "ca7536f68f252fbc00086d768f19f621d8f14bf28b372c95815a5d76845d034f" + }, + { + "public": "0fbe08ab1bb21d9b22c6040c07e01e73fd5b358dc75316e4b9ac6c688e55ab2a", + "secret": "667cd2f0cce04f241a427684911ce6384cf9d7b9aea4d0b1f4ab21a952584bec" + }, + { + "public": "a55bedf307ff63ea89e8fe72c5e72b82e915bdd32720e5e5bdb65414ca398d0a", + "secret": "5330fd596c78f1c848b4735a506afe2c7fee89b78bae655d7159cb2188e25211" + }, + { + "public": "6ea3b0f2730527007011343073cbd6ae137ca4c877db4a4b6734b2629095b446", + "secret": "741124ee85eeca84e90162011561ced350aa9213830578618c5088c30e6754c4" + }, + { + "public": "f8f25013a3d96a5558f10e17fb3a621ab2fc4fdb5534ac96ad52c0c7eafd8031", + "secret": "17b5b505440e76edc6bf29d4f59b84654693add66b7532e538dc16e7a67bffa0" + }, + { + "public": "38c63f26ecb6d76477d839436ac30c71273263752f5775807a21ea9aeb772710", + "secret": "5689a0beadb4e91f37c6fe3fc2faa08afc2b9e64dc8e7bd6ed07cd86e9179b4b" + }, + { + "public": "1944b544c3a7b6908621d808668a93079d4f6359b643e40fcd84b6f35452b36e", + "secret": "ea4a221ef36a4dbc9e6e1ceb9ebdd72db36fe4d51b6557b1f11d982286ae97ae" + }, + { + "public": "e41245f7ec196cb67d1fa3e50ed55cc522c04fd306274831f506c8f3540afe2e", + "secret": "e98611926124c5eb1564e889abdce5518b21ed7a0800ae463973812d395c5c21" + }, + { + "public": "42fe2b9845b8f7ad1157231febf65c913ae321f952d66f4fa26351474b5c5d40", + "secret": "620830af6fbe28c6175aaf94e6f6337df5010f5e573104eea2845b39ba1d471e" + }, + { + "public": "d0a3f7d5879f84edabcd2f6e4b0f1fac82f502fdcf4c56bb52815c6d1895af18", + "secret": "55a96821c2b96632f14f422231c56a8a106bcdec85001a43bb8245cea579268a" + }, + { + "public": "a6380a7f0723e21ccae40932f44c28e5b26a8d695a0c68fd1466975d26d26f27", + "secret": "a25d23d4dedb46e9b03078fceb74627ffaf31ce86c065d41bb6e38d6f2bfe6d2" + }, + { + "public": "ce974854024dfc1c9aaf2563740e20da0b552a451377583a74e647dec3db8e11", + "secret": "9882e4380f9f45603e7cadb01f93cc3a601920c461b5d93dfcebe4636270280e" + }, + { + "public": "6bfb5e6b3a79b2accc9915083a68c9cf7065c2e992e1b71b9bee3ff5fe041e0f", + "secret": "d9c52a54ba1f93aae63638d9ef831d8dd991a85790f21998c2d2674f678d982c" + }, + { + "public": "1c1e03d9d5702ad3f3112a29a84ba8d8706fcd5887de2cde340a7163f5ebad66", + "secret": "94b00bd613059399075f6b947b83dbcb6090723ddcbe720ee63457185a0a52ea" + }, + { + "public": "7ae4b96dd87d9963ed17ffeb6dd1223c216271f6e7204f2457aa04f677750640", + "secret": "cd68d5cf9e99b93b5e43074a55c6e23e4340aab9f73caeaf720cb8e03600f95e" + }, + { + "public": "0bb9a66e96bd6b8c0f21846c2c7bb5f9cf4527293f9cf097a59f73734e32fd09", + "secret": "78fadac3449567c0e4e4b05563c0be5dcccbb0d718f8925342db5383c6266eef" + }, + { + "public": "72ff000fcc97869858c2e8fa765f14d06cb1f603e9f0081e01a08fababdac436", + "secret": "a5cdef938311f0c32142443e791b20c127a11c48144169427e6cda2f5b3af1b0" + }, + { + "public": "4e2deb3c8a0f92933b5531704f7e715f6e34885ad370229deb9c34b7a9ebe61b", + "secret": "1075a77ee9f3e22cace2ff88eb56d93df2bcaefae2bdb40278c0b47ad2200507" + }, + { + "public": "5669066eb3e11fce08d151e92b3e279bef5f3b7e0a24e01e1ee995947bcb3817", + "secret": "3de102544d8b1ff850809421a9a35a9f0739dfd8f89c8048c5b1c145e4e4a47b" + }, + { + "public": "0ea3d7f905fcb20067871274e575fc5718bd804c50a7dadf098b7c2d3dd7a113", + "secret": "e88f380d8648bb007101e4430e50034c03ec3dd18c9e4efec9a8506ba72d99aa" + }, + { + "public": "5eff5941e0cc179bd70ed01de103e8a7bb6f86b4852503e8cbf225933075e42a", + "secret": "4f0df8e1bdaa02bd03c8801b3c06bf8997b5753a5ba2bd7f9b4f93f65f492b5d" + }, + { + "public": "bbf5a7586463d369589bf1ee6f02a041452e7d05a44342cfb0783be6cc4b0f1a", + "secret": "327375899539b7d4dd85bd39cd5f7ca5861ecef75c4e04c4faff34f06c2cba9d" + }, + { + "public": "27e022b1d485bb59ca24745eeeb8eef94fb6778e2934e59eb3f3afc6dac6732f", + "secret": "4f3a4bff2c25580b7327d7c06c729e4936e58802a98cd6c7e986206e3b6df2ed" + }, + { + "public": "ce0b75b43f3c7c30e28223393a929e6c254dd5e9b7d573d9f5ff01993b4b4257", + "secret": "2c783effce1639254c4057c1f7563af5a1d72e8dc55d6dfb7a184e290cadedfa" + }, + { + "public": "c5a845918819aedb25d4b612250a139b35e43fa6240edc2569b4669531193336", + "secret": "4c273af47fbf8b08db2535b27013c8d05a646ef2f3ddeb0e11f97dfd3b291f7f" + }, + { + "public": "0ff0eaf436dcfb69c3c6aa6d049c4c3fd2c38f2cc5658a5b556d54d5a8b3bd0e", + "secret": "557b8e1aa98d706653edca72562fa4f449caec8f977fe093f53643b278732141" + }, + { + "public": "99657f18974ee55fb332b08fdcb0c33215187db27af681424f2a7f2718df0b18", + "secret": "49ab9030e9f9eb0e2c41e407a62fc708cc59959a669829b666e72cbca2042a1a" + }, + { + "public": "1423b22532c6399218e67b4ed870720e2a8c6d2c2adb1b95fa60b5a67b40b014", + "secret": "caca0a38932751097012bcbd21634357c0d4ca8b1c7dcd2780c0b09d7651fe52" + }, + { + "public": "3d8c29bd72a6a455242451daf30ee7cbf7888f179cb9cb245b72ded887995437", + "secret": "841dba6d20314bbea8ee049ba809dd14407ba3496b7e0c47642c09acb2267a15" + }, + { + "public": "ae0c049c808f6509402d8dec8a9a5eda276006e3854ef415efd013b0a24b5935", + "secret": "e77f2842e8562ded1e740bf6616a5888db180fe7321fdc9b171392cdd94b2608" + }, + { + "public": "a340b1c44d20e8678effc92d5732da2521024f3ac898448d54cb235c33535b3a", + "secret": "add5c4f7e57f65f6ec2ae40171250ed278b23d2ffc41868b82a7f782e1783108" + }, + { + "public": "6ac0494a7571b3375016029a5a927eef9f223c3898d4bcafda6c36e480daf569", + "secret": "a39db787574e1410a539865227947418c184d3ccf30c95533a7438250893c8da" + }, + { + "public": "3a6ea8cdd339f346672e81d3b7607241774b460eb71e5da90bb39225910a6c16", + "secret": "631f00e1b8ddc743c35915297e22a734661c650b75963fb4ea2198e6f3064718" + }, + { + "public": "99ba6e97e7fba9b94cf85a8ee74969ba7e4e92b7204b173ec51ee1b163a0ba01", + "secret": "770aed7fac0e99d525766f2c959270998aeb35bd32cb5ffcb4ed96e71b7f1490" + }, + { + "public": "f233c7fe6665dd524e2a18a628feece7bf370f99d075fd6d232d2c55269e8740", + "secret": "5883f1fca5e21322b6aeca51fe487de159a9935960d93b1f10ee4fedfab4f254" + }, + { + "public": "0f24e6b97afa293d3d1a9d3bb1bd421117661d4aee6069453e465734ff535d7f", + "secret": "5c6dca2b8f7fd0c4cd22f4d40d8a9ac68cf72f56de9b6a8dd8028f09cd28f5fe" + }, + { + "public": "94512c7a7381ee7b49760a173d1969dbb1bafcade35f677434b57f6399817660", + "secret": "73c0870018173c7cb556467d80007000d2318acf8c069a713b537ed090459852" + }, + { + "public": "0fdcda990c8fd31559be0f8e5242b670a612ea83ba5ccae058475ab1afe1b462", + "secret": "0f2816f8d64dc4c60a9126279c43cead073bd845788436501f721d84f4928b96" + }, + { + "public": "b12c450bdf7eb33d5f2a7f6927eb842a934c4b8fd2c9fad5da9c72bc4e900653", + "secret": "859a842ec9fafbf52962e55737a3eb5a93b1cfb2046442ff5708b6c03eb47599" + }, + { + "public": "f779e0fd223fd3d080936152f9bc461ad6f96a41739c5ab3a761dd2e99ee197d", + "secret": "5412ce0afaf1a19eadc753fdcf0a3f5fe1f5b84f16ae0f2543c1d35e4c78e22b" + }, + { + "public": "8e69b69ad08e1a155d563d6c61cebfdd8f60c4d0b9c1225053abf42023b8147d", + "secret": "ea16cb502797f5405606f5eb601cb47833e41d4603f780d67dee3cd89535da08" + }, + { + "public": "81cc04f358b68531ae4be700454878675e7993e459e98634b1b0f85b4258921a", + "secret": "e7b2a429a65e0943df1a03e719b08e564ef6c27d8a0f3f4640b161a6ca3612ac" + }, + { + "public": "43ca5dc394792d7609bd9173b14384d531a943cd11b86609f0a9a8a549b68e02", + "secret": "32936ccbc3dd27314bf6e272874b6a4a9e2b9586f40ebb3999fec15db26d88da" + }, + { + "public": "0a149c5f2fd5477cd6cb85263faed3249c4d2ef4f39e54d26f7f8121bfdbb40c", + "secret": "4bb47cdca486cd97c59042016cb315f15cc3750302f34ae6d3c080aed2e1b94b" + }, + { + "public": "765a0208ea8eeb2bb93d0f0d619111de7802951293b1bf73d312e422b3396c17", + "secret": "beb27638effe9784fb1ec62cd9cc4bc440a2f6b8838b9f97ff64f118c8e62040" + }, + { + "public": "db856e8b163711e7a81729111ab1846ab6b30a601113475c136c370ca161ed79", + "secret": "c871f61c23561a6d5ade21f410694f1d63185942b991e2cc1035e3a66b608228" + }, + { + "public": "1616a26e0d460fc9cf6e8c4a0a4fe1ccf2e42106d6bb69b9a78745978e29f415", + "secret": "a70b88316568db7b2bd7824b097a39db2cede20f118fe7bbeff4c1db4dd2d8dc" + }, + { + "public": "c1a4d23e0410ddd90435358939cd1b77eb0dd6c925cfc310518110797190892c", + "secret": "fbda72a1a62b144fcb34e73fc0fd3f527c2e439b53c1d58b337584869d40be64" + }, + { + "public": "5b100ae4253253850b2461471957cfdc414dab98fdae9b93f0aa7b1de8bee02c", + "secret": "20743c8f9120c97494a92122d65e67b7ed9445545fdeb436f4b26dcc311387ad" + }, + { + "public": "fb22aac2b73c30e25489ba597d4e61100f1221465b43c373cffa5bf51a562a13", + "secret": "55b936f4a6947482e6f7d0f328c2614f0e50d930836296eec08075b504e75970" + }, + { + "public": "3be71e616be62d226fe5330d14d4b7c44563ea48a0210105e008514d47f7df16", + "secret": "8221dd80f55d26db1a4b05bb846b5cb4a8f1e10fca3dc4aed9a3ae877a4f216f" + }, + { + "public": "b57dfde4681c9d5bdf866a0b29840a62e4c142ae6734cbdd098c2663419afc66", + "secret": "df75bc00d9c2b8a4c0ff824c6a7c72f2867129675fabf9e76db2c76bdf786f5e" + }, + { + "public": "fb456d54be21c49326392b5d94ff3239536438400fe5db48bde6554eda32473c", + "secret": "a2ed62555aba3d26a4b70bcfb867f8cd15788079935420a2f596c8c511a4b3d4" + }, + { + "public": "4c17f3eae951c7a404f0a90eb4e61b3355c13a0e3ae45602183f8343a7ee0112", + "secret": "b838cc1490b1f0e8f1ebf9d282f7d7cad68bd3cc4f3eff1a57bd8edf1592374f" + }, + { + "public": "6d43fe1fcd19ea1438ab7d9aae68f301236a02ab6ae12b2dfd658d25e34c4b14", + "secret": "e91b3c1da135e2e274896b2c709d9cd7e8b032679c6d70ae3c996fba5ac96382" + }, + { + "public": "10dbb44ccfa38cb69346cba7ae28568c0e95917477bae57c35641122a916506d", + "secret": "9c235931bbacfe1adc4823faf65a41884745dde11b3b7728a8e16f52eb2d0fd8" + }, + { + "public": "e70b009adb587f2ccdd59512f0dae96c9e9f15398aad88770f5c9406c9ca0d34", + "secret": "71874e6d2597864f06275760477f1bcf15635d6e231ec4b913a9a0fc1f75365e" + }, + { + "public": "242b73fa8f7710506060036e4d0140f2f25f88527fa060414339d9e048b49963", + "secret": "eeff5d88eeba478a0c0f56519126d6b1ebd74df11dc78ba1e3f62ef910641014" + }, + { + "public": "01fe9541c7f81f53d64e9993fad435b9ee29129051279ec6106f220b5988c451", + "secret": "bcff27c8bf20290b605b3aa4a07b707ed4d5c69182516899e7d675ca5c5f8464" + }, + { + "public": "fb1e78bd678107e9cb8f9bc857adb6bca3bcfe772c660c62b984b09d07223c6a", + "secret": "5b25399139cd4e64f8a6286cddbfe607e02848df82c2b28b897263c0eeec3791" + }, + { + "public": "4f0452a4c9e64e0bfbadedf8c52d68c74086becc7e7117f69a1d005caea83e17", + "secret": "566127f946c88e87a5f638d13e88361aa74c71258ef7cd7c4ea7d88edbb24cb3" + }, + { + "public": "d4c7d8698ebd7a33a6af3eedb11f869bc60a4937f5209725db3720ceec959d1c", + "secret": "109064280670b364fa161105740388614afbca92832a955254246a5395693df4" + }, + { + "public": "0557c44b7497b21987a20e13bac7d5aeb62be58f309dae8dfb03ed99fe173b7a", + "secret": "b429742f66e440d41860606be72b1c8cafba11c6ad18517e18849e187f1bad28" + }, + { + "public": "fed8b07486f7ae733f154058c7b9939b7772e73504a86f49c3a27149adc16912", + "secret": "d333106729caedfb0a1f402f670a6559ef6c27ae7b7bff1a1a03939d6065e1da" + }, + { + "public": "7461d91c29403eb29d1783f16ae8b4f73a04c057e8ec0bfe80bbab1fbef78b20", + "secret": "a6878097445d8da1c6b11fd0c94b7be6df441c13afaa3eb161a99001b363b4da" + }, + { + "public": "81ccc8efa64d4201b75c07f66553f961cfedc96ac634708b918eca5bb7d7c32d", + "secret": "51ea675cfb7183af2840fa32c5cd9bbd8c96517b0cb9ce1289f49d09b76e7380" + }, + { + "public": "084f5eea0eec63398fa0e050af41e3afb70b143c0791ec1b23ae2d69c7881774", + "secret": "d4cd00de03c8ca01809bf943c6e118783af6c5777c8c4df0779c198784b829e1" + }, + { + "public": "a5814079094de679f4382ab5cd6d1ce4a2efa5ea07733f483c8695c2e75f4f30", + "secret": "34feda2f8b44825f4d7f01d156459d189f5f73d26d4fdbe078d311b22a47dd9f" + }, + { + "public": "014c39493e244ea935735b1884f49f54fb5cc4f0848d9f65c543133af02f2827", + "secret": "dfd897b8bc7c39d8a702322fc430ac148be9f7e221b79da74eed11b2eaf85ead" + }, + { + "public": "78366780afebd8555074024844e28e29dde9428c0723de5acb9eb9941003ee6e", + "secret": "c43334b46bc4794d9ac465abd73b00bef3b6f834848a8d6efb7a7092016f6052" + }, + { + "public": "af2da2cefb1baf8d53bea4b9696aa56a1aa2ac6bd3df73a114f1ad127e392b74", + "secret": "84e303cd760df55e529d7fa60660305810ad7e4400c383f99b48e454b58958bd" + }, + { + "public": "43c940b742b9a1a7dfef2ebea65b878e2a4e8a7b4a32004cc02242f9d49a983b", + "secret": "c1f497a832171bb71382bf182036c3554befadaa94bad5a065c3b75050f3ab46" + }, + { + "public": "8d7e9e71653d6f96f900a1d890c8732f8ae66200ee2ce57079e693aadee0e164", + "secret": "eab3a84609111ca3072fbab3f59011cf575d66b46e802900fc78c84fd8b71e19" + }, + { + "public": "d50b6655910afdab1ba2cef6d5184bfa8153e49f6119cf0aec65c9c9d4d20267", + "secret": "1802c190553935aa81a800147fc65c16f67da28d199a248f570528fbcfac5ba2" + }, + { + "public": "0dcd9a8fd40cc007608844326f400bd897e8aad098bc1a010f7d4736383d5f3c", + "secret": "51fc61ef00cd01e11af7f2170376eacc7a66e5e0b4ba04d21f66defe2143ffdb" + }, + { + "public": "98cba0836efeba5a1921b84339a9c2c4b52ea5f9269b15f27e2a1413f44e3221", + "secret": "5051705d39c7a52e407baecfa7a64acf3342f8cb08c8e4de7e58adb579eef9ac" + }, + { + "public": "ad415c620b33e469d279e4052336eadf9601d934e7991a9e7ef41e5cf718d34c", + "secret": "6e46460f407256601064b0bd0a7918db5737613fc107fe9b67f82ffea844e911" + }, + { + "public": "23943e772b4d1f00cc9a80d3125dd2b8146b8545a45499385ec939ac4de85c5e", + "secret": "ec55e8bb406f0f821045dc649c73de8346c8d7a87f4b236d0a83ab0b95d6628e" + }, + { + "public": "b64c6380626fb775623acd604196159f8ac2b0b9a8d666022965186093b9de1e", + "secret": "da5a7876575cf8fec83b8f23600829037f09c55272effe24f5824a82ac7dcca7" + }, + { + "public": "74df2a44fed9e23d8805c5cc84b99609975e4d445149d7029bbfc6a35bf76f22", + "secret": "d3ea6ceb22a23b1b05957ce3bef8918b8929246ba6e681e84cb8e02347ed181b" + }, + { + "public": "d21a5e87b7b6f5b787783775ff7ceb7b176859eb4f29f5cf53ab42c085845373", + "secret": "871b8a1882464969b198a84e87b85d3c9c747691719333be097abdcfd547ae77" + }, + { + "public": "17b7c7ce3ba2618acf496ab3005a59f7eea378e931933de2f304326abd7f0e5d", + "secret": "b5f0d78358600218ea1b69f150c29f7811bbb023bfe29c822ddb3b0415acd8f7" + }, + { + "public": "81b438fd7e69d8c3e827c96aa6d1ebdc963dda816c7e89443ab675a18a211165", + "secret": "8e2fe876756355a6c7668f6a4f033adcf19ca89dbff0ff07bdc119b6ec574116" + }, + { + "public": "489effc9ceb13319cbb8ddb75956ec1ec9a569d397d53b1709babb2819d4c96b", + "secret": "3cd6cfca013cabe84650233c85503de9937fdc324f89edd3ca5302d20f1947ae" + }, + { + "public": "3f541ba487d34a312a7a9dde375eca9b00357afa56d1ead9cd0b2c4ff9302f59", + "secret": "9d9d60072b9ea339d723508c3dcc516c8c7803029531fc403b344e18ead6f09d" + }, + { + "public": "ffe57c58512d8aa71206b80eebf8d80aa12b1a8e4fa19a6396fa003504ad2576", + "secret": "a69c78c08dba93ce290e1408dfc17127ef6049f8cc51c89d4f6a5cb023b64d0f" + }, + { + "public": "fc33042293cad32006e3ac50117c73e6f7e128c24a68931f67d96a264f88427b", + "secret": "d219929f85c3f91916aa4a934d01a4dd996038e39645b23b90bee60d7d59556b" + }, + { + "public": "46c5a9937776f685b15ec8a0576dae93273381432ce40e66e3b9fcd9fd713873", + "secret": "8af7d5d79e7b89f1e6cffbd65e122ca7eaec2c8812bd037901daa5d797ae3105" + }, + { + "public": "920e10d29b86828f2a2566c942555e900c761e4228bbd5473eaca4ebae2a8237", + "secret": "4e9e160a35e3cb087e71576dc58073837512ad957a11c06df5d111d410dee6d8" + }, + { + "public": "3aaa7c0d4879f9abd7b890bf2d10b5b2390ea66bcf6e6f6089587400fdf6fc6c", + "secret": "532802fba4c2a547a5ac567c17e629b209f8e849751b21b394520cc71a099045" + }, + { + "public": "2b9260191e81149023e9ea899c18c2a677d58dbf50d24355b0d6d135eaed841b", + "secret": "b007f29defa29d462c21dafc280eaac8fbf8a115afba4fe688b647f58a9f3f8a" + }, + { + "public": "e7fe900b47df3892b7bcfcaf4ac91ebdeddcdcc15eec0789d2c0a9644908b83e", + "secret": "f86b1eabc8157d9b38a8051600c35acce269b27b76a7ef254067463ca9905e93" + }, + { + "public": "8ba5f7bd38311d8fd47481c2ca921d53736c5875e696aa7691edc1a0cd8ed63a", + "secret": "4ad8e7c2ecad61002d9f8954a8a9ca905ca52e7e6699497bd89e88a3f011b380" + }, + { + "public": "f4789a2ad6ed1bf5b5cd84e76693435a8753c74997bbabefa6ae0da594fb3860", + "secret": "0ef09023a43bf6a02aefd3ec45f2fda3b040096aa668fe41abed6ed9d4ba1a11" + }, + { + "public": "1cf2b06f55322e4112c4a783becec9d45d4b6a3cac772bc2dc0ea18cdfce595c", + "secret": "a9b282da6b74cd0ab0897e33d1e8d0c39a555f9d565e98c654743099dbdd508f" + }, + { + "public": "5ce201879dab59872ac37de2a6a855cf739e197811ceeebd279ab13b877f7e1a", + "secret": "e0a127c2ee4fd27c552f3ebfd8ccaccdf26ce43686e5c4c11801fd06b4e58fbd" + }, + { + "public": "b9d727f412851721e31f614816df3fe6edceed547479221646b2c5757868152b", + "secret": "83e5410bc0416e134810d24a243d213c8cd92de14027774df52a70a7ee7bdbd2" + }, + { + "public": "70ebd9e8b827619207df4dd9fdc0f01b5e691df4675e09cbf2acb36f7bffb17d", + "secret": "927b2190e69e181545fda5606175b01653b0cdfa6237f7592b8289d3021f3820" + }, + { + "public": "57bbdb2d7dee0999ea5d743098391e96cc7983a801e814966e4b949c5fcec455", + "secret": "5e25e6fcdbbc92f41167a574a4f75bf44e3111d01815f810bd1b6a20f7e8f1d0" + }, + { + "public": "8d7cea0f0ab63fc0f6be354c305bb7448baf5f5037a0ce00e3ffd785131e2839", + "secret": "09647b21eceb7d83b67ef8748176d2a5ef3ce95de331aa73bfdd691571834608" + }, + { + "public": "a287de77e069e222a3841dfe71711a2535939e62b1b27f70a5b45a0a3fa3ec69", + "secret": "4339beae000fb7283f94d4321213d1526d791985fe2c466f3ad55463915f6ec4" + }, + { + "public": "855748f871778910b3086358acd24933fe63cb1317518fd8d69c46ce4bfeab4d", + "secret": "14141d5e085e64d2591b49ecc9b025a854bd6aa129a69d9859a3a923e77b5222" + }, + { + "public": "9d6417f45af03d422345f1e325e5a0b7082ddd7e01baaabab9dba40f357ed210", + "secret": "34651ff9c9b965d34765c5f63a42caff1ca8996b0bed534514f61a9b89e19f44" + }, + { + "public": "7eed3386950ae1576bbcb8212a4a3e06d0bff27ac18a53f98679f7845d96a524", + "secret": "27616f9a7b5bf9f708eb68f2aeb0a4f526cc19690e383cc6bd7a984c86fc483a" + }, + { + "public": "1235e3b2333756341047dd619266118184195429b63ae22bc11b44df43b39400", + "secret": "f748209bd72275bc14e07b6a6cde7aeca7d716e0c52f65aaa029ebe6e8b71f6f" + }, + { + "public": "73d04aad12479ee79a0c80be62c9b305bae9f2cf6f64eb0711964a985b44ec54", + "secret": "65b41595baf7952650ffd631bc7fedf27bd7df2212be42a07953b98dff646ee5" + }, + { + "public": "40881125518dd9ec9c57c238baa168eaee4f7bdb4ae29732ab50adb87c1cd72e", + "secret": "9c5e81766bdeae41d1c0376461069bfa4b4ee93bd72b8cfef3088cb67dc460ef" + }, + { + "public": "eaf8f42f98cbd3d1f93f6e772754c20bc6a7f0b85b317fc3418bc772da98d37a", + "secret": "9d15236267f9614b03a6a6cf3e7447fcf9de7a645285f56daa348e0fc10894f7" + }, + { + "public": "621d8c9a3ac7c2313d0529d1fd55e3a98d5827b74ee22295a48d4cbb59c17a0a", + "secret": "35d5815a0a8dab2c3aa3fa83e68d7f626135ddb0c94e96af98432145373f5b11" + }, + { + "public": "f95e915fd3bda2cc4c1d51cbbc5a05b6f304e282ec936e4ed8f4e17f3905c87e", + "secret": "177040b621b4ffc60bccb5aa4d4e5501b2d3a55cdd80cc5d64b93ca7a83db3e2" + }, + { + "public": "6775f6c7985768c2ec722e816e1d9ff195aa41dfefeca2ea6f604addcbb8c726", + "secret": "06dec3c72a18b48a8a5617d030461009cfca45d5b8670dd1baf8c429aaa2c81e" + }, + { + "public": "fc44ce8adaea08e06cb18791f686498910068f1efbaf71e7aa396d68ff0abc28", + "secret": "f1ca54696e925e0073523550a2198a49d81590df0aa5778ced19e486295289f1" + }, + { + "public": "06ba36aa46827cf5aeeea0bf56e2e9e04d76248b0b33b5b72077ee1c36f7ba14", + "secret": "fcc53eaa920eda73d6d1c813022fc30ee472b3dc5ab2c0051e8866cb95dfc079" + }, + { + "public": "86aef10342ed1e14b2fcc72c93866c6ad4d0424da9a83172b6c308b33116c07f", + "secret": "13fea5d1989ae32701d1dd27090ab2c5647ee9ca38f15d846fcc33391566d58f" + }, + { + "public": "5d3aac592d5cd50d43fa263884704e2f2997f4d9e68bb597937fd243b716584e", + "secret": "4ef59312e76e1cc2923685c1c8b0dff7de5ea40f030cd03517286f1e22227935" + }, + { + "public": "7b8392f148dd47623c4ecb9dd4685a105f282986735b4ea615b75ea19182c611", + "secret": "9681fa6951c9fec62b285eab591cee02e5c865d08e24f67c9ef72cf0c1e4421b" + }, + { + "public": "e00624b93e378541d897b78c4331cde253986dc6e39a5c1b8d8574e89e055114", + "secret": "25aea5401695276d6e38dde3fb550e7f73120e4dc2dbc055bbe92487ce95fb13" + }, + { + "public": "077fc3c568eb72dac6067ffd5806b6dffe34e580e07a754cc2e763c0d347270a", + "secret": "440cf93ccf20294225337611a11552558c10c4db14d9bb2945d3c75ebbba76b0" + }, + { + "public": "7c7f46b58497c86edcfd39c6882445f6c827cfaade35b1ebe38fb75fdffdd046", + "secret": "ba07fb49a777788a62f16d3a1fad293d61301735498f4a73524a1e180cb5ec6a" + }, + { + "public": "7c5a5d8e114ad5bed1618791ca5b22d93f872cee234c0c74e5dbbca0ca84650e", + "secret": "d952957ffdabbeb6253693291c47f72778785f17aa91cf9d464a3652da48a5fd" + }, + { + "public": "b6360d66f4cb38903224fcb1cd0426dee32ac6c5eff3c677008a3f806c834c38", + "secret": "e7717faa9d3ce741cc3b2a145967b13be9edd66b61fde14e306a035755ddd076" + }, + { + "public": "144bb206422fb10dcccffbf056e7285549055d7c0953faeb84b11690cff56274", + "secret": "73671261e735aa33f884d5c437e736f468fef0252d24e2c36cf26764da01a595" + }, + { + "public": "37352d82aa24aee2f5637fd4defa9ebae87641c928e6bad3e16327c3c761b55f", + "secret": "8dc3e677569c8d0b071b7e583c2d0f0a5e2db379d630c27bdea65cff6f42cf29" + }, + { + "public": "60309e7715dbe13b9fa25786911db79375597029fca24ef3f433cea450fa292a", + "secret": "dbd3bd2482be6ba69a9a345b576be08ac8462c01894478798970f8179604a1d9" + }, + { + "public": "2f61a13cec9ce5f7e6aec3fc840bee39f438f4cc48ffc0b9db661d4086cb073a", + "secret": "b3a425442e99aa622f12bae44afb6a3cb8671613a5a6385e891dcf9f3bd95f66" + }, + { + "public": "d5d5b9b3d64506f08f74ff741f521aa8e05e1d5c22b31f122b1eeb6020a44222", + "secret": "e11063fa7c740e6eb4430245e6d52700cb3029a0212b6b93aaf93bc908396ab1" + }, + { + "public": "ee8735cb13f42a2fdd99c647e6d90c51211c06f6ae64793eddad67bd0f8c0322", + "secret": "f6681d46a6239978562e3e9b7c794f17ca87fbe31f80bf855e7c8a9fc0fbee2b" + }, + { + "public": "b05bf971722ada9345b8108abc2b32e2ebd63558f2c375161a38f82658f95503", + "secret": "b8ae4de15ab23b467cd839bff0f3da8580dbfefbea534205085d50d7e12709b4" + }, + { + "public": "d136bf21b6ce288dbb86b7ddddcf3a9ade093466f2a4f71620049840d22d7552", + "secret": "d5ea08ddd3b332a1f6c702d2d5aa25710fee1000f00a220d0b4ce5fcaac8ee25" + }, + { + "public": "13888549083b332860888441f98ee5a8a65814ffaa7c30784c8206d743cb713c", + "secret": "bc823320946b4c507948249b4ac16b21cce121ce812ad51e131be91380c0c7c9" + }, + { + "public": "4265d75743837a5b1e5f0c773ba82cdd0937aab39803c0e4374411cc921f4a44", + "secret": "8066428e1789edba02a8b4322f8af310313fc222176b12663e615d96bcab0472" + }, + { + "public": "6c2711b6cbed33ee3323c7cde0ed30b2e9493a3468da4bd5018083c18390b40d", + "secret": "6b9dfa924b93df1c90f8ee52877552f6f0b60359dc1d9ac6cfa778c31d431ae3" + }, + { + "public": "6a6f0bd1c60db7bff73744a1214e77cba95f7ab651673016cbb2124e9ee71b0a", + "secret": "ef6464b359b31c511b12d58d13969758fa4f0b2382860e93e8d2f82b152f0fec" + }, + { + "public": "d57c72bf91228167bcded2e7ab20948f923815ae07c3b51ee0dd63f21392fd49", + "secret": "fda528da59a45ec48bdb6de1172a320c756bd8d8be1902c383d7f6977b6c9c8d" + }, + { + "public": "804aa4d46d3c910069ebeb86ce8b535aed43b9dbf70a04cc0285c9c1f613f827", + "secret": "cb6ab59edca42fd9dfdb140a9ce346bef738142d2e7c09dfac0426ff0b7443e6" + }, + { + "public": "04ceae37c29b70b10924e9cf541dadde24671a7a5094d0905e2883312ace6d3e", + "secret": "769df69b8da603d3ae75cda76d5b6b0d04b71f0415caa9742e5e8803540386ea" + }, + { + "public": "6d1a6d6b626a513466c38f269a6285eab5e69feca09e1cfd3bb2d8244ba7c834", + "secret": "7dd9d9d2ec18ad181c28d161f75a38ac1c219427689d0fb512b0e55de71ddb2e" + }, + { + "public": "a5f7d1bcaa7e336c6907cfff05d979374ff8deafe9587fda55950017d5acbd3e", + "secret": "b0b9d1cfcd08fd5780549c18caaa8ce3f2817912103e196038a561d921035562" + }, + { + "public": "fda4d2451ce631121677efc088a0540655f1ce15d3d9d9910a9a0197ad20b538", + "secret": "0c7747cb369389baddc5b83a4a277b7afbe25083c5d25cdce7b8d2f9f0aea114" + }, + { + "public": "589f85095f3273b1de05db1e00c53d2a34c47b57533c6f89657ee90ba8a8d478", + "secret": "fd9c1f185348a862c07da47d0c5aca5dceaa4c81bf4ffc7487800f2e12bde71c" + }, + { + "public": "15aa7cef57863b0245069f553893cee448ec9b61a25c4a563cb28b36b239ce52", + "secret": "2bfa956b219b4a9ab578089509eda1168f2196f02f7a7760bd6e519a21cdbc26" + }, + { + "public": "147255923e1a1982b73ddf032d8d51ba484f7653249df47510ef2f001cf99e42", + "secret": "68e9ea014fb450a6d81a7eb7ba43f309253861a61097dae59247ff608d9a7305" + }, + { + "public": "6e21a15e1a9fa04504e02a2111e0d8f0d2fd4ad3f8a0bd3606dddcebd2969b79", + "secret": "26f26a0a0c0b573afa0e77f1413f805f5825d9a3c730abc7597569f9da311f9e" + }, + { + "public": "7734cbd1d586bc77e867eb14b219f63974e9724b8ac37818c2d17add5603f83a", + "secret": "998bb150fb14d68ddc6e2c9467b8141b08ca1bb59a7a184052bc5d737c69b648" + }, + { + "public": "3413300f46d28a7fd6492fef510a18df6ed6031346ac41c8250766c6d0544136", + "secret": "e3dd37744d595596f03204295682b73fcd24096d6c0832543407443b9f7337ae" + }, + { + "public": "6cdcb0b4d73ec666845899e8500f4bb25795e2657abee79c434c8d3b8628cc01", + "secret": "6cb3d041b8978b878da9ecea6bf6b680272e292a1f9ec4f2f210bc68389ab104" + }, + { + "public": "12db7f8c5c6db8c77d7b5682430d3d06a6e739b58b6e45e34639758d89276700", + "secret": "93784879b17b1184c65dd918dd06aae9cfcdd047e509e70b7749f9e11121d5c4" + }, + { + "public": "69245c5247282b7845d6dd7c6841beb8136efb58a4b5891dc2d5641977a4ee68", + "secret": "19ebbc890d8601d2e7e0a044da4b03a13834ed4fed419c621004ebd4b3afcb85" + }, + { + "public": "aeb014810c0f1bcc438f5a1169504e8935feca342f9dc8b5cba9c3884917a220", + "secret": "cbf28cc8663de65ea78e56af5f1353c25e77175b338059d814ee6591321afe38" + }, + { + "public": "56975e687bce30b4417bcb0724cbdd345bf5f44140035ac55a7e8d25b1ed8e55", + "secret": "70caca8fa29c031c7784b5674cf09d330543392083b20bc6304cfc8f2743a16a" + }, + { + "public": "54be305e1fda37224a58a5301fb04594e09adf1f4824121a170ea947ac5d9c05", + "secret": "4fb076d283c04f9a6e6d19aab3cefeffe7edca3556db179befa162ad36b9f043" + }, + { + "public": "942caa62a801f1ffe906b994be8b186438133506069dcb70365d7b9812feaa26", + "secret": "5d9b1a1c1e468a881df1cb0754306821754963ca57abfaefc63d618bcbf03b0d" + }, + { + "public": "bc3fd42006effeb4d66a6f15c403faba0ea5ef7090051badd7859d8bb0477f01", + "secret": "543e94729d0ca5085b4c25e1c760962fa81b5316d3d674f5108e3c85fc7d5489" + }, + { + "public": "a80d0830960bc33e7ea8ae8239e5df686b6528b4628e21af8477e44453cc0a52", + "secret": "b3201630e31dc7ad5ba73017b0dc4171296fe72e4016e95c81853f775ae8bf9f" + }, + { + "public": "f5f5cb181f046ea4c17ecd1b5eda992318a7194b3560bece88bb2de12d78a863", + "secret": "e6643b1f08f9287e91edc2b4c010a19f278cedb9aa127d9873c56859484962bf" + }, + { + "public": "60eb4e500825774aff861e8d1ade23130a20e18246e2d6c443d987fddd272f31", + "secret": "f487bb3c271c48e9600538f7048ae95406efac16dd622669008ca91e741a7595" + }, + { + "public": "5e7db7913618ab264e6e84604f77876ee5b54ed35cc5926a826773410a155f2b", + "secret": "bd544b317920e18a73ab7652684b57dad813da598a8c4d5429452e372f423b62" + }, + { + "public": "5514e5e9bcfba7116cd4aa5b47c5071f29f0292915ef8da2c7020f353741221c", + "secret": "9334d4e82d6bd8fee304aa0543337e371beb0e36e6f2bd76a0f900f9c540765a" + }, + { + "public": "dd2084da1ac4d31bd2aabc354544bf20364e597af5a9540f1b23302d8bed2d45", + "secret": "3d4bd068f26ac5156b9ce740c39b7e08641c32aac54a3532aea001cbb38acd49" + }, + { + "public": "45e854af2994cf247cc8b60c6280d4f13c08f64217e028afa464883c57876650", + "secret": "9474425fb983085b9f437dfbc0af3c1714be0e41165d3120cb527bafed55c42d" + }, + { + "public": "e860439e3e9084309857c6f3256cb320f004fd94d36ea39a5e3311514d108850", + "secret": "d560d0c796e682e61de7ee1d368a09956258041fad7d6c7dd6bb2688f19ba2e4" + }, + { + "public": "9eeed3fe087ab3d3261d75a6cd82155ea1b63e9dc244fd7bc00e37de49e4a840", + "secret": "3e9f65e499ec39b91a68407e932c88280351c9c81861caa04d6688783333d5c2" + }, + { + "public": "0ce81007dcefb2ed31954079b6eed84a273cd7d3f4b02be7dd2f4cf4356d4103", + "secret": "da41504e4dc1782cfe9e34a7c7f2c49b99f397ce182b0a4fd71cf7c295c1f474" + }, + { + "public": "fa961ebfe7558c40a574338020be7891d7cd49a508d5fc69db694ebaf7148c6c", + "secret": "6692d3e6f708db945ab4f72c853416355e0a5738db8b59c89f402d3119414c3a" + }, + { + "public": "241130a3eed3a7fdad6669716c84ee2e7a2047438ad6307930de3e5e96b92461", + "secret": "3ae9ced7b9529e8c6072be5caeee1f4110c1f30dcb1bb879a27639c8890fee73" + }, + { + "public": "1d6e45f2a4fdfb3dae3d9acdafb2e591b693bfdfe4f58af46dadd822c7214924", + "secret": "386f99bacb84e2e5b460e5e49a02cff64053ccb4db3763bca3b78e8d45974790" + }, + { + "public": "d1e1e13c7c71b33a525f8b37382b86c238a21cc8a02fe0bfa20b007be35f085a", + "secret": "5f20aa8e513eb3145f7d535f06a9b77ab2a8589e601fd556e772512f2823a8e9" + }, + { + "public": "fc239a4ba46c68778dc874be3fa9e4fb54c340917528bf8585fc17af1b414c7a", + "secret": "1ab804f852495f5a1bcadf709a5cb9336e665e5f753afe51c05053681ebe46b7" + }, + { + "public": "2c4e0874bbe36a11c45a2a7246fb15e8c95e085bf5590df560fb883ed5f34432", + "secret": "a83da2d8c0389b7c2c1191c57a802ec5b2f9677327148f1c03cbbfbbb8518d93" + }, + { + "public": "c69204da6cea251e49a971e3794a7224ba66881ff20e07790c960b7cf8f99659", + "secret": "fb29b177210a02aa0ed2dba630371c971171e97d3377c9a1824466ee0c913fc5" + }, + { + "public": "f06a73a35def28d22a1da371b6fab270dc747b29b0ecaa02a6b3b74113c6872d", + "secret": "9db5c9e0fe6216f1202534cb28c93a77db6e210a55f023d1fdb8dca3550d9c6f" + }, + { + "public": "ea6461069da077c33a9fde81f1460ab3e9827fbd718b737d7ff18ed216d2094f", + "secret": "4ad71da558e79c3e4073bd8c54a6e0d14715f9fe85a591dc53f4304cf87e6e67" + }, + { + "public": "006e26532f7785cab6a74c3fe93570e158282dccf7fb1a7468d9c66b686f5f54", + "secret": "ecd7c60c25dcf6e90bd85d08b4549874c530286f2a6c5c5275444e663de5dea2" + }, + { + "public": "32c43164e1f1539f3b085a18deae0508d628cc578666cf9ef5952ad54aea1f5b", + "secret": "ac40bd5d5db20f54890bc8471c73aa6f969c210e08acdca317bf401d52641fb7" + }, + { + "public": "55c2e656b6cdb4e023c1e298a09add41993dd2f88a70e98d586da9f0fa58ab47", + "secret": "05f2c35758acb44072eac62935ebcd6671716eca52b7c3b6ec776588c3c545cd" + }, + { + "public": "8c365d2bc5ad8d2747c95cde82e662b4e3061b1c05c5b89308fb37d7a34a3217", + "secret": "5cbbf9eb991b81aa9ed94ea1993da93cdad786891955609188507e82d1cb40c4" + }, + { + "public": "fb3493a292e45107df55a848cf47ea44f36342c4716700f0d48afba906965272", + "secret": "44b743c79839434987f4e2f92796cbec499de5764671ee2f9557d19267de30fe" + }, + { + "public": "41569f7142dba53949e99a8ec51a09bd50fb8e37a34e2dee0aa8755659c5da5a", + "secret": "7498f97e5ad531056ad0f30be9c989c59d9da670a77ebdbcbab48f19248dc65c" + }, + { + "public": "4bef3f7a31d21488f27efc67de130e3eb6c67ae96743b8bf80283fa599661318", + "secret": "01f0954a3c2b94c37e8715ef7e42d65340afe5633b3dcae729674798684e8f80" + }, + { + "public": "e23769c2aede700f41d0c04922aa91b64b6280ed8aeb73c2c85ce9f3759fbc18", + "secret": "59d1318513703241a921dd1923a74d1864845ef3fac585c85228b475a45d1a5d" + }, + { + "public": "c9b264dfa0e4f7e0fc20744d881267dd76d7dc118c600cec152aa2cd9d73594c", + "secret": "99edc62de7d9b4d979a33165d85e0787af48a5840481031869f40bce2527db84" + }, + { + "public": "73f5aab6e4345bc67a32f1fdee50227c9a6ae0d1c506b845adb6c99042f3b64d", + "secret": "17a780c8099ceb98d50304dfa9381c7c27a1619236e8c6caab18c4a3004213e7" + }, + { + "public": "64a3405b0d6af938271fefe89b1fa8ec24bf416eb3deb0dd9b7287bff9773e16", + "secret": "6e8bf7080a46adba9908d0f1b38e2bc2f17d6b95c5e392c7df53458f81abe6e6" + }, + { + "public": "03fdb6d182903a68616f54342a39654717f078091613e9498f301268193e0a68", + "secret": "c320836d137c77b46442f90fcc6b493c00460c4fc380f384772f8de65b92b943" + }, + { + "public": "21dca878a554dc6e27d9c9d39dccb6182dc89e9be3cbecf49701890da733af7c", + "secret": "52047454477994c320b17a4400c284f17aaba88029b942620c22ff4da9476a49" + }, + { + "public": "042c0e805d7cc5cfaf959d8d68912715d4a06a4b975fa0d514348f81ccdcff67", + "secret": "5ae57e19a9274df2d69144a4b30bcd9e4244c8082057fcf67ea229d0c8caf75f" + }, + { + "public": "a9122043318310e035f6aadc5d973f88107fa9178742ac6174691055931ff746", + "secret": "545a481334aa8814509c5c5eea83496aa1ec5a0f33257a1b0b30697431bfbecb" + }, + { + "public": "6fc1619d2f950668e4f9326710f52363d09812e5acc89f002ac6e54a52243f13", + "secret": "e2f51c22ba8a50149286459c0884448d03e52cd00f448604b1e08ae0aba6790a" + }, + { + "public": "acd15029368b32d118037f232a7e9a916d1c3bcd27a61f4cae7b3f7744ace571", + "secret": "6ffeafce4e6c453f6c15972f15b1ba2f1868b429b2372fafd48af32701eaf5b5" + }, + { + "public": "5b3bcb49938b08872b9232e2257038d3e408201663ac284dcd3277a0f9290c5e", + "secret": "18e4ac7eab66ef92f604c839374d7ce1b0cef7722954d9455e735a69c0062348" + }, + { + "public": "bbb5a0b5bd3ccd5f778730c5b680a9c0e8a2ee6528357b8cd36a4f6af3d19d04", + "secret": "f4fa6c58de0f0df421ad52fd171b966a2c26d258552a425c1c97b9fdb5c3073a" + }, + { + "public": "8e44e590910df387d324684e0d8fae291e6b9ef85d6954b70dbcb3745890b160", + "secret": "12e7d34ebf29a60b0424be890a748b7611f226751062af79321c0fcf53e48178" + }, + { + "public": "9b9619d756cbcd3afa8f369006b7646995ca35f77951268e8320b5ae14b2037a", + "secret": "3d2c9f37b8ad1de44a5fada4c872d56c4e73a67eb1dc5076d52e1ac2b68882ad" + }, + { + "public": "5c30e2ef7b8fe46f5f8e2a0d1c5fa6563549fbd0846c4fd7c38b001bb62b535e", + "secret": "39cf0a6340d17a00767e3eafc44ac195fce1f6d8e6d8d7fd237c17f41e3b9bfd" + }, + { + "public": "b51ac8562328c85a52fa20de7de6473fd5e5228e7cee8e466bf32dbe9c13647f", + "secret": "5d904793db3db3b4f56f61e1bf33aa99dc0f72e4edad40e35a867f0f7323b50b" + }, + { + "public": "46b906a54f357d0a5c74a29e52be99eb980be9aad69c1542aa5d755a29e0ed5a", + "secret": "dc21e747658180205df09b53d7dfdf879771f3c9ef170d0bed2737b0e55d4ad6" + }, + { + "public": "b653af87351e38a1ce4c58086e77aedf3c804ab1cd023c7d4b1d923011eede7c", + "secret": "ff4a000a18004271777d589422019d9d23778f3cbedc571a4d3af7eb9fa5c11a" + }, + { + "public": "256c147671512c27369979d034b0cb9687e3532df253bf76d74f7133df9d6174", + "secret": "50cb6583cc112ae4fa210d4b0522711dccf16690bffcb0e9541b132fb8c5b8a7" + }, + { + "public": "513906968b1002d2e64a82298c577a9c70fada420b87ed3599c93b25c2c22403", + "secret": "97b83bf153af4a0f435e2abe8684f379db34aba4be888f90ded9024eeb10ffbf" + }, + { + "public": "db785ffd57270c22604cd943bb890b8c82f43e915db0befe98cd81c42df5135c", + "secret": "61455e486ccf1f3c40be2567f94f5dedc16922d1a760644baf2c371007418b41" + }, + { + "public": "d8ad195616c5e825c4a2e3604c6e5acaca2137b4b51e05800edd9ab02c56fa73", + "secret": "8147d2e374cbb7e2f6fd35f4e6fb9a0b68ad79a1435d54dfcada5d56bf8dbe60" + }, + { + "public": "88d7d389b6e05bea5255a7fb9ff2c3c26a9331563445a380e7f4216dea4c6260", + "secret": "07e98119bd6f7fa7ae2332e3533848c405925b8fe41c0878eede6c8f56f37752" + }, + { + "public": "e91fbc64f4fcbd92ee01505e9be7d03364cb29f4f55b0a7d3ac105f451eb8d1c", + "secret": "9e0190fa55c6c9ef8bc83adc8241d1f6b8ebeec2d823239db46191d4131b7310" + }, + { + "public": "ec44b62974b5f4ca0e2639423f008584dc0d662fb7ddebdf75e8f5a4e7eb2336", + "secret": "8fcd65341d62e0ee00f403715bc191b0ae55df7fd16c8db371088ee715c1ca3d" + }, + { + "public": "b62d9236922b3083ac6b1fbf82e56378969f29c01c4369d5029a987c6bfc1811", + "secret": "285a74af5cd7ed5b06f7de50e7b5e07bddedfdb59a3771c966d4d6e3eaa54f21" + }, + { + "public": "952350b6349d34990da4bd988afb47a1963c50685b0d66732c39e98ae169ba45", + "secret": "7ef3a3669f7a7cbc3f9c024710700aeab9ede21107ebc34199ae64377707835e" + }, + { + "public": "c5c377d03b2ebe3134c2e2ac8a7250794f37a232f7f309e65daee8c1b9a2f734", + "secret": "a1bb4132f6bb7c86bab0780fb9f82b1fc80e8ab9b67eca387e9c76304940f557" + }, + { + "public": "afdc9e47b50ffde40e0614291acf3ca59e548ebe5183448444f9b77661a1f32a", + "secret": "e279d2043d1ef090f05f60a9a5556a513d52e48a7aa7ea89bf8fe85ed986924a" + }, + { + "public": "0164ff434ecad8ed5c2beaba9ce4c610c895b1ce54fc8293a954a0817407e109", + "secret": "8fc46f49ead6b7675f5f4018610b142957426b9f8f4ac9043d8216504546ca11" + }, + { + "public": "17c05bb31a287d5cbb3b4c95c879bceacdf39152eee303f745bbcbc5ae1a1240", + "secret": "aa88473b318d1fc49037f2ef9cc9dbcc3dd18e545555c9c3349deebcdb54cdca" + }, + { + "public": "2e725a8d05e1229b37efae480de4fba6191348fff88f98bcecb9c8dfda1ec81c", + "secret": "a7ec0952a486288276909bb235ae789e477437d32b70325b8db40f8f74e699c2" + }, + { + "public": "5e9962807d967df7758f6bf1bc10edecf209927a203b98bfbc6087eebff99f1b", + "secret": "0d3becbcdf96933948f59f33395bc1012fe699cf1c7f98cd596d5ffd3ba3dc0e" + }, + { + "public": "d89e97d6ee033b3272e25267ad552ef56c7b6745c734b15d6d1d7141701ad243", + "secret": "93bf0780ccd4714338b0cadeaf8d241fdc3acd9f346bf67ae58a7088c647ca88" + }, + { + "public": "3718e5b8178d00e829f9d4fc9f9798a3eeff9d151c01c35bc3f49b81f7184265", + "secret": "4a7d9ccdd6de04ca37add1671630a438f5c06a14350cb5254ee93e5d6a76b551" + }, + { + "public": "3668eab4ae67487312809487799be727363f0b685727c1c1f7fee315470fb778", + "secret": "9a4edd4171ddc2cd22f40992888fad9afa5b5eace9c0203cc191c7664a9a4571" + }, + { + "public": "a73a9fb6fdcb788c30d14948b46420764e848b2a997989f5a4733649ff505677", + "secret": "e49d821d0f021fad289626bb88016d399af174e953ccce281c92504750ecf2ab" + }, + { + "public": "59c39a45ba768fd47ad8d174b4e05c6e92379174bb18bd0b40d35d63818dda63", + "secret": "7943c5a4cc161198ce51e013c172971f981c16744ad2fc464f2637480b821e85" + }, + { + "public": "aa0a94f992214a680f8b9b36796f32c29e788f45ec8dd8512155a2c0f304b926", + "secret": "49db0b7eac043592b6392b4db91e1cebfbce4cfc875d031a9666a5d309f3991a" + }, + { + "public": "7bc1c24a2ba6c58b9186495ecb63c6400a86be099f379fb73b4bd9ce47636458", + "secret": "cdbf0e871d56f5ba22a8dad7d9b6b71a76ae537fafc07c2366c72a7a3949f2f6" + }, + { + "public": "bb206715c5000425a202490adf30401d9d6bc4786b5ca3bdc69edce3cae74b5a", + "secret": "fbab6a6477e250d1eedeb7903af503a125f5d9e1ae1aa9dc1d9dde2793641ab3" + }, + { + "public": "443cc2a2f9cc6107fb22f416fe7cfcc952c3a6dc9d08c1c81ae7615eb9ab5872", + "secret": "208cb7c796debcb14b1c3487e0a9ba6cb842850069ef13d735c627359e651dde" + }, + { + "public": "a82ef97f2ec0489309e830070f7ce16b2df30237ce5eb6dce080eb3cad64f258", + "secret": "5fa525280d766b6a46464dfe5e905af3cbc2c2da323fb24fafe85604e4ddd2a2" + }, + { + "public": "adf84eac414fbb87cfadf7cefad334a0ba370fe1ce73c5f8abda29ec1ce29f2c", + "secret": "68261af16d221e2eab1b46cb9894d23cdc3540d8a958e64521d246e1d8766ee7" + }, + { + "public": "b58f8c4221db669518e849be76ebea6d2b6827918d365cb6be30fbb2c8bc762f", + "secret": "07169d161bb5695fe11edbcc86e9f1e07385908230bc9e8a8f7029963be5f445" + }, + { + "public": "87983f5574fd51d9159fe3dcc16d12a90686f6eda4a69a48f72660d71a436455", + "secret": "670861eba4e12707984378081c62c50b79e98b1c322ce11835d274abb0a19103" + }, + { + "public": "337c86409a95ff937dadec8aea07387933a399242e3fd2af3f4698dc68716c4b", + "secret": "d6b783445522a54e9c67535465309f3bcd2717226d26ccb53fcc749812645e88" + }, + { + "public": "ed1f185c7dde509650da2406ee5f6f3959f391cd04a48d8d52a733c371f0337e", + "secret": "f40fcd14e8c26b07fd45c6493ae70d38deeece672f31ba9c30510bbe41bda71f" + }, + { + "public": "fd57a224a89b1a63a6e414e0af940d59d9b9ccd7fc5298c2010d1a7b7b61d06f", + "secret": "94d8c086e2f3aacd18a895e18718b489e907e969885e54c828addc92ab04deb3" + }, + { + "public": "d5d75fbe46e6f169992ade9f36e6362cadb2e57eb8efd8ee7d1d1531a12af703", + "secret": "e15a5593b1187da516162d294fe9bead3a519c7f5b2616c5208f60a5f8308f88" + }, + { + "public": "1d90f6af69473c19cb9c4515c88dafe5f104acdcb8d62105c885bf32070eb133", + "secret": "e97e4a7a86be0a40861ec76ac8ae7ba4e0a90e53fa4cedf3d8c860b67905897f" + }, + { + "public": "5842d29ee6a9f752bc0b5bdf7b614d65c5839b99dd20e532d7311addf75ce769", + "secret": "4941852b877820239d3899629ac728aa218ec2bf4a82cde477462381541ba134" + }, + { + "public": "4b153687e29978f67ccf00c5f97f72de79c2c21a111be8e2f4e8d1e0461afd40", + "secret": "3061e7ead725f74ef42e286372eb06af700942d4a9b28f9faddbe438e48c39f6" + }, + { + "public": "5431a01e0b625d9ac8a607919d286e2ff7b91627bbd94f591d4fb291459d2b72", + "secret": "3f13ff8ba6e19ef59bec324547dd2fac32ec73782ef1768eeab13822c4fc9f2f" + }, + { + "public": "b6428323816d31873ec8d36d043d86f4bcd987e9f848a2014c360f9f60fe604e", + "secret": "b0c26c2ea1dd78368ca104f0c83d23293128e866900312f95339cf00425c6c6e" + }, + { + "public": "f2f5427d404ec59790f4f254f773fffc84fc1073887faad0b8aaa624219b1d2c", + "secret": "d9b6992800e4ef14281c8f53fefb813028ba4a7e69a6f9a5afe468fbd206b7d4" + }, + { + "public": "fa93bc82ade9dd90c45a27f4954a82914767d9a01625c140530ed0183805487a", + "secret": "73139ab0e5dede0cb93776c5e0cb9c5b2f15d92dc5d6ea068e8d5a13f1a17d3a" + }, + { + "public": "51405ccac428d954ea9e0b61e924b54ed40c1519df1e21f205f65565c0550366", + "secret": "d192b23eb8cf49a02533935fdab6bee4aedff2e8f216d26aeb238569ef3fc20c" + }, + { + "public": "dd24eab8c5e73ae9f3dc014baa9f078c11fa4fcb8cad695df54f6dacd42b8a6e", + "secret": "3ae90e576577db68926c6afae1a1cced00eb7df41bd916f245ad0fc8751d9015" + }, + { + "public": "4efca589d1ea7a5222710bdfb9415ffc9b79be741a40681db47774fc7d3f9169", + "secret": "9c5b9c3bc4671763a005c6ffe2720eec9c90dab24d1e9ba9c2651ffdf152a11d" + }, + { + "public": "2f9808bdde54bfe18f3916634890d2ca24a26abadcef024c3c0db6d5d70d513a", + "secret": "476e50f2f5938a878226d6399ee1c7cf99177bb1b5e92da27f890d32df05107e" + }, + { + "public": "60e264ad2832ef69d29a2c4ae8da92b70e0617587bdb393440156ce25dd58a25", + "secret": "77184905738d29784b1f690fa2dec543990c7f95d8ac0d90b725730f58a3034c" + }, + { + "public": "f1986d3ea6978424e9a5ad8d3ed90f4fd5f86af3c4c9dbf10c48757c7dccc745", + "secret": "45511fc17563ea10e6bc206591a09b085dd4def4bda9a4f3466ae192c3d16dbc" + }, + { + "public": "72bfba6808c6c7d1f7f40a461e7d17f2f5f90f5f256d850a8a6ff28a04ec2262", + "secret": "c52b6544ec9dfbf3106b2edc72b87646cc6569b0ce687040bb0a88bd90e12a35" + }, + { + "public": "906f5a2fcac3e5f3580b68bd748e8040a4c0a0c435a0f8857511146a9d03b355", + "secret": "72419c4b8c507a611d934869ceece3216364cb8b86bdb8362bd0e308caf3ab49" + }, + { + "public": "9e91a7317669369c336d47e3870f9995a4a4b6d5492bd231f4b16963d6ea275f", + "secret": "48c7a65d250af497a7413a845faef1ff1561d0156f634975fc32d925aeb9a7d9" + }, + { + "public": "94b965e06279730ef69f6a008af363b504e777c49e0ded42550ba5b1db4dad09", + "secret": "d9a2aa66b37f83345ed1d85eb7688a3cdde84af9ca13d61d89fe9d710d6626ee" + }, + { + "public": "d9adac287709f3b58613007ec828266647e0754ca786b1edd62964cf351e694e", + "secret": "482d57571ebbafcd8419d90a6047e5d2e1db7092d19ee1463ff1490682ccca05" + }, + { + "public": "1029e34ef3e8e922910d12ae0082367d55e7cac4b757fa63255f60a5bd769340", + "secret": "de0581a8322f28fe56b8b1aca7ddd0d56f1206e4e13f7dd74a9481eb212c6b97" + }, + { + "public": "9794fedf8864bd663fde25678be0c7b667b733099f19e10e4239921defd75e18", + "secret": "66a748a24b7912b460d392665cc0774c99f164ee8dd66244be4e5e6450f2070f" + }, + { + "public": "5c6da8a06ce7862738dea924a18c0091519b45a5c24fc55d73a6c8ea02af0059", + "secret": "c3c7d6ea3dbb5edb539336d7f8cfdbf514831571713da64941cb4920a535cc2e" + }, + { + "public": "b8870ab38555f283da833ee9de07632f616aa3b9dab8df6267b67a771540b21a", + "secret": "1e110ee77cb361edd11fd0269a6e27e1929e57d68c902512062e8602ca8bf9d1" + }, + { + "public": "a3c3468537ab8beeb2a136e17a6f809016da0c58533d9826318f56e95003a22d", + "secret": "37daebdd73568562cb81b9f30a53eb6ad1c5d6d8c9b1dcb47ae41ef3e084404b" + }, + { + "public": "d9fcff74f709d373b84f968ad56a8acc030a937f8b115b9afd7b4c163a30e575", + "secret": "84063f7869c726f354104e4dba361bb47ae1284f6118fe2027df396856421dd1" + }, + { + "public": "9cb54d794a31b7e250ed2df50093945f0dce5fb1541ff490938a57e28449750b", + "secret": "8f9a6531ebd8ff457dd64be69c10387ccf7f0b817456d6e15e6a2cf8a38a7914" + }, + { + "public": "7a605856e745fb604b460cdf28e26f0899e37cf63c0a1e961c56e337ef8a504c", + "secret": "d0d9c11152fccec8338809115d30733d6ddde853f558ab9ce50548b741ff7bae" + }, + { + "public": "05bb3de29cd75663ce49d7e84b5cb7e5f6acfe4d20428ff4e9055d7556b9520f", + "secret": "78b9cdbca8e3c9c55f9a17391fdd795dcb28b9b3d7513edc027c9b12c7b63f04" + }, + { + "public": "dac1d230e8acd02f42e20a88434145f767a64298fef8ebc13151b121327af20a", + "secret": "5a3c8242ade73a8f11133c1ff70c69495fb348871ab017eaf7c7d5733c1709e5" + }, + { + "public": "46f640a93512efc604f89d595f7be6c10eaa396ef284daabc898e0301882656b", + "secret": "c49b9931f0d9bbba1251716fd9a20b716bfbe9e1a1ef55a95550d7b530d33438" + }, + { + "public": "df8325ebbfc599fba6a002fc93571d39e30eea179720e1bf5753dd54387f394e", + "secret": "980a2acbf3a9fa462409b7dbfe02eb1683fc0c40d1f3c9f148d30f45880c9024" + }, + { + "public": "b7c65c98c3fe5f5f77e8f0ef21917660540a702438302672582a6e6fe2e1521f", + "secret": "a4f7c717f710a2a02921abb2b08226a2f475d8511a56bcb1be295e85c733f519" + }, + { + "public": "cf12a1381a8a94399feaf3204cdd6e060da2dcc479bd35443c8b57981934423c", + "secret": "58bc0630b357bd415304a49fc4d7d80d911765b773c5e8a5335ef47d71e305cb" + }, + { + "public": "b91d65ee431aa2028ac1b6d24a138049d6c7e9e6f86c3f0363472b50d5220c05", + "secret": "6c5ee9ae9f443968419e3e149f56005f3afe2bba8007ad2c409939693917d430" + }, + { + "public": "f2be5b6c277268c2d1a28a892c30eb1cb243e881c19b85d7d0aeac22df9db747", + "secret": "e30af0127fa8c8742fa40faa9a17bd942f76cecdd892d5199c2f9032c8b10a61" + }, + { + "public": "7d98d6b55413f8fb84bc0b3f97913670c8a8313932570c026d0f1d8d022cb353", + "secret": "b2112852f87cfead905aa6386606f7596e5cb94f606373b9e436602a29fa98f4" + }, + { + "public": "f5bb951b2532646d7a71e01f6a30871fdbd3be0d70c89fbd71242a6758f86617", + "secret": "65454655f6d948611b7da33841943b42c9cd8c5222ad21d8dd9de5ea047f5a8d" + }, + { + "public": "19e83555b4e23b389e1678acb9e659fe0bfb4fdb4a9e9120fd18caa4ea3ffc6b", + "secret": "8c84005843776c116487681768a8a2d3615747a0a044d38fc463dd0815a49418" + }, + { + "public": "3e78938a089f0e58cfd5c40c4bedf9c7500327ac8df4558d56ca6e53bb73cd62", + "secret": "4d868e43c6052f78663834503f32a5047fe9cb73a7f86c67aa8e52025214ec03" + }, + { + "public": "9b7dccbd2f93d2f234bd47d545e29a6d53c07c44eb500151a27ee1dd7b94e163", + "secret": "9f9d1fbaa0ef53d9634611baacc14caac02095b158352dcee6f0e7bd2831ec85" + }, + { + "public": "fc95209f25f84abc4bb2fd389e2e007f3e9e6bae8092c203912f132937a5e76f", + "secret": "d2a2ef641ef26d612eb389a3347b2d5320e9f95a18737bed9932de73f41ee28e" + }, + { + "public": "d7e699c2b49500e9ee1bbe11395ef5d6c9c929ee0b113f7c3e56f8879226c56e", + "secret": "c1fecbf17305da7977d401d8d80ce5dac896b262ac406fe88b578fb05437b72c" + }, + { + "public": "e3a91c1d3012192ae10c141af64021444b1006c6b7fce45d55fcc815e8e6c210", + "secret": "b42abe02a85fe7912c0cf0f28ff04a3e32c17df39cebaa76d3b3b81446fd27a7" + }, + { + "public": "380017297e099f40856bda6d44af723ecc67f9c691957913d7b71cfc1de50153", + "secret": "6d2f182df7c89e49f51f6a7363f1a95d4b3f582c91229842636e7e5ca242fd1a" + }, + { + "public": "2ed1fa1246c0d6c19392d2cbd53b381718229732a08b73491df8f518981c3875", + "secret": "36ca45fdd5bfe34d4e72f525da9d11cee58ff1f3bb6f4aba6a32c88bd2091f02" + }, + { + "public": "6e7e17e3bb4c9c50e9cc30818f880f0afb6ea4ed70b16ed39203084cccfed250", + "secret": "e68e37ecff39d2fa685572142fd465c394836d2f9b6a9f935653df5aa7553f5d" + }, + { + "public": "15b82f4a5b4678e6bfc2ceefd663d5a1303425ffdacd3189b153801a75e5ff52", + "secret": "e876a8c0ccae06b31213c6c6468e21df58e307e73f3be004ca60d60894076f76" + }, + { + "public": "9fbb1be82725e25b2ef5adcc85e1012e67866fe11523110cf269e1227c978378", + "secret": "4752c932669a323b0e920357c7aa9e4e41e753a944938e484885ba3a39ec4ed2" + }, + { + "public": "33f0d32144def3c968930293c47b0cfb89a9a0e5342c1917c0e8471cdfbe323c", + "secret": "dbcc2c6b9872d42b25a47e4e80c850f14c0833df227dd1e98c02779bc1014804" + }, + { + "public": "9339a978e8d1493d4bebcc4fe9fdc2789e91656e7d4e1c4142554da3e262d35a", + "secret": "b9245ff83f78c0d826c79517870f7f4f3211fe681bc9fa0669ccee9e663a7665" + }, + { + "public": "c0c3bf1337fefdf7b379cfd79ad81b9f0adbe2106d3bbfe210e8cb27e98ada40", + "secret": "e6fe410ffcec3eb487513823e1d8165f11e4e50cb1a2b82ea78dd2d4352ba054" + }, + { + "public": "ea8fa6cf1a5b6960d46ab00c63a3ae868f960640457e06dd38217da66d69b56c", + "secret": "91bd5135582279a4fa5e841db3a301589e4c090f1eefcd893d7267748e6c84ea" + }, + { + "public": "cc1598d231b4495b770c3e69b009a935ab0df75a37f16dba65f96ffc69100032", + "secret": "f0b0b4d68360b86f18bacfed1ab41177ecef22e9a8b5a426d23ccdd653dc2b38" + }, + { + "public": "127aa7ea814aef98c7a43c974e5b835b65ce806d7b83761356632c7e08c7ed2d", + "secret": "a4eae4b8b1e06ffe9d5d46c8faae741154b16f56ba843e357f18c5c145d145ce" + }, + { + "public": "2dbc99e9d736a866f8b00a08264546d0a06810d2c338c98d7e32f4ca5611396f", + "secret": "83bfd4e9f89f624b8694a30c69cd52eabac3521b0e87375f744efd579ef0976b" + }, + { + "public": "6cb16a4828736b751c4662ac02acf7e66a05145f4ceb93c2be597d87faf7db01", + "secret": "acdd322e2eeebe0670253d75c8d6afcb92d3035d5ee76d5400341e6e5bd3b1de" + }, + { + "public": "43e1d1ef680f27ea9ab62b7581e7c302610a19578c63e4572b0625014aa6a56f", + "secret": "2d0a455bebe733e35f25a801892831862a67a036cd24d0eb825ee18d62723f26" + }, + { + "public": "19fec283fdc037e1ea3fe3f0f18c82f4e7dff3afbad282e780604f6a0b3e022e", + "secret": "5ba08bfa044b6dfdb82955e01dcba5f87c33882364956f4c8cc62f05848c1b65" + }, + { + "public": "8dede7dc25a230f57ba104d2c00a1ad792492b1762a968b3c5b17ebae8eb6a5b", + "secret": "25ca3c37e6e054b3735593d157fa050ae9f83e53fccd6aa4d0bce6fff604b105" + }, + { + "public": "aded83353b2a45251ce75c38c8e5e0c711c0ddab6e705b1875e3d01b2ce6d750", + "secret": "4a4dd68dc6eabef00645825bb65211c2f77f8271beabf0535aa7a054b24d7bed" + }, + { + "public": "a7b49cf00150f7f1b94aa54f4687e92ae0b4c1f5a0ef699c1a0733cbc87e013e", + "secret": "5269173273c866d1cb1f76bcb92c0d192c86a555da0a7f12dbb3fffe2a2b2922" + }, + { + "public": "6f8d4f7f0abc9e6f6294886961a1f142fec8b6bff826bbe036331ae125c94a17", + "secret": "6f059622ecc8319a6f2b3bf507b45677baa321a9da92041f2adbd5e3e6e0e1dd" + }, + { + "public": "2916c291c0ed597a7dd1e125fdaa06f5785b07a18b5a17af77668829f158657d", + "secret": "84a43d4c7d413a5ad762b130308b72ebeecaa72dbd90a02c1c04bc426a4ea469" + }, + { + "public": "b07879b955cb2f948eb5af823a59c80ca36900d70f6b902781453bb878451341", + "secret": "7321df37797a88a6d9c724420d4befe80883b0225da9a42a03d260aa3eb75d81" + }, + { + "public": "972a552fe6c313360b2f4b0990b9e3f499806dec7ba4a27a1d05b898c989bc1a", + "secret": "e16bd75ce73db2157c3063df0d3c624f09dc5dc07d07dc667a984526fed344f8" + }, + { + "public": "bfc61d1978306a82bd6fb50e186ebf49e1dd61d96d9cf996d8fa288d2514ee02", + "secret": "cd02c7bf721f411c8668958e275f335a2b322f851cca93bd0c3e93456dd89234" + }, + { + "public": "1dcb2f10db9495a42df898631eaf1f902b1659fc9b9ca6520ba46427c2ff6673", + "secret": "926d67b8b071168a115680887f9bc54fff8ae81c19c7b2f39714196aa5241dfe" + }, + { + "public": "2e4ca61a73c5f7aa3f892470104a177701e98b4d926a0dc74eb47d923789a766", + "secret": "553016d7b1f3be8fa75053b7699d8fd6c96ad5679075b200b6bbacce0a5ea885" + }, + { + "public": "34a41497a31e3156da3fd22149125f5c0c7e38d517504ccca0a77ff1a23f3a0a", + "secret": "e7ca8c2760ee6adc5964069471972304157ab9208ad72119634989832b6516a9" + }, + { + "public": "977c274ddc7f3c87b18320e420e732f294430c8ed72b48ccff9cf5a8aa55e50c", + "secret": "5a338cbfce50b825600fdd47ea82414be2ccb01530379d416c43b869bcd3ca17" + }, + { + "public": "816477abad3b19c4e47a775224eac2a9f96c8a4854fac6afa269b5ae4704ae25", + "secret": "a3d7b710396b26028c10c9a57aa4984e05bf722365a34dc54fe0f699d7c99492" + }, + { + "public": "15d3d7f96c00f6778276b78a94b9526fd21d83c946ad97b0435ac2641c459867", + "secret": "27a8ffc3acf57b6dc491fada4e5099b3e49ef5614d9ca620435fd12b83e8a2ec" + }, + { + "public": "077496fc1c9e2f0e1141bcf04784dae401bbad46b6257ffdb619ea373070427c", + "secret": "40e03ba9a01516ab728fd9491f4fb9743ff7ab3b5800c0d8138e913ee8dc63b4" + }, + { + "public": "44212559b1051be914f8b7defe4032197d4c761db7decf0cee7c1730dd07fd6a", + "secret": "bb9fb5c30ca9a075fa3107a0fdb78f51ac975ef07c3b0dd7151c9256887b153d" + }, + { + "public": "7fc995ff6bf288c68f1cc01153d4851520ab388ce23df5036deb8736f0584425", + "secret": "ee98f636de12ea9d6c93716225df3e1c1e0bdfbbca78d43668bb05f1cd766c11" + }, + { + "public": "27ce8debe8fc65fc7103b4f833eecdc4238bddf75c364d046f03fa38410e003a", + "secret": "d7438279e505d88e631cbf19eecc51180e9509e34ad951e2cc77476bc124f6f4" + }, + { + "public": "608c4146a9b5fc79904b11a8a9414582eb03d89a801983d387943e88ef3f0d0c", + "secret": "f79730968d0173d6c73c59915aef84212e3c5ca8558d17039eef9ac49ddc33d5" + }, + { + "public": "03600982fae408dfa890afaf3de93947bf418e565a1308250d9ba86fc21e385c", + "secret": "737bbc8f730406aef5e383f75214a0ce2e9a3af6256432e1973837c96097e4eb" + }, + { + "public": "7ba30408cae8222741f9d185dd4ef0d4d4e4bd5bbf10821611af5938aec68d0a", + "secret": "a23e479551895c4afe083f3989ec80cd52101e4e9fdbca856a0da8cb905cfab7" + }, + { + "public": "643ad97b453e99b31e9175a612b78a783b0bcb3f871abb8acc22a892c7d16622", + "secret": "195a8d5f13e30541153626dde98e6f964cb0289c4dc669c0bdc84dca294d3546" + }, + { + "public": "f43626eefc8bd1b8bd20bdc78bc5cd4df636a3bbe5650c9047061591f540280f", + "secret": "9f3c6b71b062bcaa3804c19b815d499d6947ea1d8787f99e8b7cfb92e0e198be" + }, + { + "public": "254c6f56653a00437d9d83b795c7f44a997c94b36b9e460e08a5b71cdf9cbf74", + "secret": "9fa49810b1ac5bc521b2c022be2bc6de5c4e2830cb9469c6a553082f398d76c0" + }, + { + "public": "4dcee0f9ce63856c44aecd7bad52d2e47fdfa2c7a89239929ed32c3b1ba70930", + "secret": "60ca466b4c3cea9d7691edbd4637fba4180f90d3067c74c6f08b0181369ce14a" + }, + { + "public": "5da3ea208a65ec8c48ae8b737b86c5d0b4e2076b57f2aa3a6a2b4c63f913fe03", + "secret": "be313a7e8f825e4fdbd88509748c97236a512401c0101c0e1fa519df19f1c5cf" + }, + { + "public": "c527f4807345363a4e992925b9f0714029a709e1d905389ac1d872995155b233", + "secret": "ffacf512e8ef8b85ceee04ff5d4320820276aa0de089cd5115242d56a8b77891" + }, + { + "public": "40b3851710d0747e9c47ae5180fe55e5da0ae8c577b789201804846685c8680b", + "secret": "7239da93bafa6e565c9a288a9468793595befa703a735c23c08248301dd14186" + }, + { + "public": "6dfa78e6b7de144e8ce48532b3d6867c6a66cb4bdb9fd72059808f17be3d6b0b", + "secret": "e03428f92ab389afa1faafd3187268d90cd9d59fbcabb34a5bb7c215ec3738ff" + }, + { + "public": "79b8ab3e75d7b4c2d3bbbd7b11ad3775e160968a90263d2af37df542a39c6a5c", + "secret": "6054ddfd27163f621a07b727545e7bc79e9c10ea41fbcedb308883dfbcdfc9a9" + }, + { + "public": "ab454823285ee2a51ce80bd8e487236e0effe950339f6531377e0d4733f3924c", + "secret": "70eb09cfc1275fdcfc603e115c5ce7e01037944f1767c0b19a2b97480cf04887" + }, + { + "public": "5f4a41eab1b11cda023e4ba701c2804defe8ab20007c02f68b2e0f9135d88169", + "secret": "d598bcbc4660548870a0aecbf5db9f15ff40bd6d85c9256e6c883870ae583278" + }, + { + "public": "8bd0e5f32de301933de6b2f2382fabdbef2251133f4f69e6af6c1b9f89749525", + "secret": "4c62778abdc39c18f872fe475d663a131aecd79dfe75eb069750eaebbdd3feab" + }, + { + "public": "4779f06e7ffa740370f2214b091c7c9750b1167c6f418763155a04ec9db34664", + "secret": "cd432fb9445bd8eb844ae76b577d9a67f07f8275bf7c756bae905a5be84cc558" + }, + { + "public": "54c4629d5fee8cc0ade21239184ee07b053d0711139a7ec820232baf0cf3ee3a", + "secret": "490fe7a8568ec6b8703f97c0a7596e5abede057844e1628a28407f96e34acea1" + }, + { + "public": "bae4914c5d0b64069883015429e136f8881501dba8b1420a0c83678d7afda525", + "secret": "5bc78476150e4926be88b87825b83d1791351bf347f60cdf48bdad53bc540f14" + }, + { + "public": "62b4977d6533be9cfa8e68c584e704aae8abeda1ae19c5c7642e7f7395b99e6a", + "secret": "30873e27fc3d2b4d1dc8c0cc95425fe9cf11f9582a3b2721e4766b910ac49526" + }, + { + "public": "012134650df003167b7fee8df53bb8e706abbdbd7b7182825288975931f33208", + "secret": "06c6dcc0b74a548a536f7786a5f86f6cbaabd44aae71342bc760499dbec326b0" + }, + { + "public": "707bc6108075be77306d693808eb1458f7f5c4a6601fadbd159a1a6da8aa9e46", + "secret": "cf083fa5da24fbebbb83d78d0a64624d65c4eb5e387b7de1c83d658fa470bae5" + }, + { + "public": "a5925c286ac7f427327f4f21d16b5f8be08b7d49a24109bf7fb1b81a39951602", + "secret": "75fd69b011b1bc4442ff5b2bd041b91b3b9d223fffd77515110df2d1093180fa" + }, + { + "public": "c0a3b673acac44661e18df2830c0f21209b013c7292d26a28343f02955371c68", + "secret": "6400f69185f075b480d59db51dba57c577fa150becbfd0d12266b3ce767e4bc5" + }, + { + "public": "b5faa6e9e191e183b9e02522784726a60dc6dd58801b607ee80550dd5d7c9065", + "secret": "3185cfe81297d3723d6a6df4da388bc3dd38615a27e096688541a40aa353348c" + }, + { + "public": "860df4af1d395dcad8e5a82035c34f20e259afcc7299c65924b250d00a4b9925", + "secret": "eb32f30301ba1480eb802604f10cdda5f44252b9fcf489526291ad99bc46779a" + }, + { + "public": "e64f4bb5bd753902442c97d605abf678c3f4c008c837ce00ee2ddc574a0e8e6f", + "secret": "7e149e3fccac7f0ba8088e6ab36104379df0fc383d3a0341b224c2f73adc3d7f" + }, + { + "public": "c1b75f0eed14f2dd6e293c55b4042c5d19ef7395af2674749f250cd79ec50531", + "secret": "63f598bf196d25beeb1b9f2bcdef375de126138d797876fcfe934354e43ebd26" + }, + { + "public": "ae2487dc8c1add14fd3d231fb00ae77f8599a0fd611137434dbdde0f9d8b2c67", + "secret": "bc44104c0e70687d191dcd056578a5e7aec371cae50c0dd4c56ecdd2168f6c22" + }, + { + "public": "1e16b7ddcd4159208ebfc7cb19db547065d911dbca06e1daeedbb715f5876221", + "secret": "0b128ba56abccc42ffbc11e0e2d84f8b7b624abf5783690bc4e6a876cb4519ce" + }, + { + "public": "11f0ccaa27c32643aaf723b07ce167f5bc28f4f5e1a5864a7bc3738bfaea441c", + "secret": "0e8572277d6ce569b96390ae0793ac41df39f2265fe5b9eb5ec074a0062ee548" + }, + { + "public": "7c8259bf772212f8084af8f4e728f83d3464b2bcc8a02c61ce3064d834b63230", + "secret": "6a3df55f5db4f76b5f7fd433400c4f9b181bc612cf00d335c063bba6803789e0" + }, + { + "public": "9673e6d28e57053de66ad70f8585505b46850903501f1ae341e03928e1fdd935", + "secret": "e70a9952fad27963c89e3c7c09adc5d45730f9245a4b2bd60df9d321062a2be8" + }, + { + "public": "e3d35bef7b962add6f34c76a36d90e2bc59477ae5afb62b8b16ce104f6cdc74a", + "secret": "48cdb84def8b157e74387755b901e7b11d0592bd8826a107b4e5226a3b699c5e" + }, + { + "public": "058f1f58166cc3e5453bd30c8e6a169db262b3f389acade9ef9fd7e1b0f30d02", + "secret": "85cc52fba84054ee997394282624ba27d346b7e7ee9dc04ddeee32373144fe16" + }, + { + "public": "342bfb842c2b0cfc58fa46f75c23fc0e16a4f2ee51e17da1ae44995ba481b34c", + "secret": "02862bf4116b187edcb77c9d167bc0ebb262a8c7266cf45858409b64671c2b93" + }, + { + "public": "176fb80b6b8592e785c14b8427daf1c6f46f82904bf316e756ed44d468e97c01", + "secret": "c554fa7258e6d0a01cac988f8fbf575e31aaae236adb79c9a6fd2521ea26233a" + }, + { + "public": "6418b8c20fb70e9c87a55860759847cd39d9881b0afd021d42d8d8d9b3b11409", + "secret": "47d57f9173fbe16717ebc301abb59826f2264b406c4620de5e57a99b2823e259" + }, + { + "public": "c7e9a984635e22aefe918f9f00919db404f84934ec10a4ab0be2af56f532b920", + "secret": "dd910ffefcbf40c158b38737ac54f328707029c62a3d025a9978389da0e7cea0" + }, + { + "public": "78524e2394c057ab62192d3ab62a55d40833448698ede9b90af6bfb546fdea3a", + "secret": "cbc4eae0c34c2a196e0bcef5fdfd7d86c0784f210cc0423a4d7169dca07e830f" + }, + { + "public": "e6ee7da63fac3dbc9db63a48e5fbf9896e384609e46edbdd48d85da8120ade1c", + "secret": "c83254df25dfafc13f5d05e1af16b04a89fe8a7629f181ec563b9b6daabe6144" + }, + { + "public": "1337ae0ec7c42dfe27331ad05630899976485d4665f7e0bcbaee263653896e7b", + "secret": "7e246512a5ccc6b975e2b1f4b5d1ab559fe10e881692aeab7efa0422895c93c5" + }, + { + "public": "1aec21b387fc596d14957a6045fec2cc288e4a9b4723ef800c62a899f2976040", + "secret": "129d990952be3a18c8b9a3bda06d0d7e27ce30ba161ec8cc3c2ed4748b383c63" + }, + { + "public": "9ddc810f7bfb741c7b3339b4f1445eadface2cf99abb4e687da10511c4fcab01", + "secret": "c01605ec43306219bca8ad845360e7bd18b3bbf1ef8ac6444121e0e3e110904e" + }, + { + "public": "9e199620ae24fca08b1a0f2d003cae1751b21e60f028615cac2c0a59a3fe9c6b", + "secret": "eeb69a1966cf44260816b05d2ae2af00aa6f3276dd15aaedbd6bca2ba091473a" + }, + { + "public": "504f2d3278600bbe525bc3a218c0c5e59764b2d931e6005f6f19d0bb6880b572", + "secret": "049c99d5550ea8e519c1109876e2902a128500eee8dd560ecface44fa4f61857" + }, + { + "public": "396ff515283373dec40da680cd740cf94f7f112ba9a49ba2add93ee19e468e32", + "secret": "70d0a04961fa3b0a1beed3fe68634c28655c8dce7dc53acc96b09a8d730b6a82" + }, + { + "public": "279e89806f469d79688b609050c4d73ba22f680d553c409d9db41fff5a9abc48", + "secret": "bacaf51740344af73b72858daa905714775dca302ca774557d25ef26b06aa624" + }, + { + "public": "4dfa2d81bd123dda962b7c9b22c45d6d7c16e64d8410596d09674b3fae7f1e18", + "secret": "391a74bcb3c6dacc196bc149e8bba186eeb6f0b11bea79d335b1d1e95c1a1bb2" + }, + { + "public": "87f0be75f3e6bb74bbda8657a0d6b52f25c772156e9d7ed190f3f638fa95a53c", + "secret": "535d269283e74c83551dd5cb51ac164bb03028650b103037032ab72f9502720d" + }, + { + "public": "c72f543e512a7d8e21f520736e27bc4e5b2c34ff5c194de6d51d4c9b37897f6b", + "secret": "8db9111c44f91d501983cbf15534530b86b2f49463e67fcb888d3aeec47acfe6" + }, + { + "public": "44c4f3465439b4dabf6dcc37dfc1febdaeef184703ce7b98b6432ae93dddd766", + "secret": "5d9fdfccdb29ed7c74ba112575630d6c3eee08d059fc881df36c43984aafc782" + }, + { + "public": "8ed02b823d847bba459c6ef656d6e16a34ab8469997e8ed9569167a25a790567", + "secret": "ef682320078125cc6b56351fe25f3db45365322c6d6d69b39c90f5e52c539e24" + }, + { + "public": "5065cc57755a699b05738a75ae29d16f67a80d786f3c7ab3dab06612ff331d30", + "secret": "51bec180043fe37985d3d1834296208b9e61b25a3e489555c285390023767714" + }, + { + "public": "3c57418525d3a80da3e21a3696c6fe3c80b947d5e7845b53d36e183aae68bb36", + "secret": "d0a70772225e7e1e18d38761f5984be155a88294a5c8b7e826a8545f4f1e79e6" + }, + { + "public": "f41cff8ea29ff89457756c6d6309c4412bee6941d578d89573b2d2917f7b1b5a", + "secret": "8428cc8f885fed2da8380885b7133e3eb95e8cbe028659b398a7a30d14b3cc81" + }, + { + "public": "9fa244ce0d0569ff2771d7f95ef41ae7fb9f4a63652b8d453420f155fe571317", + "secret": "7f31dec347e847220170e3165a218d1b036806023b193f501d7a3d691829c2b2" + }, + { + "public": "46c96b3960a11d3350158b0ea8ac7674dad503cb3e6372ae2d839f16e6c10e5e", + "secret": "062e876469d049b6d98c6160db989f90d4f08ebc267445036c5795289fe70676" + }, + { + "public": "7abddca6ba53311a83d9211be1a0d4fab139344b7215d4b2eabfcc93829b2931", + "secret": "8e90259c907f2eac854075ecd2584caaa0a655be120e7f4028c17f3923cf37bf" + }, + { + "public": "b0b9155f43cd802a0ff2a9106a06ff5cdc71f5479eee1490cb994f671400fc38", + "secret": "93cc064d51207e5dba18a93e8715e35d259006b49ce51446d4b6ed5c82b0b519" + }, + { + "public": "b0d839cbff18a3e33f55c306b6ebc2b4e012de1b7507c67cb90133a11f2bcb53", + "secret": "5f18a84635a2b8d769ecf539e0ff439861f91604e2669e550732af546619017a" + }, + { + "public": "c645784fbd63329594ab419f49e0ba5471cae3fdcaf4c6215b8232cae2764673", + "secret": "dba27fb75402f155485a3f2f665b29aff409671792668a2961f3ef753b6617da" + }, + { + "public": "b0aedef77b0e090ab806c548564ba4e5660644d841c6fce4baa31f7a1df7b249", + "secret": "c99ae5c3f239cb7cbdac39e28a562e81bd89954f927cc1bb7cd5dc8e96f37a82" + }, + { + "public": "ad3960c5d3d3ce598c0cfd20c6a43149edb55672b83be197f1f241de1102f07a", + "secret": "6e7801804001329e2893329c35b447debbb38819401fa1d66c87422b200117d7" + }, + { + "public": "29546d58fc20e75463ce01a637de688083c6d40179beddbf1f5964621b4c5e23", + "secret": "f83dd4e54d2ee25a03ac86b5cabc625a4a409dea9a11dc0d3d8cbcf9746a3a42" + }, + { + "public": "97842d246628673dd8584bf2ad998b352f517cb14d6d8063384bbdcc4e78c653", + "secret": "797ea9854898609eaeb32d523aa3996b99dc8fb65cbf54137bae7efe84d1cb3e" + }, + { + "public": "4c2d282b50c1ef9e548e53e9fb1528e15757cf2dab1bc80a84aeea0547fce858", + "secret": "aaea5a10671ffb0fcb7f62d3b405fb6f9e0a557aaaf967ce073d4ffe4f1ad4e9" + }, + { + "public": "4192fc263434cf7a5a5904d094977c2e9b0e16651ca3af7da4758bce0215cb06", + "secret": "5e669a2f59c2df795a9ead9fce0d7a27586ab8a0a55211ae2dfbdfce47dfba06" + }, + { + "public": "7cdf481fe82029be654097a5e4518354a18d13736474324e2c927f5638569d6a", + "secret": "a21d44f9dd7f8ab2b5837a1f1eb102015d99f3a340032821a311ee782fd3920b" + }, + { + "public": "4555f0ea6fdb9a743fa003b8c9b1d354fb369b53bf383002b6210db0de5a1a2d", + "secret": "b8a7c31984138bb9bc5b733f2b4c5d3e48d99e8dece2d19dadd3d1a0ce130dec" + }, + { + "public": "9fc4119264e5a024d32d95e199cdc9178b4181ba2666711cd9e30644efc4a244", + "secret": "74c3378e38b5fe64386e8152001ac04be768ec9813cd9568b7b3c939cf970a6b" + }, + { + "public": "28dd5103956b2d2381d384f977e1dd943d3a3c44306c7a569dfba744c0787e0b", + "secret": "285931d0e5acf76f9fa22972c002a2aa259c9b2ba2c3ee2b0ff2dc700f701a52" + }, + { + "public": "09be14895739d859f0012a3bc927859172b83ad1e8277733aaaf1c96a6ffca27", + "secret": "7c81297ee997ca94df1d9d6a4715a46bb26177267c06b884bae510291677773c" + }, + { + "public": "5619002b67246207c45706e0988c0fe4a2a0656fcdd1940c6904cc31f43f632c", + "secret": "b55b6d43bad1e204726dd272c82e54eb3cb84d679b7fd18a825c9f736c5379cc" + }, + { + "public": "f8c07a47fcf089ef1b0ff7826b475628223dd1f6cfba99faa659528f1dfcb50f", + "secret": "c6a718225fa7eb1db796896922dede2257e7b89174b9e382b53d177880144207" + }, + { + "public": "6ba6eb48233138bbe0ba3c4f805d3e8541ee60386e31285de5db43087288c51f", + "secret": "458c1248840e1c2615c734ba927e1a51c5d79a29d38b1101724b574c3492abbc" + }, + { + "public": "fd05f84f98f40a6ed713213ad46abe0df251777c1c2982469c6d569db339fa13", + "secret": "47bffc1f4a57105971652d2b593c26e8171af4b6c8c67876155efc8a73209a5b" + }, + { + "public": "8a3fe3e6f6cbca2c6434943a4a0be77534b5bc17a1700b4ae9baba52ee02452a", + "secret": "d484891ab5ef102b8dfb8866746ff39c9e1e295eaf2f7fe792c09c73f914711f" + }, + { + "public": "a449a9e34aa7c21dc0911e80876a74e71357c72dd53c94453b6c1ed422acd56e", + "secret": "6b179bbd46696c2524c95586a273b797ed7aae84168de3d8e4157e9e82972073" + }, + { + "public": "053a845e71df2ae3b709f57f7e4d5cf17e37268f92826a39d72a708090472974", + "secret": "af33513baf0664a7dce05bcfc55c156b70d976e135f94018ed837823e7152e97" + }, + { + "public": "afecf8fb2283fc515e6505c3df847a7ac510fa005e24295db50c9bf3789a5b17", + "secret": "2ff1610a7e00f130eb2ae27dcbc6dfefa7f10614d4708a747a42803e06cb3358" + }, + { + "public": "1e33a54984238b17d8b3f100d02284e2c1906c5f4dfb976e786aca4411530c52", + "secret": "d681aa827f10410d39fd3ca9f67e5ed7a20313f3a9a13b08419e93dbd6a3d644" + }, + { + "public": "046451f6a092d7a6c51e017ffb4f139c4545ab6b33075a5833f11b982273525c", + "secret": "304cc5cbe39b90d4949d2d288e8ff9e578304ae46bb9f382503fe2e9f05bce0a" + }, + { + "public": "64ab0aac56e192bb278634f9641df204bb6abfb5280b7aad66ef3a97628e4d1a", + "secret": "9814e635824ccae06de42264ee96cb5ef6a6a11bfc9c13647ff17685c3b5d207" + }, + { + "public": "ba902fb04abb8e4f919dc696abbbc3e957521e05bd2f1e19a83bcbdf4d5ed75e", + "secret": "e9c6fc66f3c1da6018fd560d830ee95a8af88131c7db57d08e8141598cefbb4a" + }, + { + "public": "c19b2cd9d15dd52d43d21968be7d8d6b6f3fcd8b0e40f656e4e823f126ae9759", + "secret": "2e5992082bd7aecbf34d9d7dce2c63a7c9725351e518277c39687174d1a1a518" + }, + { + "public": "55a0ec68968fe771bebc634fc79b3fd3193e275bd2b3cde64529a46c864a1a4a", + "secret": "18cbfea946a844440e5a206955f251a13a00b073384803c7a9bcf98427f5d52c" + }, + { + "public": "43f211b1880edcd9a76e60f67198ea38adf7127797d44e8466c01d4299bd865c", + "secret": "5729b0a143553034317e85e23ceef5a97be749621bc9d9d160bf9fd8fa5b87f0" + }, + { + "public": "f8cf39fe5015d256f1692110584c82f449191e9273fa28f26d1c8bd1241c2407", + "secret": "80cfe70467f7995743e32421c93d5969ec317d4ae157ed2e3e735339bbbe141a" + }, + { + "public": "b459d9e26a00314cd306bf70c3dd09fde56a75e99cd9d935d9fc25585c9c7027", + "secret": "79db1a2819d05a8f2e46284229c5365e070708f7a1073740f1f9325fba14d48c" + }, + { + "public": "75b7ab71c120bdcdb2fd3243949204aaab7a4ae579e22c543be12822fea6d470", + "secret": "0dcaf98d5592da3392ec0259e6b7ecb07914ec359b107b8b2f9585d76a2577a1" + }, + { + "public": "a03561c73194e1bbccdf29d0cbb4fefb40b0af1c536b3a46aed7443d5cdc9849", + "secret": "75b041ec699ca8aa2ffe963f2ef490f5f219dc0176ca9acee6390263563e4fb7" + }, + { + "public": "0aea4dae7a7045f26ea7ec2dc9970fbc3f8a0df8bf7be67dd8ffa8e95195527c", + "secret": "fe9d576d8125d7b23efceee7d506b10b1665342b2160cf878fb99c8bb19dcaf9" + }, + { + "public": "599c9c032d1e412bb4113d2ca06249bf256172d470cd0dfb80e09427fca9f256", + "secret": "33601324e9d0e3a2c8777a26d65e00500ba5895c5ca03aa3b0983b7ab2cd6e79" + }, + { + "public": "36c278af300f2010a61d255dc7da0e3784f7879e2e3270fa020c83107882eb56", + "secret": "7bdbf01112a9742ea6d2f3bf7e9d311fde0200b1361a18ca06779b634e7be9c5" + }, + { + "public": "8ed5d4c9a6f8ea362fe9e53471d599bfad18948e22804ecee7a06fca5c9b6b1e", + "secret": "3ff532fddcc015e5e9ba659159ee5bdc6564c2c4205800bb3877812da50cf3e3" + }, + { + "public": "9985fc97f38ab764496e7f5803add0bcbb21dee53ba2de854e2cc28252554c58", + "secret": "8a3e801842210c96fe3f3b3e41be6867e91c810ecfe523190493ed52b7697713" + }, + { + "public": "a85bed7485f23a330638cb56fff6a29b397dcc1879f4d39605394b5f10ad7566", + "secret": "cf86927d106a1fb2351250b9f60347daf60d5e3000d23daa615b79aeea3869f3" + }, + { + "public": "fe1100a320ecb7ae707cd98212ee65f11e79775696f0ee043b6a371d7420e960", + "secret": "f4d61a86913d9afa3e699ded37ca51252cc46ebccbe449abd8367898be28f58c" + }, + { + "public": "3b0a86e212ad877767d8b95424d06c9b7365438e6785e2bc6f54d92039eade10", + "secret": "01009cf1d93a970dca2034f839fe3868e3c2e6a157f55734c7b2ba68a76e6a21" + }, + { + "public": "38b0ef4ffb7d2f66f41b83410df46b36de515537aa23bfd3ff86fabea43a9e02", + "secret": "b02935049a8250e80d190ec19810b6d03bd18713604ce8576500fb2b5933d79a" + }, + { + "public": "bb056df37f48b26dcbd3ba804471635c82ed28acf9300a6f69f61c130347070a", + "secret": "b0153f90fdac2915923addb54592539863dbf96698eb56c95880f26cfa4122d5" + }, + { + "public": "d778e9887d9cfdb4b331175904ac2b4ae5fec562f852d317514e76b13f6a1731", + "secret": "cf23efe160804c1dc1c24200d7ef88b682b5a7ba8ef43f40de43f36cd8ab35c4" + }, + { + "public": "3567471b762737d860b07ee703ce4fd5ac6d95f60b66d15f1725a0e3ad212b3e", + "secret": "9688589b820b3e1d25c00a7fb2589d5b10428b982ed29b2085afdf41e158beea" + }, + { + "public": "e5a98ac5799aa7f72b4be4d00a7cefef3325f6a919a874981eabd951dce92770", + "secret": "fb2aa3144f3aaf90f15118d3aadbac340fdb6f12dc82149b3249d58ed39f62c0" + }, + { + "public": "6b11067cd69386c19c5b9f81346decb3f6913912b68a2b1f86e3576b110c211b", + "secret": "4b04c5e9f30ed5e1b3966d0cb8e40d3373cd74c4d1efa22f95701399c1c0501e" + }, + { + "public": "bb38399d3c48397affa05527a5a481186377f43b5682248bf4d32134b70b247a", + "secret": "8cea48f704a5e33b093f46e9bccfb36f6f7dd44e922da4e0229aad1e7d1c72a1" + }, + { + "public": "3dd6f381118ac5bf5f73098fdd31e77b2f7b7871e9d1ea08ac74b37fc4797175", + "secret": "dd223c41cd08dc7ed3761988f03bdb67a62b132907f12825369a61e7207ebc90" + }, + { + "public": "d0d625bc67db6223843fb0b2e87d0409935f644eba159c024dab9325b0537f44", + "secret": "c8bac55d865d835913bd31008f788a766f54081df68314db32dd27322e5cf2f2" + }, + { + "public": "4c5eba3f44b6a4aaf81b29f580dace3202708675745aaad3b45b6d0f3ab3d443", + "secret": "42bdd80006aaa7eca9c2ad89add60f104d52f2c0863daa316e9f744fbe81fcdc" + }, + { + "public": "2062223114c38af310bf4dd9105cf4cf5cb1a34552af04d9107efd704c2cc37c", + "secret": "2fb90f2b26b0ed3c0bf4ba6f7aaf94298e82a07722f702d241a61102c2ab32cb" + }, + { + "public": "74bbc65de7f2be199a6da9e1fac3314873205755a5b810d2e2fe10a29835d469", + "secret": "4b0314b5ef22858627c086e8cf0ec0c0b77744e33da3e77e76e86ff4324a5973" + } +] diff --git a/rust/tw_keypair/tests/ed25519_waves_sign.json b/rust/tw_keypair/tests/ed25519_waves_sign.json new file mode 100644 index 00000000000..159ff07db78 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_waves_sign.json @@ -0,0 +1,1002 @@ +[ + { + "msg": "", + "secret": "29232a6d56e00bd21adc7ee8a12af33eea1da85e40510097cd9bdc6d0cd78dbb", + "signature": "14e0aa80a8b29ab3410ae8076a74fc55c355855bae3ebf1ba8a29589d412c59d5a5678ca6d194b0d7882affce54d8dfac7ae7347d19a67aa225bd6c6bbad7f07" + }, + { + "msg": "fa", + "secret": "9f30057164620aca54ad586d63555861de9d7bdd1b694bb67de6f60c3aa4de29", + "signature": "099f28b68cc7b7605b4f98f63bb5aedc9127b760a3350d724c12148a4d74b786d6cb94d74f61403d0b2d09ec9c8ace493d7975da79e7fe41e11d1b5e36373189" + }, + { + "msg": "9f9f", + "secret": "49f4a75001195504c907a35b9ab64d6920c1170e59117d7a6bf9364230d1e0aa", + "signature": "a98b0b5330c07f188f4683da961c345b4009aa0373a1dd9a5e205bf6a17645108f1928b3b31c6c1fc7477b5facb25d7a93907a5ee06b8ef7d650e2ce41011489" + }, + { + "msg": "e6167d", + "secret": "c45d1ba60a5d929d228d1b69a8f91bd256262498e81d32b6411d6dac9a60ed3b", + "signature": "05db2483b8107187448e7f3d5581e48380a83338d53fe70c59e88b281b995216085518e5d331a2b698f2d0d387529dd94437157df88cb14be8d1925afbb6e80d" + }, + { + "msg": "650b72d9", + "secret": "ec48169ce269fc98df8b721803f6498cfc813ee2d073bee23881295839443ac9", + "signature": "dcb846e1b571dd5ac6563a53320254efaa9b071e58391cb6b9ecaec50fb63896da4949cc1a3573576f7455ca5c6f27a8e46c8e8e2b7a455067883aa800eb4383" + }, + { + "msg": "d60c3bc46a", + "secret": "1638f2146bfd99c8e0e67d21c7d3e411202e90377380fd85b846dfc3dd21aa36", + "signature": "7d3ca61e03bd298f2903efc365ce51e7809c43d7d1e8e0a135aa620d970a8ef0ed74223303b0282cd428a8e825db4cb3bdccfa85bda20854f1400d2e9106698e" + }, + { + "msg": "96691bee184c", + "secret": "3c79edf897d7685b5382a1ecf54b4dea41b9066844f7afc7d8beac4b7f908782", + "signature": "5bdbf5816b7ff64e5ee2d7aa6fc27b7ec468c3417439e77fb49e0e9d82e64db497237da265b324d429a282e9a90af157a94ce5f891d70f2e2e0d8a7992d9e38c" + }, + { + "msg": "c31191964e60ab", + "secret": "d1188370e98d2b1f35f6eddbd111f8144557ffe526f07a18af59cbd1601d53dd", + "signature": "2725204e2d214a2f2554867d150b097d79f7a5741e1c9328762dfc546f1c4e73f0fed5f2e2c76c3b43eadc5984da906270c27b0168afe2cd7514d8554919d384" + }, + { + "msg": "780f3653c04c28eb", + "secret": "b0cb7c3f15ecf4462021c16b9bf7e4aad0edd1a00d52d517b1443aecd0f9067a", + "signature": "ed884b0d446911b75542218571e06ee09751bc637e7877a25aca3c3d06d0e88dcc78c11fc97e65c6fa91d1860fa8ca20a6afc216a4c6930dcb11988b65b74388" + }, + { + "msg": "a231b7b9ab4bfffa24", + "secret": "f765ae39e8b990f7d59e08bca241f8d2851c6edaad9933d6cd27a769e22ee7fd", + "signature": "93f98182d1648ad37c156d529329d30f54b9f833878506ce7fa3dd3b545e3564c741c7b46fae6b8244c69def092d22d17e3666cc86cc42c231271a905ad2fe09" + }, + { + "msg": "11bbf3d0be76b57ee344", + "secret": "fb5b2ea31515875641ee839611b9448b9632ce9cfa6c3d414289947138f70835", + "signature": "3195d0c694cbd150509dfd3071b35041496514b814287527ff248d0815659fe29622d098462f2a207de1ef60569dcace06fcb35133e2a71cfa798b788db7158d" + }, + { + "msg": "44692e2d3f50b02e34ee54", + "secret": "4d82f350b75db7712615b783f6a8ef28b71fa87c3e7aeed9185ff36f88f7c28c", + "signature": "5d8dd314a9ba10a346dfe0bb5485924048f52402b32e794116fb91071b186d84e26ba7f6a04b0996e34d136d5d9c77bdfe78cd6538332a217a29998de412da0b" + }, + { + "msg": "fb5cf6d2d1b4b1367ea88171", + "secret": "fd3882b4e8f253b02478c14f40644994a1df21d4b051185a49c27d940e63de05", + "signature": "6037e6d413250cb749f53bbd15560a447cc0fb781a41f083ca33f9951ee57b4a26e82deba91e8db04de07dc7344cc50e031b7d9e7f72c14580ff0d03cbe41f06" + }, + { + "msg": "d0a7221b077d1a214208f37712", + "secret": "c877b864bccac3f12bf72369c736a6ea7d0771d769f8ed8d0c27141b774ae1a1", + "signature": "9499c3b4c093897890bf5f0446eeaa1d14fca3488cf2e6e30fd78f0cf1f6bae5ca8367ac186a5ac4ea1764b876d1ba945cf6162ba3ea79f57d14bceeffb6b400" + }, + { + "msg": "a2243e5b69c71e58fc16644b3226", + "secret": "0b6cb29e64a1c3ac0cbc37b146a9462935dbad36dc23404288dbe4bda3f60a58", + "signature": "efa97a7fe683df736ca814b0b041e04c9329f560044f704e66360df449b5442f8137615c1cb1763063fbb803e515f20571cc9cff54cfc8a2b9613b468b9a5988" + }, + { + "msg": "5a3be6c17021a8eaef9ad0d360e846", + "secret": "d552e0fbad11849488fd9c65491468c9c85c3b2d7d25f62d3f29498d9d783a6f", + "signature": "c33b72eff7cb6c86eef1405639d749c374273a2ff661c284f65a2bbb324c490a5be5358070f21ce39aead3e847d2ffc7a4e5b24d0f987cf0d2bd16fe78650e8e" + }, + { + "msg": "898bc102c615e776d5f5f28c857b5072", + "secret": "df3424af05f797adcd90bdde28fb8b25141886749f5389aca801515c437faf95", + "signature": "f89b588decb88c6f87b9241bb118e4612b86dd4a0c0ec55b89421f954a0c9378d5838c7b0b5d87d0a416ac20dd702c37666392eee984206ebb7fc0eac7242402" + }, + { + "msg": "199af157cb2e5ecd3dea9a169731209119", + "secret": "fb0bbfce58eee7b210812663ac16f46c77108772a0c8243844f606fffed5a5fa", + "signature": "e88b6111bc4a523427241c022dd675d26f68f4c143ba01b92158d4d9d4626cebb7ac6afac64e0e0be55b91c7c0e64903489b803d6c9690455a98543e0e0f3003" + }, + { + "msg": "a31b4eecdc290e623bd1b8f29a78e557aff7", + "secret": "200462d51de53fbdfdce72fb0ff61d01b6a7c02b452ba8e2426d5234a2578989", + "signature": "0b4ff700df6b197e05141bc049547c969c5186b1ea646b972c9073b6cfa909c10d850b9f4d7e40dce35697012fb170c0988ed78723e85aa71b6f72293d323c8d" + }, + { + "msg": "32b605977d59e9c7cd329589308cfba24b4987", + "secret": "a03784763e4ac33b29541730bd345cb98537795b7cfb31e2da89ba5fb230b937", + "signature": "82c0fed9c5b6bea4594139061a1fbb2a3d51642b612f90ee5e7ff5f7b4511603f8beba98dd946dbbdaf6376e4475378ce7c19f8e33e36388fe63c640f120ba82" + }, + { + "msg": "a3c80a4b549f8ab9d8fbc3e3419660ca9c960ac8", + "secret": "c5aae1ddbdd4b738b92f810964990deb046cd665980b0cda313a8b6679dc99a0", + "signature": "28cc2ca611a33075c0f47cc744835d1221c711ca8a18403f4fb0278dbc962295754dc4d9a2e56cb2bd488119302cc37d29c3db8420c8cc47917c7b9f955c3385" + }, + { + "msg": "23da4e835d454b2d942a2bedd2f8309bf06e9427aa", + "secret": "c80f63b32d8fe520c0a8b5afc5468d10f2b1644e6c366fd75710f8ed7552ce64", + "signature": "bbb7b9930f3d78670a43e38ac11bafb4e8850334d5a0a1ab607c9e10faffb7016ba347f60d1ab3037a613a8fd12f1d8f0aaa5294468385dc5ac19bd12680c500" + }, + { + "msg": "995236037d1fc630e71b591dc5d9806d354d15f4591f", + "secret": "4037ab6879c0663d689589ebbe166e311b0f9e7ced54a99c5eb31d1ed805de29", + "signature": "1962e19a89a633c7e4aac0ccbbc63fc8d678c00c0da1d853527e140b18e63003703cff6e0cd3d7f5d0a67788354a1ba44c320f5e6c4acfb27d808f8c50b31582" + }, + { + "msg": "5dd4c4bf64c159f27dd540be5b92bbe484c115ce9b265d", + "secret": "83df49e55453d3d0157f9675d0cf565bcaccd81a0f502fbf59cc556046502afb", + "signature": "19030afe2778fd92f4448adc97720b82b6ccf53b5958cf3c7ecf4bfaa1af2baeac6303f66f5e36a26943c76a2bd3e215d1015fce246f1041c0275207c49e7d0c" + }, + { + "msg": "141fbf43ad3c9c937ee42cc6d7a379229c2b0580077e58e3", + "secret": "a047e28819e1bbe14ac006e0780ef6e69cc98f444d8a477a67b5dca4f3783a40", + "signature": "caef435a8a86764205235cec12a9e486bab2201d97682b48c280423b3d7e1a1bb5db7dc8e917cd9b8390b76f66f79ad89d59de03bbe06a0e8a99e2b8b666380a" + }, + { + "msg": "1ee7aac8a07217e4cc226493f632711a75fc0ba44a79c1b30c", + "secret": "c12adcf996321dfe739d23b99d2546984e987dac4293a40b95ae8a59e71ffae2", + "signature": "7e1cb2b09c19310c2f87dfdcebe4d9cf8573cd806321bd083c18c7a71c2bc6356a42b0f071843503156f93f3fde709570f27a518b06892cf07e5ebb012481f0a" + }, + { + "msg": "67ee20282a6c742059a7b7ae6cf01d50c454ea862276917d4e40", + "secret": "14a1eb6a5e2a12896deb99ed7ff63ca3b3e281b22ced567181be1ef970ae7ce9", + "signature": "76689ddddaa29f2143f9ad977b78ca55fb6f1a6aeee0a1f817902f9f565cd2cfa70aafab024a69f33a327886b4b7b9a9a7930765b39acac5b3e55e01d7c1f48c" + }, + { + "msg": "bf217ae33b3ac20bec59fe343960cf8f6d5d173ca44832d9cc7364", + "secret": "b35cd84a3dc9123da2db79cc8fb37a1831fceb9a74cc3758255a5584b6ceb444", + "signature": "c0ce9cea86bdd756adca3a83db287bc2627e113dd7d1b18eda632ac8a11f085bbf07cfba666a3a9a435b4f4b6648a7fc3b51896d5709a5b7fcae61859eb39887" + }, + { + "msg": "87449231eca450270a8d5b6b23be9e96e0a8952b116d9bd542b897ab", + "secret": "0ddd8bef156a4d804255da0ee705d3daa5aff1d374a473d6dd8e045042d63718", + "signature": "7909006534bd1f28de6c45d37d89295b2b788822d54a4dfd4102db0f399b99735df7cd2c511270807b08b1598ed85ddb4024293d962d84e12b088d74fd08be06" + }, + { + "msg": "be8409225754b6c6dcfea6dedce44ae915a86cce07ce29d4363cd0309d", + "secret": "d796f00143d75f1a334a04c6490c9741782df209eabd5c6fb4c94161e8f03528", + "signature": "44ed443508d6c65fd5c1c1aada1b8592bda8caff9f6752997bb6e3b6a5364c8329a15cb02e51a7eb8cdc7ef93b2c48128282d348ebf7486ebd107f6e598fe485" + }, + { + "msg": "b40b5fd7ca7eec6db6f80ecb72779b5338b96b7f60e8d1d1d628e0a9a7eb", + "secret": "0cb259f53a29913679225f3e8ee7189c8410bfae0279432f83fe7c96f36870ad", + "signature": "28e0aaf249eb5cd75f1c65608d2f2d8fc92e6d5fd9cfe6c8c7f4b6c35424571d966549958968c936a5e2bd1f3ce0329c91763d3c01545205784cc3cef7a0a486" + }, + { + "msg": "1816f85bd01eb88bf833581c09cc8e188872b724b0354176d85b2654c656b4", + "secret": "fe688111153ad1b2eacbd98c161badf96d87c408ebe0937d52be3aff79beeb3f", + "signature": "eeec0a323175d54043fe75539c4300befbfbb6e70e350b30cac944a7bc078a6fe37e1fb6880179f0de60eeed33faf45b6e357f224c8b25aa62ac27d39168698a" + }, + { + "msg": "dc095ff38e58492168ea37b8ba801971724447f0dc33b66dda5c5be89c42b126", + "secret": "20f1047fcb297f02d54b189e82d6f532d096c08c25c06da291c064463ff1df93", + "signature": "8c20477e4dad15d916f980ae1ee7cf037b7552442c4989246617e8f8f21e74abeb0cf940691cbb2d413b96e40a9f2a022e61cf98221253c60f720b7ed5129f01" + }, + { + "msg": "2d49e9073db150967803fd570e5ee1d08f4136b840a287dfaa16d89d34ae7229bc", + "secret": "bf4a3d37a2d5368b0fa67f2311cb9640a4931348c8d279665c919471468f12e0", + "signature": "f49b855b8a457cc9a5c5d8289be8fb42a6faee4b88d56c2455370fb2b1080ca62386d3f16686d555b66d04c6d48960ab1b69fc6c159e218b07fec08cf71d2f0b" + }, + { + "msg": "da0be27dea4c2ee3a8f08a456912e8c2867df53f3e662a673df76d1edc506b1530c5", + "secret": "d621d80a6a245226f47cd2684bc388ceed6732df1177c993ef1cc655b9105a9d", + "signature": "1a4111e7e1d14b5a7197035ee174023aee3955b7894fa83b9002c7db883607c6b26bce7df70de182bbc539df5cc49090cfdcb8712bff07f0f3fdbb109c160203" + }, + { + "msg": "094a5561470d408f5978fc85ef18d2b4ab99ff78ba703df07b1d29947b65ebcb22162d", + "secret": "a1e8254850e7d53e33afc0130d9522af4974768831cc09f6d940272d47bf7422", + "signature": "e00602b5f36c542bb13e4b094f12e72f860ddeecf0f328a4c91bfa0cea1ea99991d4b9ca9386c5b3213f97299ed3387075ab5a08eb6a47775ef6df9186835607" + }, + { + "msg": "5da30598f66844ab37032b5278f1ac196b9b9765fff0850ef237e31858c0c5aec6061fd4", + "secret": "0fc36a96280502fd9e89126e59aee354dd05f08fe8d281c05491abee83b84f3b", + "signature": "5beee46711e4c37fa173b15d2d1db445e3fc9bce09886e7de495ae17963ad832076d89f4671d4c027ab8af1251f923ffd0bd9f75c9ccd93850c581c7ef817208" + }, + { + "msg": "32640e1999269491ee4b46fe9e52af94653cfbd8ab63bfb34db4faace4fa7caf9765e54347", + "secret": "81425fe5a3cf38f8515eb7a52dd3fd01cd507e43e44fe49fabbf1aa940dd4181", + "signature": "77d19c5fffe0ac8d1770e1dd3bafb65cc0d02244fdcd14f4266baad35182213b6ae2ecfcb610247cefa6eb0d38bb21d0de4425c9c55d2acf1dd91d9786559486" + }, + { + "msg": "c0e00344d4ac446b72f4558165f208faf3ac173ac0d1dd731e8a2026c6c6d49df5c0897081e4", + "secret": "fe7297cf33e937ebd86cb31c0c6d42a376bb30120b848e3db0ef99fe4883d50e", + "signature": "99672e0f4e6a73a0a1bb6fade8ec6b8463ab71cc862156d1873cc8a01e65e321497a30740e321f223eeb884572c274c278e09423e7017f04956e84faec874d88" + }, + { + "msg": "28fea738b342151461ce38a2b87bb178146c7aad663aa38e54f361b59a04e4335ce9a430244ea1", + "secret": "b6f5b1eb55fc3c7ba29adfef7bca50573acea4fdb203ce1f9be464c877c796cd", + "signature": "034492706b5157fefc825d43e251cc10d6fd4e22bd54482102026e7847f89f329e437f2dc5bcb9bd40f08436bb1a7bbbafcb966a61ef505673976be87e531489" + }, + { + "msg": "5dff6da4086f6b533c19519f683fcf870bab20f3bd316da6df00c47023dde0de892acf76c4c163b5", + "secret": "21f9a003f845177b92835c0d764b0645a24716dff82a477008a3c99e73df3ae3", + "signature": "060beef7c25cbaad23be604fee10a1bec3f23005d02d237ca182b5341623bd7430afbbe0ffc01667dbd3437f0aa2927c433ca9094cffe72222a9d9ff1731b60f" + }, + { + "msg": "4262a49ec4f480d508830499600f26ee1db77c7da5a00b0b3fa89d4ac58395ccc47f16393eeda1ad32", + "secret": "1347f3ff46b55dbf3aa9e3131054c4e01821469c46cb6333c4869494789c70f8", + "signature": "2b52c8c1033e780b6dc99dfdbe298fcbd8407820620964bf21078bc926747754ad143840e6d7b27256fc7a8056017f872ffa499deb5ec5726155c2123a9b8407" + }, + { + "msg": "e4ebc6c1101daca21c181e9d95234db4aa927157b54f875b807974c3cd25e195790d79db6f848eb0bdf5", + "secret": "a8588d8140a5b01861f766cbd0af31d8a9aa2dfb32cfefcf3cfdd40a35b5bb59", + "signature": "f919d9ef90aa5dc8518fad92b4fcd80cae87dff76e1c7302a06d2e8fc2236eab68b9401f14f9400467f259af0377ac9e09afde2772f5a5665677969f6c166900" + }, + { + "msg": "9b8c48268a95b0077cd1adf51b2c32c14bee6e8e68941d88c0f942bfdc5cd53fd6b848db4e565c3ca8704f", + "secret": "b90773e6c695196318fbf118c9dc377aa1e1de9fdb1eefe416a26a1f67471969", + "signature": "282899ed07c0f7bf965f6e4e8dd1c992c6f102a52f8ba4e9a586936dea461bdbbf6bff600d7d22fadfaf6704fea9cda9322e9093832ea42b6d3979cfa3ca8789" + }, + { + "msg": "19116e3ec707dfdb484fd78850e5fdd4e18fe5b4cb07fe1e2f5ed5366bfb47274256182f8e92c9aea4b3bff0", + "secret": "ec2b1e5fc2a271a21fe51499b216a2e0a5b92958a1d77a52a40f2cb1a42ab178", + "signature": "3520211e967619a9142d84ab856e11e9af5be9d9a6b5175327c42b613a2f449a7d60b6834c9bea983a228770307eb8a04872352cd4e2dc4754b672925301298c" + }, + { + "msg": "c216d06b5326b6d83febb7b927c2bdace9a816a1ab854e0403e52b4a1e31dfcf66621f238f806d2dfa44146adc", + "secret": "67e3cd7bc2bc04c1b1519f8004bd80c28da52b98a62af2a50279034e4273a168", + "signature": "8f83a46786b97239d03f0fc01c055e369b96965fa70e986bc0f44ac63fe052b6bc2fb88f95cb3feb3eafcffc30f4ccf2cea6bc53ce7d4ba542106a41ae125880" + }, + { + "msg": "175d9bd6990d02db4412d0790dfe98fa17b2e5584c2c118525bc62078df71d496729bc2065c05f4189e35073b52a", + "secret": "8913c40b979c45af98a94b2bf42e7f0e89bda7d426f724279c5c6c0295182116", + "signature": "a5a1c3f9c816f24747770c288a83e06641deeb74aff8c76b201a22be9ddffdcbc490ade6965973a115c97a0ac264ca007e0420e0d1fa1af88fd43f836f333f8b" + }, + { + "msg": "b60a18de7914ca143f6ffcf555da83c78a901fbfc4144efb2983b5070bd79440c31d89f721cafbea88f0bd9c555d58", + "secret": "fbda11b1076b94ee9cb03eca996226d5b0d0839b58369d12217b97850c996783", + "signature": "bb441458a9d790bb6e0e42b8066b3fcb14e17d516dade21e174375e379fef676a50741c27fd854c87e984af78953a22ad4cffd43196c91fef7689ecfb531a10d" + }, + { + "msg": "33cbbb77a6e088bb922e4bb2042a21b54043c34e1736cbd75e9a9a7f86e6c4ce091c75046872f608fb44d8272665e796", + "secret": "9e47d4a4b0c1e6f6db876b0a67329e3a42e2727cc720d9c230e0ec5983861d50", + "signature": "06706f933e4f2892467ebd0da31394259416693ad0baeecadd49362d15d0dabc182e479234bab4dcc6815b594d59de6e158b8d8f459dad8c736ba7f17bfd5703" + }, + { + "msg": "e9e0ebb3abfbea86731d8e5225aca6b1c2dd5f73824ce5d0b7f4ee99839c1f2becb2ba686893258bb8ffdc0b41bb954c80", + "secret": "925336eafcea668d49fa14d50640ebae7b3cc1bb05f6644234618086ba6dd5d8", + "signature": "c5c73de0523421f8c1858fd225799b3147c35367051ab3660beb0ef5b86753086810c74743ea3fda36e73e50f229745554645f2d6d1e5438317523d4c8542f8c" + }, + { + "msg": "a14e2a8f543c3c5190d25a282af781335089296238ad754f29c9a08aaa7c71229aaa868199efa9f8911b203da6f4f0a6bd0d", + "secret": "9bad136270ac39145ee10fccf54da42d22d7de6ebc1d0bde5467f8fdf83c70af", + "signature": "54a66045d85c1429277357671e3fbec8fc5c77a97d1306d1ba9c74820ccf662e4358e5c015a2387b0fc55bfea61e337852ad6d48ee46070e29b1812c8d57670b" + }, + { + "msg": "7a", + "secret": "1c5ed3920c40d967f065055cf6988c66520446f5df354da1b8ff87f64d6644c7", + "signature": "5a220ebe499bc12c3feb8184d258e7f07380b5c7ebac4c6378f56ab20aaf84abd7d53fcaefc5efb73dd1f863ba58931f87580e5bde14ca4a5ccf623d1bb30b80" + }, + { + "msg": "db8d", + "secret": "9ee0f7ba52f241682cfdfbe5cf00e12651a7a94be24aed12910cdd21f34947e5", + "signature": "2303bd957a62b2ceaad2bba616c1eb2475eee033c57f32e7e2124a9dca45ca272195321c5e27f16c6d42d1c7b39b5f8a60330560569a969cbd5fe304ca097989" + }, + { + "msg": "e61196", + "secret": "516ef0f80a479a93c6861f78e8fbff04b01ef8d6c65ab2d77d55b912257a4e0a", + "signature": "980147e841c8b3fd32b9d5b7c0a743850178133118b49c58b385648dd58814410f7f0fea0116bdf88bc6231cf7bce09ae46c2b5498f8c0f4a18b2af1d3c39f0f" + }, + { + "msg": "b7adde6b", + "secret": "206f1ce13948ca499d1bdf13bc6811c2d154f055f9bd18f0f924f2d1719913e7", + "signature": "51fd7161e9356ffc4e73d4793e37afcd4583044292a3ec8ed76d2f3897d760ea303b564bff854b2e84763c76587dffa1242a53d38f1567e4cc60d606b1a1570e" + }, + { + "msg": "42478f7914", + "secret": "c3b8feb9306753bd4037bea0b288b15facceb40dc565b377fc68ac38618e665c", + "signature": "a70c8daa236ab91823a7412ac15b7731a1398f52e2393447aabfebb7cc43736836d38a94100919d2e75f76ac2cb7485bf2fe6459b9f0922333882c105d1bef00" + }, + { + "msg": "a8cee83b4840", + "secret": "3bf7e516d8c99f1efb437c0acb2bc44e86eaa6b44468ad9a6c56f5ff2474c153", + "signature": "540c2aa0466707a015fa7e9ede994beff92b41aee867b10b44a395303234ffe7894d8e7231509abd4185e4915a3482c0790bbfcf3b1618152be5314261cb8f86" + }, + { + "msg": "5677cdc41f4522", + "secret": "ffa9ff1ab63e86df6c06196b3682b06987a42b5d815d40055157ae5040aab74b", + "signature": "4f87b42b96a1f78b85bead7b6c1c8f982f98f3cc71b02b6a1b68f52f2d22ebf95f0924328fcea5e388cfae3768ebf6e3b731b7984efb8d7a4b18e36fd6e26f83" + }, + { + "msg": "4d4946dec740deea", + "secret": "3b335a4555a6dbe35642aa872d2c486d19f2f162e0d1bde235e1d7b48ab4311a", + "signature": "5892f36bfa7025373b372d596c73813dd048b021149126180e01865ac9ba3a5af7eaa535429c2b7a77beaf6ad339b6e39ad6f84ad1679a5d488f1a1e0a872083" + }, + { + "msg": "736617d4b1403645c3", + "secret": "0331ab69b783b006008e363389bf361dd20c50a0028f046dcc1d0a93060498c3", + "signature": "7f205dcb3ab2c7ad36c4bd551863a6cc4a41a5c269b1dadf358e3bb9ba549073b019d82619214724cdc3ae8be39e0fb2a54f364c4a3f0f71cfd1d29a994e1e00" + }, + { + "msg": "5606ab63e7a4a06af1c6", + "secret": "7e5c2c3bdca1593a92798a3e4c80c9ef3aa378a7b5c9e2163daa3bb62639dccf", + "signature": "5f076443ed0157aa4b7adc656aae8720d6e6403449700b3cbf64834eba15dfcbe3b4761710fa3aef0d41b42402f1d6ef5bdc095a5cdc1969c9951f9735162284" + }, + { + "msg": "31fab344acf2280728e49b", + "secret": "41cff320af7721375acd59cf42075ae148f71e7da1f33dab28539e9deb03d62c", + "signature": "56cd2381d6f0c04407da267f5c63f9c47e5cf2d512f704f8a10405137b48c329cfe4ef12efedee980e51f4776178b90144bafe4919e950728612174562b4bf01" + }, + { + "msg": "6362934904b564e6d3ae510e", + "secret": "092beb36a60e8dddb614fdb11ceb08b7bcc188c127fbd5e8466b63e646541a2e", + "signature": "c2b9b93d2165c0501e1e15304d7c269b3e19d0de108d911a5eba5575d30807a70ac23c61e68292c547c337fb3b970e0d6d2402446992ebd08c7151300f0e910d" + }, + { + "msg": "7a35787ee60627af6da56ece24", + "secret": "413d16ef46977702171367684a1d63dcfe7f233e6376b3e64a5502c2d59ae718", + "signature": "fec51e61a508ecdaea7b919fc9ab3096c48268bc357dee9f8ea3062109a4f76ca1f04d16190c83642813d32adce0bca3fef0589b2f5d7d5f158b70529b2f080e" + }, + { + "msg": "cbdbbae6cee1011b869f37c26b5b", + "secret": "ed621119723edfd74090e210b31f3a61fe488ecdbec55cb7558ccf3fbbb016e0", + "signature": "ffdd3f7db648ec9be5ecda745018eb5863c909cb20ff85f36ae2899875cd98c496a09a66eae1d2123d27b5e74d08177571767b4d16c8793227ea609a02630a82" + }, + { + "msg": "27af0cd5b23facca51db775f222bca", + "secret": "716b80c33331d94650a248cddfa4eb9a1ff1f8024de5e525c29eef54ecf3d976", + "signature": "2b1f48ad2a3f2529364a54faf160cb7a07116a554e96a6f3ccb857952c49a1fff14fc469b79cf093dbe470c0b5b71bda839920229a92fea6aec90b5df58c3f88" + }, + { + "msg": "795a0bf42e946278f2c98a16e43c52ec", + "secret": "6662a15a336d3fe6febd16732f2cbf9e4efe3fb6865fb6c6bac5710012db70e9", + "signature": "702b32b9ced97024c2e5583851caa75532b1e4400a7299045560a7a0e7c05478483d829dd006f4ae17830e924417b9cb6f6ab40d5d52c2e58fd0ce146ac4550b" + }, + { + "msg": "b6c76b124b3c43ff7aae7e36df90a4db46", + "secret": "0d9686abc47d520acbd14d1641bee90ca4b2f1fad28393abad08870b59ac4e40", + "signature": "bfab672ede0ece424e96899ac8f8114ed654472ab576c2ed1abf06ed6945b28ab47bef95d94e0ab26f7655c82581f1deb38acda28bf67f0ec65deecac4f34c8b" + }, + { + "msg": "c881c86343efe607a976948dfea1419a7272", + "secret": "67d84a7e211702040e945fd77799bf71650356f6d460146c3ed9641a1efe2e22", + "signature": "0bef6a4e326c7824b136c422aad12dddaf12f8ff5ec2a448d40fa16ef5566d3c69d4208efae3d2a0f78c27b271f66e7ab35ca94036b373c8fbce96a64b9df60b" + }, + { + "msg": "bba6886e5f19f8e057b01ce75d72134fd1a2f8", + "secret": "9342c68c95be12a76fa43f8cc432250daa76eea5db5d79b011eb289613ac4685", + "signature": "33662e95cef048739b17687ced53e00913f3a425529d9e771b7a03b630aa4bde50eee98f7cd5af138a7f5c4920af484280eb4fa06bc6afa26e77137d663ea80a" + }, + { + "msg": "261ba2bdbb29ad98f68964760d8d20262ffbab5c", + "secret": "2fcde88be6c346eac8e141e98d85839ad7663c4b4737d34195d23518d693ca8f", + "signature": "2e0570bdc9d93f5d67b046ef77657152bb6b06afe300b9c28e1f05094d1434429a275119d8bb5b4a366d27aed0de6f553abfb9d87083100664a59a538639da8a" + }, + { + "msg": "500bb6a61350b268afd1a6dcdd1c4a06ed787644ec", + "secret": "af6891ab223baed9e45a0434512db31bcbd05e893771fd9e6bec33861b6f19b2", + "signature": "37ba5008f0c8a9f61ef464578f8cb588442f5fcac5c581a5adf448be6905be608e9db1530a5989646c71ce66a12580ba6eebe4431bad1975e003c26c32f18b07" + }, + { + "msg": "39f44d6827306d6dded32773df8f3ac4ab9026a90cc4", + "secret": "6583ad059810997d37673725d4aa818edd7493c4adb8432e7591ed0856d9524d", + "signature": "e88e87f093aac536294c95ced2ae2707eec554c11ec218dcd34411d407ad8ba88ac8dcf332373a2a6caaee70e81d6f9755aebcedd57128182aedd2f2c094ba81" + }, + { + "msg": "c37f9d3c4ddb8feceb09530d6cf7c94b63034d23dce9be", + "secret": "93fed124bf53258f0c11dfd04b8465abd917f67b745156b1a49cc83a9c28d904", + "signature": "01029f0f12015b395807b40b7dbcd6469b4b764d41c03b9c081768e896db09c539f6dfae09f5201f59ced1904a2d25234878238f2c2981b30682dac59220590a" + }, + { + "msg": "f6f4ced6ef76c134006c2009846be5c58b5f077e66e1a302", + "secret": "e3fa72004f4bfb20cff8441dd74b277b37f60b849d0daaf57989b1b333b5d7e2", + "signature": "af7da63ccf78fb50610f114eebfee68ecc41fa87be32ac99395b71a3477c848302da6614284bb53e1eba90d6281d6d2ef993cfc4a23120b054522d297b49460e" + }, + { + "msg": "af73146d2624fc49667ea43724fae9f56b5423d333009eba88", + "secret": "a2dbe25ca25accf875deb3890f7a616dfc8c54059562cac191f7c1eeb50e99bf", + "signature": "ca7b86d7356d803049400d9a40893045b604a7665c5c440bade874dbe6c747740bdf7e6b80359bfc46f79379fb540b83c266d035f11b2b34ba883cac7efa7482" + }, + { + "msg": "776c62ea6bbe3e9cb8cbc0be1e0cf7b4963860eb4f30a096627b", + "secret": "012ebc002673ff2bbfe043dc18e5733883011406e57ba191e00a58bd36dc2eab", + "signature": "ac97e77f76562a31cfbcdba0d46118b3baf5dda4f22c3d79cc8309c6dfcd6db3b86f4bfa32f9121cf6f340aa3ee50f94ebbe7476f5a812115a4d48764920eb02" + }, + { + "msg": "b20886bf163f4eb135f81e130504061fd2b3972f2d772ded86a47a", + "secret": "5efe8749fcccd48ae962276330ed9c0cf3358d15cfcfda639a4d0bd1e1d3da1f", + "signature": "87411311aa8eed98ab16fcaa214f80eb1db4b43f16202c97ab2175d8958e5f34de51f55ae2afc36f6c02d00d128f98f362116150b7408231de9ec7868140218f" + }, + { + "msg": "f4ef2b3758f55b29144daa6473157cb99538468fc69e828249b193f5", + "secret": "45a318c6f1a5e4647f8e1938879e2c54c6fa6c1b974c7d911568ed947ea9d991", + "signature": "5c6e73b15166247c38a999ec3d1a5f1d89cc1f75be5b1baee2ca0f9bd4bdf08c8dc2f24f53ab7ca31e55ab62341220bf760f98119df50b7d1714d3c3aaee3d08" + }, + { + "msg": "ffba6948f62a4e780e3189801d8f9030a5fa6502ddc2f52a9e4a041d66", + "secret": "58252fe77c2200ae306479e145cfcca042af6bff9a0461c3f3ae8271d1144247", + "signature": "197bff2fe14d3213aaee483579fe875a34144523d316665fc3d3ecc8cb9889dc84ba4d3f4ec847572624a2c8bdd305e34c62826e077133643f33b3bbc5d37007" + }, + { + "msg": "5b4fcd1db10d48522b254d7f4fa5b02f816f85fc70dd41a699e957f89f4d", + "secret": "926a9c29047abbd916a5749f5362ed943b146256abebdcc5678cb402bd3331e1", + "signature": "ebc5471989dc9da3c9b4c49b2bed85d0455c8709c2ca0ab31a8b30d6a1268a65f222f1e237a8276d4884e82e7aa4cd2bd99411e4f9775a032c38d794a7495382" + }, + { + "msg": "ee491c2d0d7d2e1f4da088ce5cb98697d29599779a44c608be42fe835795f6", + "secret": "9674b1850323130e3836969633edc8da34e9c432d77ece9c37318f34c6234496", + "signature": "6d8d8dd33738b3acac5c695c13414085728792c97946c6e3f0d08ab5876cae32baeebdf44465898eb82fc2836f3b2f0c1afae970ec96d93bc7ae8b78ee46e902" + }, + { + "msg": "b319b6531a784c6cf505f33ab2bd7873e5bbfd6aa632a88ab1f6064e0b80b5e8", + "secret": "97dbddbcbfa4c4bcbe1eb49a03340da5812a31872da90c0781992d03500d626d", + "signature": "cc07e2723b9e0eb7acf1ce67cf862c7937328909354ccbd2db731ea20d7e0490b344945f787b1d8509de239601c6826d8a8c2e09dd8ba810dd27e9076c0a4906" + }, + { + "msg": "fb47a9b0c3e6405736ed83d42b36918edd64c15b870ca8d296811dda0ce6a2851b", + "secret": "8a82d435f56908faa99ad6ee93e54df675b06298301c26ba8fe89a24b57ae994", + "signature": "04b60df2e2782dfbcb8704373500d5e082f73a01f257cb97880d093b8ae37af6eda97d94c20b76443cc7edaaf02ecae791e97704ea3e7b77947644ccf7165207" + }, + { + "msg": "f36b0ee3f97410d6d6e8a0bec21ddf2b8e808241df8d1a0191e361b3d05e69accb22", + "secret": "d7335e194b7ac094bbf3e6cdde02b8ea1188d1b1aeb5a3dab0627eeb58d62231", + "signature": "a56cd9a5f1b9299e077e691f04323e275182972e76c886f0eb3f93a72dd7ad63f68d846f8090aee3b286ba3c13e51b552ccf65437f0679e6339f645411771388" + }, + { + "msg": "91515014b066cd6c50322d63b00645ab0b52f9551703438e8755f221ca926f0b717dd3", + "secret": "55bc4a1c699e034fd1e00cbfa5b2d2ea622eef4bbb43e5dc0cae99338ce1a3a7", + "signature": "40fb7c229bd9591565a7b1e18a13aa36045991178f314b6c19c84f5c8750b4350d2556a027c85bcf024ea4af032df2c79d549874657791c497d77942f1e90c83" + }, + { + "msg": "93bc1907ec72eb3a6d0a5cf5fc8e93ba10452a8145e1ddf2e86a9c1e4577a72dba36ba48", + "secret": "af69e89cde8c9486252f319a7d7bc88fb3364f5f585f7d629851fc759600ce0a", + "signature": "a794b8279f9fdf1dab2a88049f0381941c98c57548f5133c81a79d45c29887ed109c6e512e37f3d990f14781c789bf35cc971129b7d404e357946f0e06ca9b0a" + }, + { + "msg": "1abb81edf8931d6b8085d424c9b74e1a2715c2ea5ec99a55cc14ba09ee17cb1007ee27f19a", + "secret": "773e6165372224d0eb686c0c5540accfbbf6ece8233417a7c4204d92ebb1ba52", + "signature": "00a00b71d6d65bcd3b95f175055c5f09c3bed7ccc3657b142466648aa499f38fce9dfdc19e10812c454c09590dd51b4becc728cfc5ca246f14e49333fc148c8e" + }, + { + "msg": "beebab1d478d20241fb37679601eb98adb4b6bb7c64cc70540c0a688bd1b722a6c1d0bc3b407", + "secret": "7759065b59040d963cc3009848f265ba3387af5eabe71555c7e5a409135bf06d", + "signature": "674c310c4379764717911d7807656528bea2cd11dc19601d40889de2d6a2b2dbd2e08da1444304689d360be0b00e849f0c604841f17a2cbd7b9ddd06a1ca5f07" + }, + { + "msg": "cc9612c390c119acfde30242d10730cff003bfac5ba0dd9f37e36c45c30d40d271bad31c7a2305", + "secret": "248565272867757ac86d0b145e3d42c831ea621b4654b6c7bc01ce8fab2cdd85", + "signature": "9524ebfd73f87a3e9d584d91f353cc8577cdd52783b124092cc9da26bf69df814f814e7c6d2e9bf1d82837b15ab16757af5d8050df2bed002d17c8e9b8d9eb85" + }, + { + "msg": "2a7c473bef4c5b58ff770282e2d8d5202bcfde284638d218c8ac3cc512f95b3dd7ef5911aee345c0", + "secret": "544de634496c366b42968be09e0ef75b38b2506be711b31420dac2446c49004c", + "signature": "c469217d0eb59c142c84bf27e33477e8acaa8aafbf6a60bea1f022a83cc52abf1c028e0ac42279dd0d223bd009d445db2a46fe77a6e70f0e82b135e234600082" + }, + { + "msg": "65e264479ae8ea8f3f01329299fbac89114b0638c3e0cb380652ff83e5f5e3273e56a0b656ee296fb4", + "secret": "3a7b3a080130af729a6f00b6e07d55c2f993ef683675353df661d1acdbc284c0", + "signature": "c49155d2117e47760cefb8b9f62232beb21c71ec2c5a375293348046d920979dd2e38df3ae47fd5a1240fdcc2a05c5d273cafae623121d674ddf045bdcbd7684" + }, + { + "msg": "d97af1088a34e02c5c25a8f1d57221b253f02f1668ba51a0066eed62efcbeff5f762cac21c0eeeaeb323", + "secret": "4df0c5c2fc0acf93d6c80e8fcdd4ddef9dd45c39cdd47ec804a8996303f78b80", + "signature": "4e9d6ff7870eb9804a838de72007bcc9bb31d8251b2bf8e656d97935cfa367c5fb6c25486794456d1f93372c0214a3b6e7a8ac3cf89449854f31cddfdf368d8d" + }, + { + "msg": "3797df681e11377d02039393da0d2f3de3672b5643fb7cc9782df03c1382475a939f67064455ec4e1f0fe4", + "secret": "eaa9ee3e8d948f85b8725bfe1cbdb2bedf22709e66568b1a28664f1a1f0efec3", + "signature": "5c43feebc50e32110233604ae8f6ecaa3f57adcf72484ef941d649c41cd163e5c8a0e2d1fe8333e297124a2b73c5a54d933a32404ad2aade321d5fb6f6a29e08" + }, + { + "msg": "cf11cc198fd50d9861aa987157a8780bc7bfe6285f6416d38330ab224bef4cfa823548e34677317115215456", + "secret": "5649087dc80755c5c160a946f180959a6109a855f988140efde9be88a67b3664", + "signature": "883d5fdebe6a47b0e1192aee242cb722f9915c374783271fac6975dc394d218870adb66a79b99ad272953ba98f38626b0914649bdcec468d486c1b2a2a46a903" + }, + { + "msg": "cc41184ab14cba951f23c9541a865c7ddb1e28428ad48a5ff470e378c458afde63fa4b3bfae4da6ab0d6901878", + "secret": "bc226a0c02009214e1e835eff8dc7bd2494410b3b50c60712a8c8a2956c00023", + "signature": "36541407a63bc57b09d2b3556aa5a7104fbf98f44aca0c2209a5cfcf98e69c82b347b7a68e359e5a11c20c94eef10c1c1059a25cbb4fe0befb4987ca057eac0a" + }, + { + "msg": "6804dcfdd540b0453ad7da57f6792bb1f59b3a98780b41241ac73d1d7597fb884ee080ea20aff26032f2a5b7ffa4", + "secret": "11c45191beb0a0a25eb3c916898df26c189ad7a404b9426c59796937d16afcdc", + "signature": "2d2d20d93356a552d4bd5392679c12dd0f45da5a6660a99656b1cad06bd7e0cd6baf30226f81509d59a049a70ed310590725a0d54e70c2c777ecc85ef8881705" + }, + { + "msg": "62edc5bf05bdf320fec3469e604504dd065c9c7c2578156dbb41a63f5818d347ceab8045cd63cf282338576a5c0662", + "secret": "b84797f691a3a5d1c1cb1b90c06722ce2153fa19eb9e806e7913affb277dbb6e", + "signature": "6e105779b75e92866fd6a91038528ea7f940d188a58e06c7d07744a58dd7724a03b97941732da8c629c9e2cb4633a6de8b42ef5159ced7b7231a12bcab691d07" + }, + { + "msg": "ada410f2809bf3c063da562de951246ccc85c50a868985709acfb6d23ae1780716744524d5c5f4990ae97be8fa9e5ceb", + "secret": "6a02d3a3d345675771d02025beb27be22fefcee60b3524472d20469b0f797c21", + "signature": "56b3925ae143c218f74c6f8d90a65ecbd500a9dd63e35b5e7a4ec6710ab185f0b0810f177b609a36d9ff485c8ab9bb4c66a3088a6e5edd0fb00a127b89cbf002" + }, + { + "msg": "6bf0c915303e9d053899c80802a42399f98d9464f780824d901206d7680f56999c732f0f487486109e93d9666d5bfcf52d", + "secret": "ec32d99a440f3d0af18cb8b3dae83a2e9c8cb39dd8c00efaa99bc1e06eeb7c77", + "signature": "c550d93e776cf963fbc5e5a00c24ef21902f71cf70d0dec6e9f0b780bd37e901e00f0ddcb0290005ec63fec5e29c1a239e469d585c06e65fc16ca896a1688087" + }, + { + "msg": "", + "secret": "5954964ab744e524e7e70c43863d57d0c62e326d4ff1400d2cd0e2073e97759a", + "signature": "f66fe21afd43427b0e732baa3bed25345ddee1042afd33f188d0200dff34c90bd6d994a58f9654be780f9948c3e742b259f08ca6b18bb3475dee3fc619e85206" + }, + { + "msg": "84", + "secret": "e38a64044e641b7cb073f286daab1e4bea645eb50778533030de6da960775877", + "signature": "fcc9b4455ee34a6094cbde6f69d276ce152bc8f0da78894f06b02439bdbf3361e18ee6068119375349a107865d2c56bf07c851ebb89660034758ad507ae99282" + }, + { + "msg": "17b5", + "secret": "7e4325fa1b23c03398dfd1546f81dc2baba5486be2a96e00f5a4637c12945bbd", + "signature": "0b11160474e7938f26efda1642ecbf8370de377da9b94801cab8558ebaa98b48bff214f28b91b135009f12cf4a8a8a7863bfd9090f3a6d459998d6d2a74a6d07" + }, + { + "msg": "5b59ae", + "secret": "41add8b922cafaebc33a9bdac3abafc93e9f81aa7d5b8c01604460c3a56f00fb", + "signature": "9e4ba3c89cc9d7633bba3f1f190af9aebb455e86e0144924add7d0224f68b37ace4af4c14d4820495864f07aa2e3031529085adcb64563d2269ac0a020d1a289" + }, + { + "msg": "95134a6a", + "secret": "926ff83e06ebeb8de976151048f0af91f33b676530fa7ff3d0e6b06d143990c4", + "signature": "7099ade107f1340ed8655336526e21916ac9e179cd17a8082b8a87c5b00f350a616e7f574db785374aad9c665a122b33acf11a151e92fa8369ad1fb06086500b" + }, + { + "msg": "05bbac3f83", + "secret": "92618a5cf675a7983ca590f81fc44a9ca6db3bc92f7a36ff89b1eaf11f13f45c", + "signature": "d3502d568e2a331816cb164120de6fc95b8ac59c5173941281e88d18d6f2f6368233d0cc3e218c8d68791ab7da84fa849cac483529459ab0d3b11c0cef73818d" + }, + { + "msg": "cb5a0050dc17", + "secret": "8866243cd2ce307bf5a7a2fc80f322fcac36a5b03fc6ceecb84ba7adef621a41", + "signature": "178bfc2e2ea8f47f0401448bcbd85070050924794d547ec74d1e39b35c8ccf01345c321de1941a71f997f39dcf4e9c5b188d66101900c9518691b0080ee83f8a" + }, + { + "msg": "5237c24e545477", + "secret": "55470981e0e80ce4c5df351f444a9ef7544e0d59c59c21be79d4ff11f7627d00", + "signature": "38658bd6c30275b12fe2401757e0f062edbd3de32b15e2e5304c844638719b128b97e211bbbeabf80ed60aecc3e65779c4b23043be1dae728e28ed731571388c" + }, + { + "msg": "930cbace486e917e", + "secret": "ceafc7e3887a8e0faf63663d3dacba5b158507136f01e0b28da651e681c0ac41", + "signature": "3dae7f80287116740d29689eb0d752392e1324beb045e2eec8ee167bc3aeaeb7b290a179a73e42a34771bc0749dd35cc819955da2cb9f41bcdf3934873c2958f" + }, + { + "msg": "badd61b029a9640d21", + "secret": "9305469f9050d4063a9a97c5470b8504c1bc9f88036a9af60829c028b2ad578d", + "signature": "26fd8a84cda417752449acd00c0032df1d96117a0cdd486c140686390a93af6b351acc7be472dc22b164d356141f17dabf8d0db541e976acd51bf5f7e0567f88" + }, + { + "msg": "3ace90966c15263b90c5", + "secret": "b5ce6ec8691a365aecff7b9cc8b791ea3060d148b81bfe6aa1a3b6a04ab4e394", + "signature": "123d78c346427bae16e73670a9407575e6144a6918173a9701a64fd6e70357e2b793609ae131edca4c6ac0b1dfab9c593605d282521de6f3d8f36d446f5f888f" + }, + { + "msg": "d8b8ad363d6f7573f5af39", + "secret": "a9091500aea33a446b9aad8932d22a9172fa70cab14f69fe3da2c6cacade7367", + "signature": "855bbb9487758f0bc681669db3fb36b779a46b829b76838794da4241a233bc7e2940d679f16f7c08d6dfd430a542464bb1491de4032aaf3224d2ccd2df0f1100" + }, + { + "msg": "f564e2c21b31afe25bd890b7", + "secret": "fdad883e7ce792ba75a11000989041530912d4038825b0d783be7b0d31802f57", + "signature": "692f76c9c451af012b2781e8f2b9595d1d3593f4851f11b109c1b47ee67bbe70cdeb259aef123fb3ced81f6bd00294f574b2adfd905eaf98032f23fc93f7de0d" + }, + { + "msg": "8b07e7bb83e09a8f30f021e688", + "secret": "ff286edda0e412c119505576178663aad6e72a898134baacb43cef90a4f605d8", + "signature": "76a09632f983e579982a0988ab015909d7f16c1e7b79181f1b7c61f034b2502943afae3d233b158ad67e8ead8e74bb16b2f0e7bfcbcbef595befdf5ec1aaa88c" + }, + { + "msg": "f9ab048b3a052e4594d1efda5b69", + "secret": "fc5b461b8023215d50e3fcf0b40494551983683fae87391636e276f9b7f11550", + "signature": "da3ed31313ab8c590318a8af930e1c2284101800f2fa8a57bfbbc7ed3cc997c20dc1f18f2ebcf1f613246be4e0b219583f250551cc52ef610aea63867f4a298c" + }, + { + "msg": "2c204fe4a5e681aa2509f132ac2fe8", + "secret": "0b5732bb442dbf9eb8d708631fe2b5f00c21ccafa7f0dd2ff72d8724a9ed2f73", + "signature": "8bb3d7bdc82e1741e89490fe2698fd62be7f0d8dd9af3bb8f21b98141b497b1a8cb46d1c7764c85ab9b194f15875bfd184e0a7732eea6fdb873d4d8f981b4c04" + }, + { + "msg": "6ab989c06c024a67bdc7a4723a37ef80", + "secret": "ccc0420edba23a5d011a6039f06175d9e5f2a0beb46adb8d1b9b6911d4bc69ee", + "signature": "85c82417e47999774fe5a68b27dd8fc5036224925015bf48b663926461ddc359cbaaaf85f0d31124a79890d7360821f00f9995b94d03b3672a80a4425cb98802" + }, + { + "msg": "6dc339870150719e5b53718af044f8c8d8", + "secret": "c6fb79bc37b10c93450eb66dc445c7978a6d00c9ee54d50adf48d68e2f1d99fa", + "signature": "50b75ce9db72f4d3c7d66bf0062a562ae1a2a025314fb5a279c352dd4d8a46c28692e13d60c010b7d1860318c7a5312f6db593e6fe66b28518101a7c4ece020f" + }, + { + "msg": "50e912ff9fdee853f2bffad12f24c930569e", + "secret": "56bd451a4b6b55d916f5d36a9d600a98e010a6f56fa9db2bc0bbc76cf3bdedf0", + "signature": "b89844c003effc136a44d8529bd5af9e2066da3282b919889cf6b1b63546b48b87d8300a068c1d85dd6d50108bca9cfc5662bfbc4684460e0dc069d87542a305" + }, + { + "msg": "9ce74a69077b5db3f6e4b7c96e43921d456497", + "secret": "35703bbbcdbe80c23cdd53cbb8f0e4eb271f1951b126f0576a0c430c620ba08f", + "signature": "727457b7fcd2d16aca7107d8ec697ac617c7d7f1f36b00928fd367128d24d179fbb36089dcbc8f8c924e00ec134d2b562a9176a71bbc8c82af20d1d064cdf301" + }, + { + "msg": "57d039a1971cb501390d606379a369d8ba857291", + "secret": "1d368bafca4b53b008f65fea913e080b0296ee633e2811341c2a9f142d97989c", + "signature": "cb9a4a4f6442e5084ce0fc7d320484038557e79bb44b2ce8c59995fa21a134244348543121832fc6dd315505e636fe8946c8fe407da3a5fa35ebbfa5ae83d287" + }, + { + "msg": "1b5f1ef10c47a9852185dd3e3cdf946aea3f01b69d", + "secret": "112be88e7a7a2ebf385d3c612bdd32eb7a45e1d6ede2fa27bf591ee2ab443583", + "signature": "e694e8c6fe848197165c1b7150d07f2a6a7ddc5ce636c8f7483dd9f87ada2eec7b91fec92f1c72d2713363410b5532157d3e9354126db2b7f92c4f28ade4000c" + }, + { + "msg": "19f6d2338ae54b8f6a0a4ebe67e58e8c96ea7c6ab4dc", + "secret": "29c632b487429f1d1528ed428cb4ba3844c1b20fb784ba2990eab5230d8c1b4e", + "signature": "015704f124f9c7d3ad183d28d1a7f3278a9e1b2b160e334ca33fef7f0a5ec36fdb9f28634fd76021177333a8b307dd90bd3c45a3193ea7ab3cfa3905bd7b1101" + }, + { + "msg": "e467c3d431cf0d90ea9c87800dfbf8ae87b1e9a6366347", + "secret": "3ddf2f29788846793b5a0ec97149b598cff609fd927efcc9a3238bc7fedebed3", + "signature": "bcfd384449591c68be8386f8aa0d24d1c381781c27a2b5aae9cecd65aba0cdb6ea437223d3581b2c54da969368cd93eb2d9fcb8bd4e86d53d3983fa5a5b5338a" + }, + { + "msg": "d3c64c6610f2b3ea994a1bade14821779dbba54397176438", + "secret": "64a68d1fe3e22fbf6855379f9cc9172ba03e00d9a0f3501d102aedb4bc0a6e09", + "signature": "ed461114f3b314ac7234817143fff12eb63824ed68ff1773fea176488b9e0ae07f5b53280522c5f47521d28a6a1c9a892aef2fdbd8aac1f5e13d27b65ce4e587" + }, + { + "msg": "128dcac7f65bffd50c70e5494284bca13866eb72a30224789c", + "secret": "edc9137c3215497dbcea39f4cb0147a4c8016a67087cd26747c12eca17f79243", + "signature": "8fde21e64db6639105a87dfbb9d510814c533b9823aa7d4afe0129638655170fbc0b65a7bca778fc18aceb92d4a02224f2c54f2064331579e1cb77a0064f998a" + }, + { + "msg": "b44f8b0f690389d5e26018ab381b8eb1cd375d42efb2bd837abc", + "secret": "8e8e2f8e3ce960a0f41f48b7a823d8307ba8b0461230e58b499b91fb5e45f2a4", + "signature": "d802689b37773573f71a9c3c416e530011a3ee7974664d30227c2ea16d24cb56a186d57066f73b2592fd6c4e81529cbf399b3c052b217843664bef21a65d8181" + }, + { + "msg": "b0326a0aff7b636e39a4fe0c9a03ef7651af899499f4d951b2bb0a", + "secret": "10686373082ccfb2b84e0ae72e4023eb9c13227e96426dcc8493259f6f6c7f31", + "signature": "4b3d8829d082f6d3b551e3311432216f8a649b26d7429126489bc59c0ad5125a1f3bbb3fdb20864547bc7733669c98e7b2b559c77cb6785116d09a8aa3f6d68a" + }, + { + "msg": "83021c2d88d1a8901777a100a24433828924ce3b8643ce408349e1a9", + "secret": "475a0953ef7641a6115b963217900d7e7bc6f764d9d0a6543843b76daea45de3", + "signature": "6dd2b9cc733130f01630cfea33f7165750c839b68cd7cb7523e9a2920b5733a5ea5a475e51793280e4deab7c5bb27bc98c59511825202b78c8ed326ba8bcb783" + }, + { + "msg": "9e89d3531e909882289afabeee580d6ed95c9bb97c2a0cb3e2df34c93f", + "secret": "0e714095c59a33103bef37e30133aef81d3d82f75f0c7abe6a5d98ad723104d6", + "signature": "416a8378a5435b1b476106dc38ff5c26343dd29459fb676f93a1590c9ff3ef06e1d85d942972b1dd6e17f27c4ba95dbdf5bbc398c7c554f05d6e685dc23f9106" + }, + { + "msg": "dd09568afcb2ec0a88e7a952516911becf4422a9297ba5cfdc88717bcefe", + "secret": "f18e44c9c01042212bcab7db61da9a3275d7ca7b1c343cfc1e7c2b9229a3702c", + "signature": "e03dbdcd9933220f10e8ccd6ede6cff53637c61ed3373fc6a870ba67aa238dc29f46fa64dcc34a50a29791072a08b3f365f009e47bdb655d5ac9a35687098909" + }, + { + "msg": "6f3763fc14780acca724b12b029b47ae0a54e42a89688cf944b2fee0fb4797", + "secret": "2ffed2c14f50d89205fd15374e5669368c343d0753329b0e533cf4421c2342c7", + "signature": "1208903c2ef03a44fd1c79f7fb34262c1070d12260eb31323db4b9bb572d13b93670fbfc4737a63482fced399c6d8a82161ec564114507061a465a416de83185" + }, + { + "msg": "1ce6cadb4a3b52bb90239a81df908c0ae4a48da2a0bbe8e863b9f25b472c82dc", + "secret": "777500441cc9fff20e9f36bc1adcf0d6c83e649997f08ce25384e0fb0eba96c4", + "signature": "086d4215eca20c38c237464d890fee375f080d9cd2bc8076a19595ab6bee458466c4a2fb49fc9a3285ef788283ba9d2288037e7865000fc6f9bceeda2415ee82" + }, + { + "msg": "8bc5924293714c0133d590d6ce9f1bbdeedc2d0a791e91fe389a250f75f7d61166", + "secret": "016d72a0bde83cb185d9b4bd4dda94ffd8a64ead53d6e0362bdc660f4260bc50", + "signature": "0ba54ff3b8d2721cfdaf40c0f8d0ff3b61e95dd96a4f3765afdb0894143f21bd525f93f8dc048edd31594558082b08588d918120eab5c72d165d2f419a3fa302" + }, + { + "msg": "6f3cc5a91723ca14cb6b69c3190d55d8f61841dac3815c49974569dae5ad4a6545a0", + "secret": "989c8394b3eababfd1319d0fef27008b3fa2d22dd6a48b41154bf1fcf30e6949", + "signature": "966a60ec7ecf357244e1d30b57cddee8b05e1f5d9c48b50e49e1a854b95e58121c2f3a8607696d600a016fd717d229e5aac4e6e3a1c510f3bc43658af56d3787" + }, + { + "msg": "814622df86918939ad17c5ed2d1d3f0325431dba84dfbe478763313feaad3971c48eea", + "secret": "839f411389e825a098cf9e2e5c0c5a0c89140908397526f27e9a605e49dec9e4", + "signature": "1f8e7339620bddadcab8b1efc1a0af83c65c674df2605d5869fea00855108e34b824dc4bb886640fdeaa0e14774e5cf00a454c0c5eddf2ff8d3834cb92042106" + }, + { + "msg": "62fe25063ba5c01bb0eb6fee7654f231f9148c4b174b2164dd2c4e5c26a57e47289be2eb", + "secret": "98ce2dae6e0b610202c79e5673864b97cdff199280db8f0a5e7a5dd9b6985523", + "signature": "7d7cf3f6fd38a44ccf102f484cd7be66f867aa289e7a2afbb9e9311a90a615f9e3e3b55dae5b22e9dae661e4d9e8044e01844256598720788956e3e0a6a4aa8f" + }, + { + "msg": "b5a94b9265bcfa5e3409f4ce3cfecf9d2b0fbb8dc69fe06b340b1f62d22c72100d7091fab6", + "secret": "3c25ab3bb51d1264ff0f0ce097814b9730752f2f50d0db18bc63ba5ce34957ba", + "signature": "b839c1c1c60c3b6aeffbc4ed7d85f92296fcc1314a835647936eaab78a40f02653eb91ffed505249cf9a224fd8681b63f7d81257367a12544ad01368a2b4d90e" + }, + { + "msg": "d5d0ec861831a21bcce0fd21c6047046a41fe05cbc1f814dbd4a9619d7b0c3d234adccc49a01", + "secret": "b09c3eff741bc30e6926e9532c5e3a11e3f2cf74895c8ddad33dde741eb1a2c8", + "signature": "359f653df9d8d1b92f0c4d30c66e157273a8e8a40064085b5c3fa749cff88f4d9a68b5f902b4f668f924adf7579fa38a821220803a4095acffccfc2f55ad6a09" + }, + { + "msg": "0d61fbc9ef233786c4aaacb6c362fe3a87e8385c5c76e76e96ac5201dec0ae622e4fbaaf225744", + "secret": "e3a1b545b4012b76e895a55f74258a479fcf04e88df4d058cbe8b61dab1d1e54", + "signature": "7016a0eba6d8c138b3de51abefa75cde2a9761235137ddaa0faabd97f7e78b189fe2bd9b820b78152fe5b952359a417c926810b287b178f36648a1bbd5e81888" + }, + { + "msg": "c5477c6bfdba3feb7168853acf8823a1b5098a7f9dfcd403d1112612ddb9e8aaf7a99d390fdb16fd", + "secret": "746167c6147c97938efe87158d2e3824a25de8ab960a105c983c25a4012ded93", + "signature": "0fbf8c0f12f97e5a453194e2d3e280d188f5fffb191eb7da87249ae3103384b87873e50a5a6e4d47cfbeda3bdcf9a761a0bddb8eb543efe4bd31b28f5b89918f" + }, + { + "msg": "43d92eca24690e1c8d85b2b46dda5dd1e615bead8cbae4180b50304fd9a467ecf1c0e87cc7e42eca61", + "secret": "ec749814f3cde0e2e2f99889736861dfb4763624c210bf494212ff57b6daaa2c", + "signature": "9c1efe672b9dbe555b77cdeb688b232fa881e9ec8868b3e5a36eebe5871078158278375eb5e316cf50e08c0c25b16437b1a3aa32641432c388a7623d7b263508" + }, + { + "msg": "9a878845b6f681401eb92eba9de5063a39d4c2cb0c39e3c686880ce6cb2fd40eb73d53a31c4ceb6bb74b", + "secret": "784613c949005a766dac2513823f0f9ab72b15d2cb1c4071953788286765e9e2", + "signature": "a9ce22526480439100ee1761f35cb36b40f13cf5f87e38526a8d6564546b71451558225e768c6c002e72ac303e8ef2c37756e6c637a427913531f9c44fdb3a87" + }, + { + "msg": "845c5026a61159fa82728fb46ca029dfd53dbc972162f094d0f4eca25bc2c12d43ca86243bbb62d4714a55", + "secret": "6c9ccd55504dea411cd3a6c6302e2925c5cee9e42ff22a04a9f68860f9172044", + "signature": "761cd67ec494be4d488c9fc09417a5f731026c5f58333582bef35b03d4730b07898bf895ce63372a23cfffc6c0f42bc088e54fec7681ccdc9b6a79c30202ab02" + }, + { + "msg": "a28aa3e99881e8869dce7c70de94d477ae8b1e5a54f8eb9a31a6378f84aa7625cb54d9b6b30c2aaea242f6af", + "secret": "ef7c67749186c7cf0c80b2d4df8389c5067367c29b08f3b79b48e219861a3fce", + "signature": "da2b5a56b194fdfbc3f2adb215571c9d14c44118c91b237d44065aa32541facdd0ffc0eeedbc36bbf6d5182314895bd19adcbd0435c29287b22e0c68adf1e60a" + }, + { + "msg": "79834ca280aaf20ea16d87836cc82f3a98ac213598921f6a9fd4362c2ed81d853a6fc473387acebc143d2ee316", + "secret": "ebaf8d312b6121fdaaf82a58b25a11540235b85d8fcd1fc59af9bc01f656027c", + "signature": "280aa451ea8857feac53db24bad528b2ab05349e34b59cd8b56aaa2a2356f952fb0d175d51b58df96edfec5b6a9539f659fe9e104193bc727611d96c8b7fc48d" + }, + { + "msg": "b8cd45bda687ad49f9d102506b11adada0d6a989487ed9ac2fe12bf506c2a14b67e272cb75eb6850e2998eeb10c9", + "secret": "00f6f4a964eaf57a40639e250ebde475bb53329f2a90698660aca3cc0d88f963", + "signature": "b2c7334850ce5579ee6e56c86438f1f18e3a417e131378ba509c73f03c32e4f232a1fddf0fa229caba418e7a4993261f7e7823ac0c58bcbdb8c4082df22d2980" + }, + { + "msg": "d926a3298b3080d3f3b7e328b3e637937ba28c978597269c93e7501c672049b41ee31557c32a5ef80b41570db93b26", + "secret": "c6460d47b201411428355e89636b0fac96ea4d89830b70c79f65beef04c1b2f5", + "signature": "b4d72baa6d1f55bae90dd7dd376b16c664a7f5b08044e076ee5c63f34aef691b7251bd86bdbba6ec8083b2b0bae4569ebdcc30f6e00428cca8a7b5c4a1a9cb04" + }, + { + "msg": "730d3b4513ee24e886f7177b62a7c421deed0b39d17146985802e1906547b3f9a4a1020222d024551363f90b6e1119eb", + "secret": "b9dbd21bce5d28322ebe9e47566451f6d0c0eb40ee42fc148eb17b040beb05ae", + "signature": "6a3147bdf83f82745aeea9a793d0e6e31351f1129d5f4b7cb2536850770c4afb80d3a37d43ed5f60608965d9947a3900f1f2b824cc2990378dac773a8d8fbe06" + }, + { + "msg": "75ffa0b27cd5b739fdd977bbd4d996eeac3398e57461ef844d89a8d07e4e300e40386a873516ae0a7d70031a22463a325b", + "secret": "9f267d7bf9d94f20dc3c23e4dff5384293ad3f8da71be821c99f284403237c0a", + "signature": "88abf01016e2075ba91624ab2d88bd3ff537cea3d6b212b37ed51abdb70c84670a28a886b3c6a628c21b3ab4a104cf96638594f8ebb22bf2768f83eef47edb8e" + }, + { + "msg": "", + "secret": "93d472d99b2aceba55ca5fa26237c67ce5d7bcf1935755ec8e48d164f7d85dfc", + "signature": "d18b46368ae5dd75e4c4e0e2383dda980ce5510f7f7f4e66b0ddf33f00091992d39fc4a0b91f80357123b5b789bc284a2fce0b09b9c2c54f321c4c0267626a00" + }, + { + "msg": "78", + "secret": "88a038f82111b66e84f622aa39498e0287d86bdc42e7ae1e5ec2ef9aabe6d536", + "signature": "404be946f2512839a739e42c415817d243379788bdfebf00d0a6bff8b21ebb80e95ea4f049f379912af10e4bcc83e581c3d36fb269423e18a6d4582d6eb6bf0d" + }, + { + "msg": "fe49", + "secret": "f9f034faed81cd372ea36ed019d5361df6c3e1c2cbf120ef7320e67eb7959f1a", + "signature": "693e12e87ea244059fd5f92ff6627c007f50f42465a212bff4b7f8c73e7c44dc673e3c553e175d7289b1239694584e2af0b354d3a2c5768d952a91f32e847b02" + }, + { + "msg": "1a3c63", + "secret": "ab3e872591009794a5e4fc8ba2813aec2fdb3b8e737285022ca92fb4fc941339", + "signature": "8f2ff78512f827dd4a0412f9a2aeb5f59075696cada4ca2728e4eef71d47afa539135ffd04a35b4a979b43a02ff094976ddf71e03a064c40a02d7b8d70e84508" + }, + { + "msg": "8d1e3da5", + "secret": "9f90e4bcb6d8ed5eb6cb8a6d21244d7d175dfce2a3757be6d86ac627084b6ab3", + "signature": "2e6ea3eec80c8df51ef85cb82677496ea2a9036b7e6f65b74148f3e5fa3f2bd7ef0d5b8e5a8c092a7ee2a103bdfae1d2e90866d6c5eba4fb641cf90d6cd5e109" + }, + { + "msg": "42ce78ede0", + "secret": "6698f52d0d1f74f2c0d022defefdf46697471c1ccbfd9b7a5c9506d51e1c6323", + "signature": "68d9139586d5ceaa87c1f1b4aebc6c1b9a4b763b3348fb6a4205926d0bbb8f434348c7c502a3d0bc499a55b66cc73072400b86246ee6fec13c7148231bef4b0e" + }, + { + "msg": "df46e7aca8ea", + "secret": "803e0b485b471e147bc554196d9d8ad448f049bc92beb804611be25aa7e0ac9e", + "signature": "a314474d60781aedc84680367219ac1e4a2bed05565f25de4ba59029efb70ed50e474d44ec5edcd9feb177744a976a5f64a3830b98e91c9775815c2d5be41e0d" + }, + { + "msg": "2869aedc718582", + "secret": "82d010e3d67d5ea6741e1dd211b76b4d6682ada52195119d3d4dc65740244001", + "signature": "fcc41e61a5dae75de9d5e9840b6e244f61af012abba11707b66410bd0826d5f5faa1467f3ecdb6b589a9319d73ab4f2a15d69b740b6e76aa3260b8cdd1eb170c" + }, + { + "msg": "adb7514055ade786", + "secret": "13064c051e0f4c9dd939da9ef839a707e8b0d8605013c7fea9298140b200536a", + "signature": "370dab4d75514d32a08c5c274ca06d32d0d856fb49aacb7382f3ea51bf72780a669463a8c79683434ba1d13c236ebdac0454058c77b2d577383c6501456b3283" + }, + { + "msg": "6c9ca3edf6eecc2627", + "secret": "dad999c83196d22a719028d5608c9c1cf2800050ccd48303742692b18bfa70df", + "signature": "5dd8aa0b6c77d7f58cdbc58fea6a8b4235d813b280ed3337adf5c2e6ec9b094e98d6c025843a2bafa973bffdd36cd63f6b6282cafc82f62e1b0bcc0c49032801" + }, + { + "msg": "b6f6543206f2e093bae6", + "secret": "d53fee096c1324b79b5e105f3d97fcf596bc4cd7ee6187f168b34dddeae266b6", + "signature": "e425e51799cd5b51476589651c95efca931f6e1fddd6ecf72474e3e291a9643ea959dcc3d4ce2e4c1a14f031704c72c23febe2837a1f121c21b9d6f16c9b7680" + }, + { + "msg": "4b95bfc2838c42d54a9f85", + "secret": "450daa8981088052c00470191ee2ee71541aaa9ebef00ac6ed0f67b62ccec0ea", + "signature": "f7a21a44a10addbdfda17f7fc5d7438a1ed7518211f72155118071a8e6992be86b5778860e7d1c1932bacb4b09deebcd6b7ff40800002a78f73a2578a72d1c07" + }, + { + "msg": "f5a988f5f4d062d96d841cee", + "secret": "e7e6a7e9b432550734bb92119ae4d1c3a2bc91e972521918ba16a28be7b8bc2d", + "signature": "6bf703633ce3bf50f085c061d2d5e5c70649da96ec8db036b63c83d1f2fcf5a5ac0a9773339d98d38626a7c8aed1f4657a1def45fb5c23224977486bb9eac10a" + }, + { + "msg": "f8fddb5ae4ace4fa510f882742", + "secret": "0213da3a2c17517becea5228ab181b43b5d6667b1ac97c41d6269326000e37e5", + "signature": "555da045b36f052ace2e12b5b77c4579f7f28b53b8cea0bab6b0975b4ebf7ba28d0c48c43c6f840c3e704623c671d77eefb8cd6e40d39745bd6b88d1939a8784" + }, + { + "msg": "3da6296ba0db5ff7f0800008395f", + "secret": "587931cc89b2b996164f683c0b7f7a21cc5a32632f1b02c88b5ca29246a97b50", + "signature": "e08848223ee675332cf8c072f831b9cf53df2b36f158ede9cc8a4dd1a3649c34baad73469e088077412fa6083fc72b874c8cedad3ab88117e1d4d5dd73d65682" + }, + { + "msg": "11b45e5f224fc1c7cb77ff9a2f2487", + "secret": "b7fe750dd550cd29b59a9b08a3004368f1988c837d19ac3bfeb98882dd535d20", + "signature": "bdaa348163e3acb8b4ced44cc3ac93e9ce928efe8bf7bb76d624d084596e5777c5e2c02e481ba4e71f0141da5b200e97ef08674b183df46c6796000cf43cc689" + }, + { + "msg": "0e6cb07324cb4a5808c172dc147b805c", + "secret": "a924fdb5ed177c9ff0f36b88824a41ef080895c84b1d077db00f1a27acd2b55d", + "signature": "5e2f8b528bb1827fe6367ac8969e56a7764bcb806db90a7fe2afc793d779ff6fd35ca6b49da48cee12e4e6b82bd36ab9d7d960450da7f4f35fa62fbfbc046189" + }, + { + "msg": "6a88459dfbe4089b9a4437f129df94ad5e", + "secret": "6ca34d1b35766d2b98eebcbf1402fd58e2f0c34921ce187ef9140a1146a10be6", + "signature": "3d11ba3ff09322083d966e2fc5e2e4e129545937323d429b50c45945c3065aaa1a1054bb770a5e82afe05715a08363fe4c47c3a5ca9541a5246c2280593d3208" + }, + { + "msg": "b513688bd4fb3e9279ed44d42b210643a655", + "secret": "03acf149c034f4ac9dc79ca8b171cc894abf2fb0bf0cbc1f13278d3c3d89c68c", + "signature": "eec2ccc354befbd918bbad60cb59678b38ad65fb2a56f352e32e7202bec3534c91e3702a7e5449e17a00fa0c1f36c2a816528c323da5e7ef511f5a45b965698f" + }, + { + "msg": "573d8769d60e4edba5947ac8df58abe190a2df", + "secret": "5565855a66ceae7ed93603ed5b6857cff5bff04caa3e73d1ece0fb9ea9627572", + "signature": "fbf8fcd09775c798cd628264369786202de8a1f6c82bfdda9c09ba867889f00ba68daeeab4699c2ec9af2cfb2e2b0970c7130f675f58e97bf4db4e2cc7716881" + }, + { + "msg": "bd90233d25d8adee6a2e5a75e3028adc3782d770", + "secret": "86a1022129b57c42b2653b5616a3a78afe05bc2ae7bd0cf60362d188bf911575", + "signature": "6f8cc6f6f6250572c26a15992e4e72bf7b906ce631077f8c6e54974330cf52b09444cd5f6382e536f93a8751ddae4ddf9bc639cc47ebad657b8e7ef558ba1700" + }, + { + "msg": "cfbede7797f686e21c1f9ee4b453639ccb3db02c60", + "secret": "45b15be6d6bc92170716ccad732e509ceef9ed52ca72ac43b38c0c0d77657fed", + "signature": "53772d89f7b17fbdbd46cb3d610a09955e30605478fe967fdde3233a84fc0df39af9823045e539f117a06713fda742865e606324a0f219b288678bb33a2b1e0e" + }, + { + "msg": "d659051d35e1fa886da17188dacfe134c8fd4986bf12", + "secret": "e3a49f9b9b7587a36987fd8ff25d7ee184bcf68909ea2c9e8467cfb0d1d83433", + "signature": "d7b80c75738abe2c1eb31d8450ac80841031c2bed70be6e7bc276b3e707f71cc2bcb84e46f1094fba4a38310cafe2acd02e5e4c576ecf7ed518f2022eb9b8708" + }, + { + "msg": "77fdb902fad9736b2f1c0a9470e6270c1b7b80c47fa7db", + "secret": "cbf6c8494e02b93d33802901756558db7a33fd5575c9e95ed64ca027387ac161", + "signature": "266f051f9c48dbf07d5115c510ac2a13acd3222c9a9d148be28a3d1e4f82bae01f73ea68765e7db1421407c8794f99da672b16bb339a65b8c53dc444d271dc06" + }, + { + "msg": "798719340e6c9959be35ceb0f38ee8a81c971f214a41c4e3", + "secret": "2f033bb8b72c5e016e01c74623d6500f2d2e85ba7f4c07e684725e319fa82075", + "signature": "dc6458aefa87a454c94f250c360af1056cbccbd7ee29aa1c826cf7d9503eb1e9db4e886210c8c29b9962f791b9ca5f2f7559463c61a2e177a19d9d801c5c690c" + }, + { + "msg": "ba0181e83fb8573506e337a21bbed3d94858e8f8bcdcee6178", + "secret": "22ae097e5e8ef789f35191a8c9610f12f3ebfb140f988566e965d1bda1ecb5e8", + "signature": "ba0c0fea15e5583bf180f81410c82eaa03bb043b5543f6d57d66f3970bb5c7a2cf12db353bd696327b35121c788d9c0ba4dcab0278570ac37e15e88aa2d94204" + }, + { + "msg": "3ae814b6d430008d9d11aff370c8aba4e2965fc27a7b86db62fc", + "secret": "f454b23530a1fd27ee768f39b1c9c2a3126dfc15ad21395177e71905863efc85", + "signature": "bc0dbe63e5cb5425ae18a7332ee43193bc93db9c26868fa945e326c3598dcfc16556679237e653e39e335c1cf341b23466fd5637da230332e57f0162766a040b" + }, + { + "msg": "e7e84bd583f45c9dff328197d11b9d2dec5069e9fa1226815b72e5", + "secret": "28598dca5598e9c56eb9ca7a26e6c4034fe272057410785373d77e4bfe0b676c", + "signature": "1c19b4d6ae6d94b6fb3ec58c97bd15cea3d54c21958699107d5e8b208f5ecb16f9d9f0b692ddef7d31c2ce396cffe4b48845cd3263b77ecd42b9a162c5fc078e" + }, + { + "msg": "896d6f52e871962d5d27f579c5dd42df247fba2806a1de895098a9bb", + "secret": "1a803890243264e8da5613bf7ed9c17f7631a312ac2593585269c0c4e598977b", + "signature": "de9cc5224415f045d52376a6d04f43fbaf4dbe1fa60919ce7f950f98bc929ae93812db7a80673f29d4ce9220d81b55fb8cce5e57ed4b059b20ca28bd12a0e18d" + }, + { + "msg": "add25a3ec36f9d8db4d7f3ebd1e23622f2c3db922863b779dfa92c5c32", + "secret": "eff034aabd45f1e665aead8b4f4942dd7297735c328e684e67e4c79d3dc717b8", + "signature": "b769f8a8edc7bcfb2ef4677f940e00e73489df5094f7066569eeb9101288cfb315917dbdb2bd03a20380f92db18110e01e3c31129e1fc159444f3cf6d34b9b05" + }, + { + "msg": "8d1a6740e3c104dccf6d7424fa62ce481229786f3f3f795a43606f915236", + "secret": "13e2cc5a0fb17bc93272ae7c24d8e3a4ea79f9d2a28b4481bd868558133d6641", + "signature": "805e2cbf2bc32bc80e8f1c3cd6bb618fbe4539ed7094f7eb070161d0bff2e2eeb81393ec8e1e4581d1d214b5f5043d99745c66feb09f9b76c46512ef8908450f" + }, + { + "msg": "820521fa473f8ee6ce557acfb13cfcfeb9e243c08ddafb69ed1ffb58248353", + "secret": "81888cb69c310d23160ba5a974cef269913d6b98d0854384b50f9e8e35d6155f", + "signature": "251f5bded93817f412e0faecc57fc2074a48e44c96840e0f978440c2fb7795fafb3ce6520a74052bf5f825ddaa8e2d8b18125ac4c5b028e9572569758947810a" + }, + { + "msg": "1311f85113b83a220ee436383532405b991711f71796cffd34d247a2648862cd", + "secret": "2a1c74345b43fea7a72d115820d30afeb831ac742d4c02dfe1f12b8a73fd7579", + "signature": "ca4c6055b781676016283b4daa13576a1a13f784cafb1d4f1f0c97228ca2906e3ecdd7f0ca3a80ce71563c50474f9e997059006b934dc7c231018ec5f7b7c781" + }, + { + "msg": "f30946d8f8bdf81d72fe263e1b86d9e3b7d7be93d6e7f78d596ea3a0dd18609e34", + "secret": "cc61aa0f0803b89f18c63a43a7d70310916109a66fcd30702ff4fc4acb845571", + "signature": "02a5adc8e122c6896ae3969dc6d3f6387805ab8502989f7b68dd69f477767fdecc2c6ed493a52814d44d967fb78fbe58744e3217680fb4cb1f6e5dddb3c0900b" + }, + { + "msg": "38d5272ddeaf42b3b9862cc2651ee009cf1536996d96a3156455f6a9de3a0c841ea2", + "secret": "2d8eccd1c5d931f64bfe14dfd48afdb869f475a7c69cb43e420e5c7560836c12", + "signature": "e83dec5e15c8f6eccf7c3a906de5535d5869d4710882537be12936194028607c518181a846935574d2ed89bf536e76878e1bd7803dced59e7f590495d7f91703" + }, + { + "msg": "05829e42135396ec56f370fb561ea3bc87385751421a68577a66c6081a0c4f93a8f331", + "secret": "c9beaa57cd3576949610f82873515943c9e7861ea211bb41c9b6b9055d62c79e", + "signature": "618fb9c6622d337df86173cbc3dbead7deee58f7f78768936e5a74ecd43b627e2ac3c94dc02a0a652016364743bbdf95c078d83895e89e6f13ff0f6c44095289" + }, + { + "msg": "9a1b96b3b5eb5072f4ff5e7a424a50b31aa15103e4d068d90cf390c84236771b1ca5a989", + "secret": "69a74e3e7fc01807a1aead00c83ca88b2a8dc549411f015c6f1686dcda55d4cb", + "signature": "b4e6c80fe103ff33deacdfe9b04cb49c203b1b458e4aa344d9ab48b23e88601826a4010ebf52d679c662f95e9076ef87e24b8aff4c80f86dfc4559a1f292cf86" + }, + { + "msg": "33531cd40fc2eb6f5d9f5948e524e503cccdc013017b306d9bae525e2a10b1eb39e4c12a31", + "secret": "8c929ddcd9037fdbf9da1f0ec105b4a293116a899a78b25d1cde601465cbb9d8", + "signature": "a3701b65d56ae44a3c0a05f33d40518c3524439740da196671cadc5eccd15a85dc5f51373cf62ba7856b16def0dc0d550afc65a4803ed79da492a848f8d2a58e" + }, + { + "msg": "6e8f2e937a8f1b7b59cbfb962385c632e0e8870e5751a8842e55deb107a59cd89a33e9e35c18", + "secret": "3f60a93f820def17699629de09cb056f2c410c471f6e224c973c158611c7d50c", + "signature": "8dbe4130d2f6202e14e0289cadfef9f0ea5ed69da8b8a111c25e8b3824f00958fefdbdc5097fc9021e62cb1f958023c58bf5945502b5928b7f2627cd3b16a186" + }, + { + "msg": "2f38d1c507897026aaa08a640517f1259ea462388695a6a3875c1c4f0c1c11b7331ae7df235cc9", + "secret": "e0c0abf8d46726b67dff6e39dde8d2431227f1720a16ef9a86c73962f00122b8", + "signature": "d60ed0e03970d3d679daf7bed525518888243b611e25cfd15a8926250eb80cb8160f706da67aff6c5ea86cae446cfb7c5c9154cc4ea53176dc043419c940e98c" + }, + { + "msg": "113149bb8044b34e7864d73fa66c9693f95915514f4caaedf65c5c9578afba36e60937575fe2fc70", + "secret": "d8cd3640f6e81424038b3fde083fb68aba0c29671c33a7861233a3c2121084d8", + "signature": "c7fe83d54d915acc2db7f2824c9ff68bf8a708c926d5d6f6840639af7f658ef14ad21f7fc8a0f83571308e377043583bce6f57ec37bda65886ad21df7b908088" + }, + { + "msg": "752d42cc7a5e3a8f5e62aef8082962c97c6c092c982777ad2ae3c5cd3f4bfa873a263f81a7686cadd7", + "secret": "7581fde1314d40147ef808aebfa59b05c8534afdc3483a41328c2e5c90a0f0ab", + "signature": "833cfacf30a15b63a63b5ff5c4e80c68a106b2aab1389d02b3865e409ae151f29977d7bb02b0c05c7e6b16d0e795d015972d752b41f5482747bde1655f87d181" + }, + { + "msg": "4369e52ec32e945cceb12456e9bbdb4e862896dd458d7e600955a30748362e035cdab0ca8c3225d5955c", + "secret": "65ae26607c65e865d3486fdfa14251b0382d1296ac1d0101b9fc7171c93f7c88", + "signature": "e7171c9b6499ad9ae0257462948dcd71ac689b498cccad83be5a8e5f557df2a21a4008370accedfd4331c2746116bdba6e7f5fce889970b211f378e468a8f987" + }, + { + "msg": "1868b4dd2ecff80a8dd77ac7a6993298eda5041cb697cb010e787b865f83c55cf08ebbcc9b28da57778ebc", + "secret": "0884e3bc726ca620a9134f02cbeeb192aa46c2e87d1711be7a9f12c369be0bb3", + "signature": "f01309b50fc5d14413781ca140f0e6809db6aaced860aae18030bdb6d59fbf3a283ccaa498ceab3b8532543a670763a436da8740e2d4bbe2375f55a3a07a4783" + }, + { + "msg": "05364385e2571c470384048cff6dc3221c2e32070afda8ef0d8671dd7639ed7ffd13faae0b78520e1c486a41", + "secret": "28b5dcb3778a97e1932641c0db2840f320de3331b336843cc9b0a7d0847158ad", + "signature": "72b76239a28fcabeb1478defa972deebea2c95df0e6f6e5091b5bf768d7cf51742993d2ee188c50dc5a3e2dc3eeba2034fdd84c20b4d514d5444cdbc4b7ef08e" + }, + { + "msg": "304fa9879bd94a4c92f4e6e03f8406b2876c0dc73b8f48f9cad5c3d66689d5771fe02dfd1172d14e1067085452", + "secret": "015d006506e5f57e91aee8f9ee5f499c37857c5a240e29852ac55c51d02a4979", + "signature": "0725920864233ffe0abb072fc8a34f5a869137b0789e53d0832f773af57b3774b1b1e39a5d61fdf7fe74c64d300b557ca8b034ae083e7a64ef6cdd30b65f4388" + }, + { + "msg": "7ebbe4e55f8c0410709e3285a92ae21d272ca2189e9b1030b5e7f044dd547f40b0c3424c4c0567708d22d4ad471c", + "secret": "987d88133846c3ba8b5839f81c2364917cba01a156c27a75efbbb18d2b038410", + "signature": "675b0a86f65073c85e733c78a630207e9a5819b286e156a721e59db878ea270579359b53f3332ee0e7c86538c9f1313dfb597ca2cff89e0a6941e1056d58a408" + }, + { + "msg": "1f77aec89a588dbbbf432622f1c4144685b41350ad7f1c116133264ccd69d5108cb8d51a554cb97bb269c4ee7ec3d3", + "secret": "fd75b28318a1f3038964cedd65d8abad03422aea7c8035cd818ba2ced4804a55", + "signature": "4afa0ab8963108a504e2da46f3f93d3ec7e818762a73b889669b4a98c24b3a8769200e59f75b1ca5b080b5d62d5b78dbb1955a9e5292101f1ba41919ea134487" + }, + { + "msg": "fece86473bfa325ce4c51ee2abcad77d385adf304120a34ee9fa6ca862854ea1920668f90bdcc92b387722df228935de", + "secret": "ffa2490743f42aa3e78f54b1518054182a1ed7d40e0eb165f0cc6c9031fa5e8e", + "signature": "29c94c4541eee66abcc4f857f22f2125ae4a634abf7da2a4adabc14b6b4a1872101e995bc6213cfa975e53b25ba2ffa16a65ef082abb58bce3d06c502e9b3705" + }, + { + "msg": "ce83e2e14ae7be44d3f601ed95118e1781fc991cd62541af4d6e587eb5ba04a4f8e32b22b3fef512fc4edf94793a641e80", + "secret": "f41a890c27142928ec1f3ba0ef6010a2dbff4affefa3e3379ad477daefd6e075", + "signature": "10b1a604f7d954fa2516969857f22b32f17c713ec596f23d89beb2e1fa32af98cc7f7757ee443fb0477986cfaf3353492d410cc4879f1f21daba08a61747c909" + } +] diff --git a/rust/tw_keypair/tests/ed25519_waves_tests.rs b/rust/tw_keypair/tests/ed25519_waves_tests.rs new file mode 100644 index 00000000000..18a1df852e0 --- /dev/null +++ b/rust/tw_keypair/tests/ed25519_waves_tests.rs @@ -0,0 +1,62 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; +use tw_encoding::hex; +use tw_hash::{H256, H512}; +use tw_keypair::ed25519::waves::KeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use tw_misc::traits::ToBytesZeroizing; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const ED25519_WAVES_SIGN: &str = include_str!("ed25519_waves_sign.json"); +const ED25519_WAVES_PRIV_TO_PUB: &str = include_str!("ed25519_waves_priv_to_pub.json"); + +#[derive(Deserialize)] +struct Ed255191WavesSignTest { + secret: H256, + msg: String, + signature: H512, +} + +#[derive(Deserialize)] +struct Ed255191WavesPrivToPubTest { + secret: H256, + public: H256, +} + +#[test] +fn test_ed25519_waves_sign() { + let tests: Vec = serde_json::from_str(ED25519_WAVES_SIGN).unwrap(); + for test in tests.into_iter() { + let msg = hex::decode(&test.msg).unwrap(); + + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + let actual = keypair.sign(msg.clone()).unwrap(); + assert_eq!(actual.to_bytes(), test.signature); + + assert!(keypair.verify(actual, msg)); + } +} + +#[test] +fn test_ed25519_waves_priv_to_pub() { + let tests: Vec = + serde_json::from_str(ED25519_WAVES_PRIV_TO_PUB).unwrap(); + + for test in tests.into_iter() { + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + + let private = keypair.private(); + assert_eq!( + private.to_zeroizing_vec().as_slice(), + test.secret.as_slice() + ); + + let public = keypair.public(); + assert_eq!(public.to_bytes(), test.public); + } +} diff --git a/rust/tw_keypair/tests/nist256p1_priv_to_pub_compressed.json b/rust/tw_keypair/tests/nist256p1_priv_to_pub_compressed.json new file mode 100644 index 00000000000..ce6a20f605e --- /dev/null +++ b/rust/tw_keypair/tests/nist256p1_priv_to_pub_compressed.json @@ -0,0 +1,2002 @@ +[ + { + "public": "02dfe31ef16b7c65b47d44a8c0c422d9ed9ffc599fbd529f85e931a90117d4c84f", + "secret": "ed9ef57bb1a39863e79561d35e479297275eae7d400d9a296abe8699a8ba237e" + }, + { + "public": "03703d9bbad179672176410d68abb2705632d114d2377b6821858a6a802913d129", + "secret": "bca2a4e7db34577e1193d6b6312244a246832228598c91fd5123cba52c182979" + }, + { + "public": "0253b7183b79ee74cb498c0153d3cd942bd17d4993c388ad7eba57de94d4b896ed", + "secret": "03750b5633bc1886819aa9674dab0cb86fc62795af2661015e1fbe4a60b3995b" + }, + { + "public": "03b08e7f118a904b5818aa0b29abe6b447fa04733904d4d234a0734c867489afb4", + "secret": "00919591227121d8a75f49ca5a0884b0e98967314d8e56e35bc4c5ff1a20b543" + }, + { + "public": "030d761d0225b983e4a0de15253bacc9f7c02ba8b549fa96aede56d4e32c2b8987", + "secret": "d9051640e11607b11e2f4d059afec950153db7acea11f1200e698b00de49e1c9" + }, + { + "public": "039fb39dbd7de59971fa111273d5dd4911aedd3e3f122745144d1c39b50911207f", + "secret": "c58ff00d1749c72c6743f87be3714a1703a1217c8f702c2e460b8f0bda9552f4" + }, + { + "public": "03aa64cc234b97e17bb873bf9644d1af0ce4a36922a2cff1feb4195300f9d79be2", + "secret": "7ed09f82a1fe14dabadf0f6a623cbcf461c9f51cc689e634dd2c950c484f9bb7" + }, + { + "public": "02baca30ff26df6dda26cf9b6fef037824f27ee4f1c8bf2226bb50b18b981d3f0a", + "secret": "9043781fd8a9c17ea7b3c7ae8546c9efe9f089f1c7af0f0318c95f6bcf4cb575" + }, + { + "public": "02f155c59d49f0ae4db7efa61ee473c8a024377f7c44e6f78b71b86a277c487ab6", + "secret": "d39c2cbde0a4ae92d066854372969236963e08b27eec3ccb4f08700c04949506" + }, + { + "public": "03b68926809d4326ac459161f3d84ab5480c8dd3fba007d90c45629e5794eb4e27", + "secret": "a40185b5e0ec6da301cfbad30c418635269017e8d2c839d9a415d15bafd48ae2" + }, + { + "public": "02f4939235dfc9f8b162c310b74ac6367419441dfe3e567a9a9aa4c52417d259ab", + "secret": "c20e4020f41393638c707e6aa09c059fc40b74bdec9553dfd011f9bda4b3d000" + }, + { + "public": "0298595e656df719bec30828bf9f4a0ad516cf85ab6d8bfc8ae13ee17f5eed4d29", + "secret": "7436540a692de6ef33de2a6d7801176e4cad20c4da8149a28b32f4c375be60e7" + }, + { + "public": "036e3a2be65b0a5d05939fa79d582f456d0861c4a2f31a28780debc827f23ac991", + "secret": "e23f18768a48dac770da04915fa328245b798fa93a26c2d8f219974a6c9af83c" + }, + { + "public": "02a987cf9f5ef51d3cd68b43f0828938523f04fb27cd7131095db2cf95f5afceea", + "secret": "681c83f84f3da5f2e080ad0f5e551ee208a45d4e21189be6545e3d27931b52cc" + }, + { + "public": "03f87fd94afcd1c25db431758b0c5e5e07767ca5137db16e608723f2614bc1ba3d", + "secret": "3c275be6f363a2f38aceaa07ebe69b8fc16374981200e2c6f5fcd674fb69e584" + }, + { + "public": "036af4fd44051fc437d7c294d087a54430661c9ef22788fbcfa3d0cce9f635a4a5", + "secret": "3ad3b8e25774f0603982a6000b6f635e3a0927abd213c0999377e3727a469552" + }, + { + "public": "02ee2c687ec9786e41ee84f3439ca867ffecfc3a8449e114ce760f5b8b18d9b506", + "secret": "60c7d4d27fc471df89094a8b46e7681a902414fa54c808a96b58de313168d4d6" + }, + { + "public": "02daef6ab84ece15eee8fa39c7f089634411f1f5b79314271277a5866e237fa4ab", + "secret": "644ff7e768fd03063cb695c524449136aae637664dab255b094fcddf7a9a9d2c" + }, + { + "public": "022c423f0bfc4302d4cf964d22247b98bae63b014e9d505a1d3c6ecc37aec1371d", + "secret": "e857fc8dad9db8022e22415be4e811d35dcfdcf733ef448f0e076f3b9331a4a8" + }, + { + "public": "03779d2844b016f5e28e821a081671573888f2bf070d4eb3c4b254ad586fe5605a", + "secret": "95345497f847940a04d4a8a08a6d1bbd42cb87099b03b436e2fde1f0aa9b9d4a" + }, + { + "public": "0313b5b6c9e03cbe397c5d5d4cf3b4d7f03fcf06fae50fd801b33724575bd19f26", + "secret": "43e7c2a816884051a5fd9d39afa859d4579be1351303dbc2b82c8d0542b635b3" + }, + { + "public": "02547dd5270658bc9cccd19ee7c92f82da7e8b778e7c96a254983c8fbdce2f2335", + "secret": "c9c714d0a63b624476de4d6c6bb92e003f2abcbf2098827528e1bea27fb6113e" + }, + { + "public": "030597f69c4cdcaeb1bcd690f3b957a3f2e4ea3c195bfbe125eec823907bedd2d2", + "secret": "b9b4efb24c1a10ede058b48d417d64e9f893f11ac18acda86a1b604989eecc07" + }, + { + "public": "024c2f40d3765e64a77f17b46a8354bb993a669f3b91c5aea6b01775b1391e9a62", + "secret": "99d4f6f04baf38fff772e9dc9d1cd1457693173d9dd8a242585f8fa7f6c106cc" + }, + { + "public": "03842e61840c044411f3324e06df9956e7c677b07fbfbabe803a93bacd21656a8a", + "secret": "b71e4edd221472a4afe53b934059068547d6e3cb96e8071a554eea8e497fdcad" + }, + { + "public": "03e935514ef71e3226172656c4cdf1c2a99e2299b526b58dddfbc4048b6ce7eea1", + "secret": "46f1bb6607be17d89cf1a8774e4c09cee6cf494d97c960c61c5c3ba576f0f959" + }, + { + "public": "037b56bd753c393bcef053779a632fdb8e95245ce4788ffa9479a272ad0d2cb27e", + "secret": "0b56dfb1a4debb0cca33a0702e19caa26d88f2d5203d52daaa495a39938558be" + }, + { + "public": "02f6bbf29c2b1734dc270fb9ec43e34daee7530c56ef3caa291c842fdeeecee872", + "secret": "d27fd3993f6d7cff9ecc2323c38c80b93b29bdb08836dd34d6e4428b66f765b7" + }, + { + "public": "024d245efeb280b0f2ab3bb6f0878cb2226504e20bea37d2b875bf83b1b8a4742a", + "secret": "976d82f71b8675f86833f12ae83160dd37c65d70a4e5b3583a20259a55651a43" + }, + { + "public": "032096dd59a1b8365ab21936fda650f176cfa2281c6cf6d42b9930cbc33aab5e3d", + "secret": "c8a6312e153e33dc37df121a7c3043804cc23b16b1d1e540fc12553da1b25cbe" + }, + { + "public": "0221b5a1c77eb33f09501bcbadfe1b39a05e91dae038fe1c4e765c855774157f0d", + "secret": "35f2a1f97979f44e1fd4115afcdc5aa9e8c54ea23b65a73c663d1cc4e79d329b" + }, + { + "public": "0339d7a0d439d97443f7ac4fce88c78eb199531d1eeb2a3e5ce7553d3493d14c36", + "secret": "ab5a4ec44f2e97cbe76a2b54d50aff74a966d1c1b57fb174a8fe3867057a7240" + }, + { + "public": "02c96a729995a923b71f2032e2d6b09a6e5922f66eba382f0f77a84395c97ae178", + "secret": "3594b104b848ea43a1d95212b628d7f0eaa71f1e3ec18840925070c46b62c38f" + }, + { + "public": "02084a9eb9c0583f2770495ad8105498a1f22630c13f5449d3fc673ad0ba4df7fa", + "secret": "90901f1beb45fb08c3ce512171654538d39a9f4c56ae8fc684396a37885e896d" + }, + { + "public": "03164166be41b4f5fec85343ae44d45ae6c73468438a4dce920729737f19e1b405", + "secret": "b5aa79625a305aa340b9fedf61118bd22ac02ed5aae4c6261cabdb2abe1524a9" + }, + { + "public": "035817652447c4ed2e32ac28a53417bb5955a9cc3321d29621e69d7625c27b3c76", + "secret": "b0b9625228fb399e63e0f73cec09b0878a78d6493a3d50d43419e61beb1dd2c7" + }, + { + "public": "03c31f7f7d2bb8565ec9902b8183b0799c0731ae2e2c73f6ce583cecfee385ce14", + "secret": "ed910281283f7c03e92ec473fd1d98f675356770c536bf83a5dde4fdc8c7808c" + }, + { + "public": "029317038ee7a69ebafd89858b28fdfe84744be1231c42f3f4c41d82332849de8e", + "secret": "50b123abcae69a6fdc679bdddfb75bfa2a6ae45113e122b0a7cd5d7769fcd92a" + }, + { + "public": "03449d4ccc49174c5f041831472beb25241abd743e41e91a01b777d062aaf16f16", + "secret": "07a3e1d1915bc1a88edd115419cf31ecb0f6d2826615914b1c464dd1769e0a0d" + }, + { + "public": "03d2f0bc54a57c7a5e899b44fafc5c5098d351726dfcce089c64489d78e6b64ffe", + "secret": "697429f1456aa8c74332bb4467d7398c56a8ea92a0f409cf9936ef9ff35c36b0" + }, + { + "public": "03769d8d209ec35edb8a327794215c1498274ad1963f0bd9f0eaa46835d2b5df77", + "secret": "a44cb1a2908b50ce1603da32392fff8e4f6f94e24780ad8b6e90c37087b241bf" + }, + { + "public": "030b267cefb25d786122f2e9ca071da568ca1daddd6832f39d592b944820110d41", + "secret": "3a3734213d099f8aa4ba322c49a0db7c604463047a1889551042c5da291a49a3" + }, + { + "public": "021491a530631f7d24b7434b0e71e374c481f930933ec99415b9b5509d64046052", + "secret": "d89064d6ed42b9294b32560b30fa4b47f740462b38e4bc5da291cc417e19d4a8" + }, + { + "public": "02918de11ad3e4f51dfaf7220b24d3bd09cf7105db2deccade0bcbef96af8d7753", + "secret": "58e579bcd491f146ce9c1d6d15531ed43b2ecc908fab8aeebeba3fba00817966" + }, + { + "public": "02f8bf8d4181695429bb35849cea493e07f52a934d57935483ff40fa56f70a3b27", + "secret": "4b45e5aea80f230f7fb298914ffd285431eed8717074e02180aab7665eb4c9c2" + }, + { + "public": "03811a7e3f86aa62a10e4b0ea1225fae4885fe2b25b71339d6394a6356e83db9cd", + "secret": "ce13491d2a572c39f12b33bfcc904cc3d5ae8b2df7aa2addee560817e1e7217e" + }, + { + "public": "02d645773a478c354a13444cea71a16c9b2778e190862d76864bb297a44174d648", + "secret": "6d740b071d56a09618585fc43ebc7d331a7f10fb662a3980f6ce864c6f8c587f" + }, + { + "public": "02391f171c2f7018a9b80d3795330f42cfb66536ff17e116793b36450291851428", + "secret": "a1d409604d77dd84288f21233a523f8e07a31762b9a862d08f2ec1f993d9a281" + }, + { + "public": "02071ec493e87bad56da4d8cc5a009b1f8757dd2474abe6c0128c3089e436aeb15", + "secret": "3b90a2596117d7f805f640a8deb23b739ac04f20885f6a8b0112e0db6c3a13cb" + }, + { + "public": "038318bc7bf139a78ba30444044dc6607f41b1dc69186d88c5ee4cea292e1b7f43", + "secret": "b568066c67e691ff903e2341d032bed6039a1843fea33adedd9abf0a03e10a19" + }, + { + "public": "02ded2c5ba02a7878df6d058a40b221f2fe95aa758554292e8bf86037e34c724b3", + "secret": "41b15dee1526341ebbeaf87d2995d2a70e12736233bf0793734d8d671d791487" + }, + { + "public": "02414bf7b8d803a5cd75cdc6592b5f845e54546d5ed39f5ce3a2cc4655e292d300", + "secret": "cd9b3447ac7b22a821d3f7d3baa1a9b1c2f3505239efcdf2d74097f3151e22f1" + }, + { + "public": "0213522b7f3b5a2abdc0a58d1d2e703c09a9ce30fe627c9e98d17cde61d02237f2", + "secret": "58eb7757cf6e191cf9280efabad76706693a6b7909b4230c52fc8cebefedd51c" + }, + { + "public": "035cb615483aa47cc488274a92c9831872fac44c7d5a663b320da2d150b8ac1336", + "secret": "b1079fa040f54ace26357d587f9c807ba2da561b5524509589c410464cecd654" + }, + { + "public": "0328b4588099936102cc2468e6c76469839d5213df843ef75347458772fee85d50", + "secret": "7dadc0f5be38adedd9236384bede70821d0e976faa161f38c32a8d77d9a1e8b3" + }, + { + "public": "02bb8fbc81cc1dc1fedec5438fea066e9e0dbbf2ca36db0629017a4784590b4acb", + "secret": "f209980b7a9cc6c77ae4ca4dbc04235225cf1c88bd581bcc7964c31d2737d9ec" + }, + { + "public": "032bdf34526fdd3990b9941a0ddbdd19be3e1a72746f9271c80e20a7be9ed7c715", + "secret": "5a96c4548eac8e78dfaef855a31e3b2f19f6447e22e5184514175a3181fdf980" + }, + { + "public": "026014f6d3bcf788707718a4359e5ca82916fef261cb59e0fac41888e2d3716d7d", + "secret": "ac40b296c5d47882b763ea2b8e00619892228f259bf49cbb0dac7de5e19efe48" + }, + { + "public": "0278252c2f7d455598cf495904fc49dc5a45334b4437a818903848407aa8446012", + "secret": "7dc88455f4dd10d193981e145f3ef455c7dd94fe73be2004b36f3e6957b3cfdb" + }, + { + "public": "021fccdc2771b153f9c56af4240daafb328dab3c26c63184a33da69901465d252c", + "secret": "ffdc9d917aa7f510c9570ed338570ae759c659a821c46ecc84ecf2a2cf997481" + }, + { + "public": "03c8cd6ce5493288c12e92515cda6e7d95acf91247d985ea3be9a285f9ea0b309e", + "secret": "8a3f1339e5d3e5eb4638ed5d067f2708c796912cf2db4d2df9a874443b94b19e" + }, + { + "public": "03fd7902a17097e5666213abab7c468d4b4edded117665d7c485a705e74d3d6de8", + "secret": "db61083e9207353e7650d61b6eff556c6a1d186c479aebec80e78daf90181f99" + }, + { + "public": "02566613da0055ccd808084eda9930bee7b9bd87d915fc3cd042818e4a06f9c9fa", + "secret": "bbd52d8f3b98eb8d652f6f58cf4ea217ab7967c173264cb28083692e22fbe7bd" + }, + { + "public": "02b65210d52ca798d1297c4aa2dbb959507c815dc56d8883d7fee838f89b66ec28", + "secret": "39af8e6f5b809fb761ea372b54ef494f66d5cedc36fca08f7ceb9232c805422a" + }, + { + "public": "023e7cae7127d0181c39d3184451c81470bcca12ff2a1100ff5dd36fe6c4cb726f", + "secret": "bd888e6cf25a10f2ef2639fc42356ffa8fff28cbf486144c1211242613a5b1f4" + }, + { + "public": "03c2ea9be1ffd28f8dade240a710b56d85484b03d3db5660839606a24ee33c5d37", + "secret": "d23c9891eb59095189df84bc22750fbd27a7d9e9ac4f70873cf4979bb929162b" + }, + { + "public": "0226491f0f7bcd46a90b9f8a10989140ba316d3d37bd5fb2a95a48ef35fe94bfe7", + "secret": "b97d3dab39df578003ee19f2c91aa3e507d11fc92d13de898c0304aa812a9077" + }, + { + "public": "02044eb68e9106f19af2a78d64ca3de810894da129b6dddc7c702e978cbf9e5be9", + "secret": "344472fed56e6d7c3207ac397a43c36b090a8f9d12457bd63b12d44dea805922" + }, + { + "public": "03ad394c02ea9b5b0b6a08add9c1ab4fa94080ad7983ad48b0ed043a6aa32401dc", + "secret": "e7a395c3ec3385e83e2ec2a1fc5c311e2b74ef5ccf4ac465f5ab0e780cd3fb7f" + }, + { + "public": "036ebed918790c734e4d33ad6d1788ddbb24d7b646bc4d8234ed1dc090b7c56ea8", + "secret": "1725e3593f625434ad7467d755351f7d619726506ca6d7e935da2727815afb78" + }, + { + "public": "021a5fb8fd4ecbef29ea4df79885b267f4df99c7bb02a9238f1fe35260fde8e2f7", + "secret": "4071e3e6cb4940f8955660359012f2c326dfe139ca9c9b7d28e9ff7879d5c836" + }, + { + "public": "0216087af1622776e43e0977795f45ca41de5a71563b6fda7ddf65e83d0fcd1fe6", + "secret": "0df227db664cf0e38bc0ffdb46768c91db2efb26e850b1751e6e1b5675861add" + }, + { + "public": "03b710f2bdbd5993789fe317be14abd6f6b81e620691a5722bc6e3e4b031b0973e", + "secret": "00a01051569714edda40d8cb2923b8124f4a1635102c96e3d41f305ebcc6b892" + }, + { + "public": "02d526c195009bc260461123ec270c887e48ebe28fdf333938d80cecd85fd2d54e", + "secret": "4b97720dbfdad731ae54882f7c6f00c3ffeb6a74bd9c07ddf0affd91a7b7d3a6" + }, + { + "public": "02d2188294a3c216cd363c54f37535e4c354a0e9e59210f310f25669e1f66240c0", + "secret": "b8d266dcc91788971792dbdb6a5006f7389ccd4d4e6403c17fdc286d1c1d5551" + }, + { + "public": "03ae5d33a880f6559e39fc3962f3d2835ae5c6c873bed7f58cb13704c2c40977d4", + "secret": "80054a2700b28aab41c3d0159dcdbe4028363904f43aa1fbd72ce1d47f291dfa" + }, + { + "public": "02094b891e293ddd5419ea5ec66a1fde26d0a7a8fc72afb185ef7808849684ab6a", + "secret": "670bc810d6d87744fba089280457898c0b495b3f36e51251ec1eb7b1525c900e" + }, + { + "public": "0385090b0a36a4f4563eb7133e177268dcd1c20a70a4ed5e86239723e2e70a2edf", + "secret": "4fcc313e7c4b7292809d0945c68840d85d03d999a78f806724a295064d270321" + }, + { + "public": "0218211dd231d217cbb0bc06276fdcb3626d7fcfed715a4288a2d753fb17627355", + "secret": "a5acfa5c2ba1cf24f6d9e704f2814f7d764c188519b816d471eb4cfa569a4cf7" + }, + { + "public": "03aa36a3f9f0f35e9f752cded2739f8b69ccc108d726e8f28d43947a69d607c954", + "secret": "e95ace74c329bae92fa1f7fded5959609950bd82d1c3fbae29f3c04c647a0c7f" + }, + { + "public": "03d3a36b9088d68dea5f75798483d172edeab7902ebe6c33be51af6594c4ca3c8c", + "secret": "dee9d868830320e8dd17d329cd548bc6ceb752a36e4caaca00af84930aa7437d" + }, + { + "public": "02897bfa08bcc88211d1396a63b21a703e0e600fa2374fbd3fa2f852bb318e67b7", + "secret": "420cc2f574be5ba97d773324994e7c0d5abd0dbebdc977a85689b06106265698" + }, + { + "public": "02e8cc38b83dbe186d83748cea7e46e458bf7925b4357e9e41f2ba561181049e06", + "secret": "ab7248c0e02335d73f2f28602d3c98291d1886a4f04eea71124fcb3e9418ed6c" + }, + { + "public": "02b7d547a02135e0f6cdc905adf82827856104228e3830a83d9871a5d679462c6f", + "secret": "c31bb95375fef9b2ab999e4225538c8e74260c2698d8870372bbabf2fa051058" + }, + { + "public": "02145ab06c412c90df3aec730faba92b5cd39b49bacd879845ae7921071f921418", + "secret": "9fa829edf72f2dacca814c8e5efa7c963be057d613f45e996acb9d0abd414074" + }, + { + "public": "0283369c017411e8ac1efbe09540387de96375b0f768fa931e14499e9ecfea9d62", + "secret": "1a785bd0d956046ea2943e4440c63cbf780c0d65467ec5c1b64092de4c434f99" + }, + { + "public": "0364bb1fff865f837ed630053c0fe738eb56962042a434904b78b74eea6b6254cf", + "secret": "08daf00188789e1cf7c8ffdbcccb622384d4819c690e2658bc5c1425df710327" + }, + { + "public": "02c946cd73db0bf6898b01e4136d775dc95a8f2e4cbc5f96e7bbb93336ae39e278", + "secret": "5686e816095280cda36189bc663e4cbe69c0ab657251ac7be8c0831532640d24" + }, + { + "public": "03a70158365b90674cacb94a4ec325f94fc8f242fc819786b6f0ede99f23d13033", + "secret": "ead0571a92d9b93058a775e82427f3f66f0ff719c25144a6d14bc05877e2d14f" + }, + { + "public": "03fb7f59708f150ad38ddda5590ea8b2d1bda6cd9099c6ad28740ffbc9d768abe8", + "secret": "7c05f5517635ec51cc9f41ceaff58ee048002d8237f86543a0c95e906fbc5af1" + }, + { + "public": "0297beb3679b0ff939d73075eb8c9ede94c659d7759519894dc113e0569831c95d", + "secret": "a8711b83d7751241ac5f536e6aea027de4a8b1689c3dd2fb850b9637204dc4bd" + }, + { + "public": "02f458fe8e89b9d52bf583bb95bd9ddbe6480c8db36f1615949b42f3e9f690cb25", + "secret": "85cf92b99f7a0d1370cee3601ca3a7b18e2ea4657710f1b3cd003997e3e43d65" + }, + { + "public": "02d937c2d9e4999ba038164acf94ae925277133a4bc7aebd27d5a1edb3a4b83d3d", + "secret": "32da7a91ac927b0e044d214fed8a790d36e8054fa039452f63a031b6e369c795" + }, + { + "public": "02c2231ea46c01575bf5ec12290ee48a8f1178f7c4f425421ea53aab4379380f99", + "secret": "554df7f7a9e65d9d75c39dd6c65f250bc48a84c9256116c614ea4ab3f9a43771" + }, + { + "public": "02c123e712cc4074587686377115808a034590a5717ffd6079af476c999ce85b23", + "secret": "1356ce15581d7d9378461be04131b7ed20e887c4cc5bd27b2cb86ae24e9fd2d4" + }, + { + "public": "02038a8a078eef20925bca7e69af26fb5d3bb19d80c9e53999141cca01ec245a5f", + "secret": "1bad5743c432350fdfc7851c7502d3f41bb06544a36f666804df23532fc284b2" + }, + { + "public": "037e03376ce55d62e0cc8c7f685dbdf9e5be4847707ff3fdc30b2eee703c6e7fb8", + "secret": "a062df60d6ff65509e4e3c35ac615e96163f68862b98f843564218c5932b501f" + }, + { + "public": "03b32d2076effdfde44cef84e25bc16de6b42e5dc4f56d7c74af9d37aa32173c79", + "secret": "a487b31673820b5a192010339ac890fc666a7c821c8e2a2c0b58679806382dc5" + }, + { + "public": "02de4ae52758f34998aca357779877ddd6f9a21fedea9ea945beddfd0efbc94a08", + "secret": "468fc4605d329c6f24da88ce7732cba734cc414c743dabf8e9926fe02463ddbd" + }, + { + "public": "03770a8a94382c4d917508d8a12f5d05e6950ef3c6ce90dc9675c7ab62b97e2f6b", + "secret": "fc69a2100476f973c1e4571ba4db41e528bc6a8f69e45a4b5824ff39b8f22e53" + }, + { + "public": "0322de013ba3d1807e66d9a2de22b8c0b4fb2628b1eef3a7c544dfcbeace1ec7ad", + "secret": "b6e4adc2b0d4ac96e61525d402a2ad5f23e32c46bcd15a86b14f3b523689f06b" + }, + { + "public": "03ab9bec011f2f4c5370f2dc11b1dd06b4aaa86fa09baaa8e18d80a8c1cadd8213", + "secret": "cc8ddc499f07b63ba27fe62a9b209554ca6a6e7ee29cc92af5b5f7ebabd5b110" + }, + { + "public": "02fddd321d1c2d58f74c71f028532d7e9a612934723824db73c74ca9befb77dca9", + "secret": "5d06d3eaa9dea8b3a9e72f63e6e1b1951709c049d6e9dc01ef97c75fe1c90eab" + }, + { + "public": "039f299edb044d45216000d9212394356fefb917ac9c13bd37f5d0743880bfcba3", + "secret": "1675ecd9eedd75e3b6cf751876fcc688f605a88a25fc066dd208095709949005" + }, + { + "public": "03b2b620c94bcc465c270b7eaeb363fc2e8527fa95a81bac6c555e0eaff9d827a0", + "secret": "0375120e57ce3ba926b98401ca77d14fcdbd12dfdb3aac10bf0533fb20551686" + }, + { + "public": "035f65ae3be91059215c6073c08ea322476d607bee624ead58f0bd3adcddd05c4b", + "secret": "83c65362f1911e3ad95446cda0fa722d20cb375445cca3b92049682cb11f454d" + }, + { + "public": "021f4e14b1ad204ffe56997aac67b052f2abda2f819d9f17796b4e8b55177c343a", + "secret": "a73a5d461701f515e2caa6a8948f5ab5b6f03d495729a5efbd263b95cf13d5ee" + }, + { + "public": "023d5e4b79c2923f5678d16ad43c268c0c004a19921174d6323a24b8d1d629564e", + "secret": "2148da2f983591541719002a340c0c384255bedef2f003ba6c9b7fec22a31c14" + }, + { + "public": "02741fbcff4d695e9aedf84880dfe8a5e9e8156cc65b3facc4b9c1a0ab2366c868", + "secret": "f0745267ea45d502537ff0bac53f54c11d9f8f3bbfd7d5da16eafe99f8c31634" + }, + { + "public": "02efecbf50a5a9c32406854a37f967d47b3aa15e1223174af5789b064ebf532d6c", + "secret": "00149ac80dffa9a23697742f00cedb15e66b3e463ed1b06abf1f5e4a1f12810f" + }, + { + "public": "02cc71b25c3780b2579dc9babbc991db340f6f55a7c17da34f4936f30ccb925db3", + "secret": "5c6e1b21348c1a35d6ff7287d8f87ff72497f1ba73cacfbd1c587b416038a7a6" + }, + { + "public": "02fa94621d45f5bc1663c98ea177ac56adea13044d66a691d89e31b2ae5343e7de", + "secret": "229dcf37c5217de267d83971a330ea5a5a06b6c9ea49ef22a5c5981d98fe01ae" + }, + { + "public": "03c33429d0f943887d082c2d92c4d2d8beced0138351957d99848f981edf9b697c", + "secret": "d907cf868c55cc305e6149d54e36565aaca0eaee6fdd0bf13728424171f789f6" + }, + { + "public": "0342f1612dfaf6841a4a31f4d71b27aa57db60ecc3a54ffd341ba427740a0a513e", + "secret": "06ce1133faff03e8264a3ef90c660ddf84d8394ff58ff1daf8d2495b98459ded" + }, + { + "public": "0305823c91928cc268a8b5cb131545996e6296733543c3377e1d05ade44953837e", + "secret": "330251f55d6411b64ceb78982a5b97cec197e946fd9ab26d9257f98b4cda187e" + }, + { + "public": "03c1c1983acb49bab11de9fc62dae95d03cbfa5e51338f9890261de701b31094f0", + "secret": "b78fe70a5d18baf9a4633dd6b452977b692af9fe8f6d7e07e26c23044765971b" + }, + { + "public": "022a868adc302b56b97ab75e2e03daaeb416bcabb009b4842e40bd2d12d5cd00e7", + "secret": "d06177495ad10d26b7ac4030e5eb2d8503153eb4b13c46a39dfff2bacb350b9b" + }, + { + "public": "023f61bb5a927f28c3751904ff13feaefe33cc21fef2ad00a8b382b4ebe223d3d3", + "secret": "08474d986909714634060c4f0ae9014b7d275883d9061eaa2c74c710271bc217" + }, + { + "public": "025aebb9eca8a3d69c350037c6f5f80d6030e238581c4ff50ae5a78333662733a0", + "secret": "a47d62ffe2f36ecc5ec3de21013dcd7288e08b046e4219a47043a7619b937b79" + }, + { + "public": "0293b268a5b30ca8c15134cf98fe018ca35ef8190280b5a6e882da23cd6e78eca0", + "secret": "00dc43c1630a58b46894a34add8024145a1bbb4913f21dbbf16513819ac07eb1" + }, + { + "public": "03036ae0f34fa098a6c4718755d28dd6b7b6a65cd8b2c126272032b08b51222580", + "secret": "d45957eddebc0d4fe46027324e6bdb83399d87c73b88fef98522b08198806cd0" + }, + { + "public": "036b0edd9881e6fb65cea6416d94cb09d798071467b3ecd58dd75a410ab0d8fcca", + "secret": "041c78a47610730fd5400a0f4942d6550c54a4dd5cf8b155d77b510864e3c642" + }, + { + "public": "038659b98de188909e183360a1e84a1db088a6775c0c678194aa2c07323546683e", + "secret": "4d663eec997b2765e7746aee1f601dbe9dd1174d8223135d35cdd1d48e6af7ae" + }, + { + "public": "020f0b649f9cd41300f039a3aa4ae59c19d4ad0e72ec60c882c0f81399356b602e", + "secret": "6f5da6d383cde22bed6a3067c1194314316d0599e4b6e1d001272e5ccad3acc0" + }, + { + "public": "03761694c76b291e65fd8dbe867cfdd34e0994d2172b49ef0524a40b3eedd845da", + "secret": "4830112c3746ee4116c07f0978a6952f80138ec86c494a7f63ef8b8ff218f269" + }, + { + "public": "025d3f3bf8a486d43696ef6d178096d660a972ba1de0c8e0612f6d537f8a9528a3", + "secret": "91260a7ae742a34e092e8f03939abff246f90298dca5fe0ce194c6ab78b0e5db" + }, + { + "public": "03dfc51caeb5cb49f97da1b17414b8811b4e75607554aacdfb756e1bce54aa1130", + "secret": "370a01a430d1272c252cc893b666ac581a3ff2c182ed6c7e19bed1de04ebc0ff" + }, + { + "public": "0274c44bd1f4790504f474cecfaeebd7d8a313534b86fb2317ce512e5e12a43c67", + "secret": "4c0a8553debd8778b4b879b237c8069b7e827f5f02c4c3fe880e0ef4c8141ae0" + }, + { + "public": "021845adc066183f623b645ed7320babec3287b6c384dc8c3ed7fd41c75ef2a0bd", + "secret": "33360e3c416e66bb6236ba4a9e863bc33d5b34137bc7a7c849ddc65b296ff1e6" + }, + { + "public": "03dafcf6f3542fb51efc3a8a4f820550e8c957f94d8b9b407bc48d70751b58a5e1", + "secret": "e8920a8806c682ebc3738207add3349699237280998af88bbba4178a1668cf1a" + }, + { + "public": "03c2ff338000d43a86416e816d7b020622e44a7812e7f568389f32db4dcd6bbc77", + "secret": "848031465e383cae8242bf514ae19208266a597afc781e4330bc88d274547f85" + }, + { + "public": "0393b5864cacb3d5d58a436dd7e385b434df641bd83d432d6aec28024ec180b36b", + "secret": "c0f435a66fd80d607bc2a154276ec36c7debf4de8c570be5b2581a023ee3af2c" + }, + { + "public": "03daf6dc688f29c13f56d5689ef6d400f4fd5a4bd18a3deb7037a64e23c39c4e5f", + "secret": "ee308620fcc11565aff225845737bfdcd1a4e37bf374c6dcbc8952a9c0c3d928" + }, + { + "public": "029ff6b08fd406fecad7d3ac2a6b2f00b64585d297c789f82ecb994c2895203070", + "secret": "f520e34e47b3ad59435519d7ee8bd4054326f09627e328151e55c213bc9d2d84" + }, + { + "public": "03ebb67e432a172d6dfde2b2c0412d1fae4674453a8f8c30100a02141d40115f31", + "secret": "f99b4fd58efe114210638b8a86f126e390a4909307a8cb637d055b1fa2aacb26" + }, + { + "public": "02eefee7896cc495bdd7eca7d04ae8c4ca9687212ee2c0a7aa15c331107f767da1", + "secret": "1fd1c5a30a74dea3cb4bc70552e64b169dca1d2a33bb4a0952fa450f67e300f8" + }, + { + "public": "031faa7ade433a598b6d95ebd7b188188fa003cef4c9c701bdac8cf8b2e6451115", + "secret": "f3cd014ac7eb75f7c2704a0dc462fb87de91fb229b63ddf01b06ce2b31cc2bc6" + }, + { + "public": "036f96de9ff8f5af1bfd65a6e30b2c1ee4fc3b073789b33cbb67d43f6e6152ff12", + "secret": "80e2df85d296379865926fe3d26b3f66153171077b7d254e2e3ff9ca828e2f11" + }, + { + "public": "03df75dca87d58512f396de3b178c9f72b65e2a805a2a49bd230cf99402078c123", + "secret": "a01cd994a55e9cda001260908e66bb3e58774e4d161b5281724f81dc4dca1320" + }, + { + "public": "0240e81fb9ffaaa1142d72827f10cd4ae1daee86db174669092cd56918242544ca", + "secret": "972e33ff7e084a4714181e9a808cc3a78fb17e396a6f040718f7ecdb67260b62" + }, + { + "public": "0228e31084194aed2c00d475c25c46759689fc43c477783d90aca0ce37f02961e9", + "secret": "620a98f079a02e7396af1204635e3a1ee8854764b7d4a8a2c501a39c3ea36f94" + }, + { + "public": "0351a2556ad50fe5636dd52bfb3878df471513cda9c457872f4e5b8f19e2fa2815", + "secret": "ceb63ac1c6f86547fb9afd4e729644de9d7abe26b9b85e0a0bfca492973b24ce" + }, + { + "public": "025ef1b80644768b05c69aad5dcc868947b114830b2c62b8e7126061f7257ece44", + "secret": "0119988d3ff25e793b21ab4558b3879351ab64eacdda1e8eb069c6f7ed50879b" + }, + { + "public": "02c0724c4669360713f1fe5f8fc3e6b840a729e0fa69c2e0ea902b2426f583d8b1", + "secret": "11498edb61aefb830e42a72b3fe267687988f9d447e556aeff8e14039aeaca9f" + }, + { + "public": "039df8d27212ee5b102a1c090388cc57d50a69c97957056d34ed35cc7f72402430", + "secret": "7817e15b90f9058c93df8843d7941fc974eb8f3acd87e9043cbfe49b6305fba5" + }, + { + "public": "0285f2141fabafab325fb62f2217285bafaf98ab1ab30c06b051536a72799ab5f0", + "secret": "e2d8dd92399b7b13a3a0592947a2110e12529a597fb969c1557042d3c2c95135" + }, + { + "public": "03a15d99bf1e6a5e18d2dd1e88b5c1121107472a3d94cd6449c3c6fe0150e0719b", + "secret": "50b84bf0b049e99254ee246c26e476ca8d638b02d42354a3928c9cc012e27223" + }, + { + "public": "02e902c761454f8dc353eb454f31091d1bfa68d2012cfbcdad50c320f396805e6a", + "secret": "fc82ee21a751169573edf5cf0d2709c9dba0d80f7d3fbeec4a31b085f57a5786" + }, + { + "public": "031ceac8d906d407be82c1d0204302c72c845c838d12d796b7874f24c08b2749d9", + "secret": "e1d69a12ffb11aaa0461c33d57c7e6cfa81a433e27ce68993bafe720e19ee878" + }, + { + "public": "02db540a08ddb4d3fbee866941bd2be7c8a5780fe7375bb54abe293c647cc7c5aa", + "secret": "e9017aba32c1b6c1b239f44ad94e2b08e847572863c107aec1de965422913784" + }, + { + "public": "03b64cc268e07aa1ed32b04b67f8e6c1d19d91b64f0186a0799d2193476516164c", + "secret": "635c2dd80c56e3c6b8208c10e6d48960275806d720124716eeed90dd0bf8815c" + }, + { + "public": "022eddba9ba82412e818b0c63cafc3feb9c63a76774d0c5ab6db8aa09406f281b5", + "secret": "3c538857bb9965d292ecc68d2f824a12fb45ae4fb8be549ee4574bc8f6b16306" + }, + { + "public": "0215be69e47bbc8eacdb67a8cfe34200b7a4ac88c8481eec2bf196197689c712f6", + "secret": "48a8b67448b3760caecd57af1da89b5ee80d78925d06565a8f032eadeae4050a" + }, + { + "public": "03557b2a4c964db2f573a3c514f24cbc5070a9e339f5923b6240ad10204b7e43a2", + "secret": "fa230b561a6150e1d7085af43afb6f5bb8b81b44583f34b5d38a48eb17499a05" + }, + { + "public": "03e47ee96a44c6eaec31b0213ea6acdf4cf53e3d8d2aa8efb4bbf6c3878bc95285", + "secret": "1ec8594f1db3a68a90aa0fdfe7b7224d8c0f8e3f4d0a2fe765d2e40becaa5562" + }, + { + "public": "0218acb50a07a42b9688011cad405fa4b812bce24d9ad4bb28bf33d539ea262349", + "secret": "aad49a6962121ecf71f73f1675367559401258a74dcbdfe2296b12f50f39a19f" + }, + { + "public": "03b382b530e29cd71f6f15cde59ed915d1a9e7de0e8eebf7b2334bb21864e99f50", + "secret": "d20f1e4a64a17aa63779fdcc0f3302fc357678aae06b94dac9c20809f811257e" + }, + { + "public": "03a51c8aac57d2968f1f848304584e6ce009acdc1a62b9310fd9ffc0f0f8063d30", + "secret": "d6b1baa701b7e43c4a2fbed453c9d70850548cf0064ec448f076d5cac5da5b2d" + }, + { + "public": "026d9908597b085fb1c51ab602ae0722f52f68be7c797132d77029b37d604cf4b0", + "secret": "7e8ad42ae65ce9dc237796fbd4f5b2947c3168297e95aafb54858d13c194a7e7" + }, + { + "public": "0342936b575aaadc94c7edaf287076bb0dc1b082083ba97ceb70fece8cdc01ccfb", + "secret": "2974a7f4bdcf7bf096a1ae49b92914e4f50a3fa2bc59e8c0a3239c9cd9960314" + }, + { + "public": "03587031c4d6cfdd301f6fe2846bb03f25f4af28e79e85e98d4fd49ae0f9ce6ec9", + "secret": "580fa2916f9137023f793d0d34774640f4762f911d21ccfdaaf3b78ebc69d464" + }, + { + "public": "0228bdee236cf55ba43f0e8589e2de57a18c33cb905747bb4b23f478ced83e9a0f", + "secret": "6882f65e95f1b827c322b2e87c5bc4922cb3ad1e33393789a17acf091bb21f3a" + }, + { + "public": "0362fa306b217357f27c92d40ef8810e06d0f0fbb2348a482f8de15689088ac1b9", + "secret": "ac26e61ecbcf7a748a6dc3964c11a8c9913257a9921a06f328baa5979fc81e8e" + }, + { + "public": "02f72327e882e6388d5d6ce61b26f0f0d6b45baba8a6058d931f57f1ea1144a5f4", + "secret": "8576966f10de06e7a519499002c9cef7d72d5adbcc5f063e2e3829bf24fefd66" + }, + { + "public": "023ab0c611210be948e1b1469acf09a1d19dc66f0138154f4edc2c534919213034", + "secret": "0b4a37b757feb5a35bafd455f8ae4046cc3e845cd258cd06b3b31670457bf531" + }, + { + "public": "039012ab51b10c26690d94f24bcd6ccca2c669171ec8da69529f25c206233c7be4", + "secret": "23cf9d4bcb7068e6ea24f1119edc0d447c58fe3ccfaac60e8f27c2707bdc985d" + }, + { + "public": "0347a931d318b37f5df33c8c639d443985ce9a4682a16f5cb414000fcdae025a97", + "secret": "ee63cc52a67b6a0504fab1e241a31e37a374f359051939cd0af8583c6085bb16" + }, + { + "public": "03ab8b47003940cc06d86c1cbfa5851ffb16c7b2700b4581987a30463380def6a7", + "secret": "a43282c2229a4e72bbcaed06340addd43f4c0f93f3a54a803d3f93bb2e17d421" + }, + { + "public": "032dd2eb86ae535d3cecea49501398f74355d17014251beabddd4aaececd418ab5", + "secret": "9929a3035f9e5a947ead6bfa97c5f00103e6856a45198168c9e2573d588502e7" + }, + { + "public": "024565c5812d9b1d977144c9ce9ce78e3ba68c81dd881af3b70cf126f6c314a458", + "secret": "8d81fb207a42fdb515613d0bcfaa3d995509f9118c09459ddfa79b999f43cb63" + }, + { + "public": "023f9afffbef9d62adbbb65bfc14a8c780589c25485dfae33869f93371c35274a1", + "secret": "ea92387ab240914cb9fb450a027d4979d28843a00f767f39257757185ab36b60" + }, + { + "public": "034f526b796d8fb894550b6d2a03b8a421f92e68d29a5dd9e80fcb9382945fe833", + "secret": "1341ddbd5ef020e3ca054d504cfc368a90c74c71b72fe3b8da2f0ef3f7b438b2" + }, + { + "public": "03ba378a40188e49cd011a147ab37ede7f8b9ae848325ccfcb3611f1e7996eabf3", + "secret": "126b0fe88eb6b7bbe53ee4e7ebc76ff616ec75c8be4a68da13bf38bf70352518" + }, + { + "public": "02ed60170856c7d32e9e64a3e9438db07e525d55a9c9b03e5c99503794b686ca2b", + "secret": "97167ae109fbaa115d61c536be5c334754d5acc067261a97e9b05602484feb79" + }, + { + "public": "02c2d54c3fe10d3d73af2af75a0356a446ac77922be898da76dd6e440e932f053b", + "secret": "7a6856b3e2c5e65d3ac34647e3259ecebf17dca1f39f0aa3e14e1cffd4c2cbb0" + }, + { + "public": "0201150b97db44e2382e43a04f3c56738d0a2cf68cc8f31c9f1c6dfebc52fce88d", + "secret": "887fb1b70d369c8d1a03e7ad23bb619d295352d1dddab6bbc9d31491e1797dec" + }, + { + "public": "03b17ce198a55281f3085a9eb99a19e99f5a43d188dd89e443adc056d52633a863", + "secret": "5cbdb726f5e1be4b0d22ae76e0392ab34ee27acfac4c4dc2c129127a14787a6b" + }, + { + "public": "02e6e855eefc43664305cced1f78cc268a1d523aebee5e78c9bde7878ae494528a", + "secret": "8dfcd59aaadb8cd7d1e127aae2927326df3f299d03d3309ae2fcf691d2e5a63d" + }, + { + "public": "03c4ffbb7361c2260b9796d146199019af4df0044007bb516d2a7b04fe54ce1ca0", + "secret": "325b1756c2e4e21e461d0de7605c84f1f259bbdf3cd1e059f8bd9be427eca69c" + }, + { + "public": "03d8cd75b5099eaf8b6a0a78bfe88b81dd13d5677619eab3e4b3895c58702aee32", + "secret": "47c444b0d0fc235614f5e259c9ac434d43a3ab6940b1454127e24b04de43118f" + }, + { + "public": "03692d15ba47562f82456634bb45dc555c679be769b42ae67b8a44ab0776e84418", + "secret": "cc5eb2566c40121516c8c75f37f710c83ce1bd86970e1afd2ccfc7bfce23ea58" + }, + { + "public": "029006793b435ce871b20680bdd268dd8f61b73f3b6ddf01812472b4fed1b2c624", + "secret": "c2ba08ab437bb228824440516614fbfe7694cbe36d582597af16bd60a3a1612f" + }, + { + "public": "03278845d3c3fd86aa93d5f83b2374b13895b322760263766913b645d9a961213e", + "secret": "28b4e61d0717259ebb99a941d8a48ba28d1cef3d7dd217200e1fffa31947209d" + }, + { + "public": "022796d4677387b6791faa301312ed89fd3841f99c0fd3482c4e00cde4ff901cd4", + "secret": "9db0c4a6fdca097fbdd0b24ba543ae941e6ce0b9bda9429f56f880d25c6615f0" + }, + { + "public": "022721c4da4e38a33c52df73abbab56a76d798ad67a80c332fa5734e2c3b6bf8ae", + "secret": "4568d345a18f111ef9034b9fed63b7be042f7c41e248f884be2bb77e978324e2" + }, + { + "public": "02b9e83231f3cfd5e199c4c3958e83873751a2c8b040249142647414192b11ac81", + "secret": "876e91b95a66bc228fae79670a3eebd9375aa915936d34fb0ff90fa3f36b5807" + }, + { + "public": "0374613394d1189cd1f0b4d14bde78b6ba5c3f37411ae16d7abd5a5b4177db6320", + "secret": "6328f66ee46fb89df7bf1fe317f65d745b53a8c7a6d7062691a9c50ac8d89ebc" + }, + { + "public": "024216c5b152215591288c8cef9bc931156a89056aebf3885caebc4c2b751c2a3d", + "secret": "8712b964f91681c16d8d6e291ebec9b4b55d80b74e275d904f151c3bedfac109" + }, + { + "public": "03337e0039e8792860466151bdad944a0670087befa7df2be3b6a763afec49d818", + "secret": "fc194b717318ba2eff35ecbf0e7c62e1a39214aa91aff6a08fc38373fb02c419" + }, + { + "public": "02302b6265352fbd74ec49e9f83650b6993f7eb21d0c1ea7d2d82dd7b0d7b2ceb2", + "secret": "4133af8ac051768a8eb68b663aaf8955dc2a82e021081b5d46ee7b65b7c15437" + }, + { + "public": "038a6e248ed91f65aa96cc821e445544894c1057f517aca0993c1bd3734f592a1e", + "secret": "971d7752efb0c6f3effe401784b322382d7546b3de5f1b4a0372e1638e0802b8" + }, + { + "public": "0349dcd67323592c7649f91d5eab013d1f8e41dfebaddf3e9d0002a35d75ff7da0", + "secret": "61cdafafed5af201d39f7e10d20ad23f1e37ec7b12ad226958bf3d04b574aeb9" + }, + { + "public": "0381001d0ea8a9d8a7ef1bf24ca57f69e4226e7d59f0b770723987c36117d94191", + "secret": "1f35712210d3a1e4561d04c0c3cf3ee8f7469e2aa5aa700555efe3c63ea51892" + }, + { + "public": "02faf1087ce9f4c3481496359dd81973db97eb3b070d092a7d4e20648f90efca84", + "secret": "7fe2b65fb3ef9c3d8338987ba9bd7fd96fa946dc52fe5552d98872d904d37bbe" + }, + { + "public": "0377b396c7301bd04ec51361b7c1366660544e693ee8481b0615acd4e357d61ed8", + "secret": "3293f7589cad8311364acfa8cda6122ce777dd167957086ec38f2431389b1811" + }, + { + "public": "0288952936d813ec27a3199c18062b6be6348d4c5a478111e650a7130e37b7cb61", + "secret": "a5ecbefa4fb2d4c74bc57c1b51b22c87e03ee15652aac85644d4af91a3a3f5ad" + }, + { + "public": "0337529ef779ddd57c141f83cb70b1f94d7e2fb5fdcdfc3b5c2538ae3c19407867", + "secret": "f60b118d1ff97282d8b513b6301592d0c5cbde1bd9df3cd451d5d0b7bee25e1b" + }, + { + "public": "03514b2d4fa322e4220e03bfa4eff36996839c6e5f5a3db2d416ed7fbb8c848523", + "secret": "bc57429ee9b0eae3a05b92c91f11126795906a7f5d4bc852043015ca8daf5b8e" + }, + { + "public": "02e75e1a095aa7951435f6b4c693a4ddda27187cc5cdfaef7fa95429e80dfef3ed", + "secret": "add85c8a3e90d76d80457817a0c348d0b98f6cb502fd3c44d8395570ca652d32" + }, + { + "public": "0292a01e6805304eaecce3dd4a45c55f32a925eb221e272712743ffc0de7e535f9", + "secret": "e28fa4d284b12e87397d2ccf23210ba47b1f71fbc4d175aafd772dc94b4d6aa5" + }, + { + "public": "0202c4ddf6da70c550b9e720c081886d5d48f7d9b3bcfe63b4c3f188e7f9ed9138", + "secret": "6460f46b7d4e83315363d014d34cc9c0b2b058249f74b6b1273c6f0a4b3fbeee" + }, + { + "public": "03ad04afe8279d00c57cc57e0822749617e1135bb5e23d43349a68e63190f90746", + "secret": "8d936c905dfcf11f1ee52d82fb4b675d8ae8b3f9162874ae31e4e9b198795cb9" + }, + { + "public": "025a1f1f38321f7f13aea2515e6b30c3770de198ba36734a5ae804a133cd933b7a", + "secret": "05e4f2fe0b3e208dbf73b422bb06b8b05e9078f194ac78bff602ca3a68688cfa" + }, + { + "public": "03d8b005f1e177c7ad97a97ac3c2cc926d4514c69cde59dc3722b3fd98aec3b49f", + "secret": "4ac0bc9d7070550e257278ca53665ec6193a8475accd93b6a138ec3edb5aacac" + }, + { + "public": "025450b28dbeb2aab1dca5986e17d0719d08bb7ac9475b7fe55b1f57fe73cae255", + "secret": "6fa2ace42cbc8aafb0cf5b70bd1a617b9f1d6fc382250ca24b20fdea5c6fa3eb" + }, + { + "public": "027c65e2dbd5160047a92302a5b3c77c92059ecdf46296da6cdaeb6f2fd13b9502", + "secret": "52903e7360215ae9f8fd0f1a38128e327633021d63f25484ab7cd368343811f9" + }, + { + "public": "029ce84c24e03583cbdd57b4e7ecb6eee64fa5d013f7a7c5f3047cdf158f628cc0", + "secret": "291cb37faef915bc101cd0eab995ed2f0844616f2684d6836bb40687755419b0" + }, + { + "public": "0365fecd7094c29ed32e20a9eb8c88364fb8e461925c1c74c1a06acfd0fd6b7a49", + "secret": "03f1767d4d83fc61eada77445eb0df091903f0ad6d49f1cf46ace4f2e18cf7e4" + }, + { + "public": "03727ced4ded5df755150c0d68dd5a94b0b29a1614581959e466fa658652341852", + "secret": "86854aeba99eae5c113d128a486504a28f49c731f5769bfa9d847509a33cbc91" + }, + { + "public": "039eb04766c279dd330b728575481155e541b046e45fa1b1e24279c63e43f6a494", + "secret": "4f1cbcba8cef09407108ff7c7425f06e779e1b0245578a994bd2b934602ebcfd" + }, + { + "public": "02011e91cd7775a521be05761ded7af54a966ccb39f7ad1d3a731b398eaad92709", + "secret": "8c083d4f920497bad44201fd68c12a5ea7f5042cb44dc0ea4fc2fafb67297b34" + }, + { + "public": "02ce28e9a3e0b0e87b36aaaa6f10c5b2b8d7a193d427cc1b89e60173b2481ecc85", + "secret": "4ffb21045bd726f547b827571fb0d818f7825a5165a5762d406c2d923c409bc4" + }, + { + "public": "02149b8db4520f5aa488c91075bf066228989de165b27cc7ca80abdc445df80ffe", + "secret": "bafe8fa54b972de2cd565ab2e5644f3b9c6004cf9e786ebf38755930cd3552e0" + }, + { + "public": "03bd45993f9c8c8a6670fe9d66cc2a91ed814f826b44d50c6d0423ec0575e53f67", + "secret": "e86bc53ab49b9431ecebf0aa2afbdb0c15da169de5bde62e966193678b7eee22" + }, + { + "public": "02b04320a9937856d6c78da7455a069412b5f560225811d78cda97f3d28fc6cc90", + "secret": "be1f8dde234ab0231eec1bd81bd15dd37efa53301ca84417b9c80d252d6aee03" + }, + { + "public": "030371f19ea03c5b848954b186069ea3f0737c3ed863060184c3d3655ef399a333", + "secret": "1cfd1f7f8193d0564580ba07cbd48ea32e4664cfafbb18bda1e10436d7fe1337" + }, + { + "public": "03e59fee4bbe872687e6239ae9ced7a5520888afdb84c48ea4d663af9e07ef97cd", + "secret": "0401ea170aabcc125f5885c3ebdf1b4b77ce5008d9e248e2b073bc14d16c5408" + }, + { + "public": "02ed41d3fd4492664fe2d53856e2c91b0a025ffbc147c36188698aa5e83501a611", + "secret": "8b15a1d644b467c1395f04e61c581b9df1fd6e0c6bf26c93b7c97d21a60715a6" + }, + { + "public": "0257a58af09968497007e8466c93e8b223968ba0afa250124401ab64dfb4e0812a", + "secret": "6a557aa06d88268ed71e3808f804ab67bab2bfafe3c9d5f5e7ebfe5c21f0c6a9" + }, + { + "public": "02c9cca8a59da75c2cb6275736037e7f1ddad1f79894e60da7d4d447ce773ba3bc", + "secret": "015caec542647bf0c12c2a0864c9cc9faa443a646ba8f836a1bee656bf7ba3d7" + }, + { + "public": "0384c7f2951c01d47ea6fde0c6772fea69325a16e7892137ccd1981f1cbf244b44", + "secret": "22c6578ca8422532dbdd3a74c71d7a4516d19826bbf773d196e5dcf614d93577" + }, + { + "public": "02327a27188541ef28a60ce68475a2adc4e70ae60b197ae6bd861c6e7dc5a1b0e9", + "secret": "36e629d17a607a628a4856e87ddd230acb8060568bc3381518e08a99a0bc78d1" + }, + { + "public": "0329b624bf66611be968e4a6ed467b2a27cd0889c2e9e723fdf29c30da52ce12d5", + "secret": "295d7b8a638b5670cc12b2f6744cecc54c2f2f4ec5aee00581db4f24933f17e9" + }, + { + "public": "0223340154a63c65fca13b45bfea15f297257351a143b634597883737401241082", + "secret": "b180c7a10b48c16795edf29a822f90b203efdb8f4811c1b28e7f9effe25330cb" + }, + { + "public": "0290c87728b3e866494f7271e9f461c028931578e0c38b64d0046482b23b7e5e9a", + "secret": "65f6c209dc38c5df331cd00fdd542762c610679a1392948caf582992a7e9aac0" + }, + { + "public": "0377016dc7ca14b1dc52f189b8d9aea2a9a2cba260423866150d7941d39d9a4344", + "secret": "4a52733f0cd7204af26d3fce17d6429290d65e1d37c5e80b40d2e1de86d72891" + }, + { + "public": "03bd9728d9db8c4aee45fbe9226721d4035cde50e25b80ca115958a6847d8dfbda", + "secret": "1d61da07ab06140d1d7591c0084a3c0473f67a31820eb0437bbd62dd779a792d" + }, + { + "public": "02020bb0bbc4d67e4d858c59291e05f8c11e4c36373d66ba96a0cb529fc842bcb7", + "secret": "c750c44960434c2257e54e95c5dfa7c9c65477e0e90461f9dca34b784ad78d9a" + }, + { + "public": "0331873be2872abb977522065b6666fbde8e939d1e4217fd35ed4b58fe3a071388", + "secret": "b72980be9c2962df3c6c3cd7032b624ea0dc808d21f2a754d594b35de62609cc" + }, + { + "public": "024b006a61bfc501df3a364f44e078e9c99d939afe91b887fccf0cb07566436ef7", + "secret": "92244b5d210355ef97970320d8b0b418d21a99b2637412ddc56d07d5c4426a8e" + }, + { + "public": "034f1516518c3f68b01bd94e9c15184ce721dbeaf75ee33af08ab5077ef309f394", + "secret": "2ae7f7fea911674e1338e4ad704784d11860251e3765de6fe05ff4025acc56c2" + }, + { + "public": "034146ed7fe4f57261efd4ec29d5605b8dbe3a2bded226e4480933d31625b19ffb", + "secret": "6fa45479df9521372aebcf237e87d0101faecdaef2086353692608b89459b32c" + }, + { + "public": "03df7497457b3e1819641cdfabde62417b4dbcca47155065ffcbdde3f7eb83bb04", + "secret": "d646e8c6e1434c9dac1f90f14e840c69cee83ca4894bdc848da6cc3464e41de9" + }, + { + "public": "020f8a1f8817f3220270f0f0c8659e69f94c58019966097580a8dfaefd153ac9bf", + "secret": "204d72d6bf411301067bc1e117e89ca8cb74daa87b5a7ced37b010d69b08f627" + }, + { + "public": "03dc7ead56167dbe951ebfe2475722c64407c12530946718fb8790a56d6cabd4f0", + "secret": "1ba08a1dffadb0047e37ea85bb31842603a99cae49ab17de71c98c32ed28d320" + }, + { + "public": "03d49f266dc86be56100785451ec0910506fa74c64acc54e6092468789a595c2b3", + "secret": "1094d749d1ed0632a62e2d019d63f59c6dc76bd0a0fca0ab746d8eef2f6e47e8" + }, + { + "public": "035e382db2f83cdaf1bb8b464dedc4ee66712d9b8aa4304d8c5464d7cbf1048986", + "secret": "080f8e3d934bd5ca6b3ce67b43910f19f14e44a36e0742c56ac9cbc16284b4ea" + }, + { + "public": "02a2931fbb5335d03699c3b779b05a38b939aa49145106b97736cd3f7d71d6fbc5", + "secret": "22a9ff86b218b3aa99ca37ac29c9c5ac2c1755c25e81113280facc5ca9cdef02" + }, + { + "public": "02519b78172b03c879189b1ee47772e9259bdfc28c72333927186b61d5367ec3a0", + "secret": "a862f2f887b3b72736873d48457968c3a4711e697177a743347d6c886d8ab722" + }, + { + "public": "0363688ed3e56e5664f5992feff6d9e0d387db27e1ead02e78cdfd65e881e023e4", + "secret": "6a45049dd925d171ff7163af49dc74335ffb024fc29e56c48c77f1447a446edc" + }, + { + "public": "023059de5081ff8e3648d4788db5555d4a0c62978d9e6978783c0b35115e5459d1", + "secret": "824e7f47fadc615a5b64453543e05df9e254552623460f3d36c4bcf5b8e104bb" + }, + { + "public": "02658ca826da63394389f31613c12cd27c4c5a2a48349a08c3ed7712ded7799df3", + "secret": "b852b9262fae46786d8a30b0eac86f76a1857b4f0d06f8a3981c233374b449aa" + }, + { + "public": "02fbcd10984523e9a0d0811afe7d339b4d007c3bd9ea8581ae779786f6f430cad1", + "secret": "d642edd3d2a10e05bc36183d4906db850681c2f0d9f9e6ad9be294328915ad2d" + }, + { + "public": "02a4700229ddb2337a5afb96108e87e6d8f33837232040877f08e1f88909771d38", + "secret": "fe51943439fdc52509afd1a0a1c9eb0a7d944f1664b42d04e9c3bfb67f0e3ede" + }, + { + "public": "025f6e04f118e974f92aa2abe68f3192fd5ce4de80daa89081295b66c714904191", + "secret": "36d259fd87b443f778372382cd2f3fa215496152535245919fddd22873aabfc2" + }, + { + "public": "0232f55a65c7970c4e36912315c8dcaf5aba56f1db10c52fabcfaf1f1995d84009", + "secret": "43f223955174186175b061195855ab33ec463f793a2fc77432e1d66d7d002a7b" + }, + { + "public": "02c2254655317e752cd872854465909c1fd6aca35c34dda1c9f54909b35290eeb6", + "secret": "b14d632a9f54230a54e4066cc22d36dbbce06f14f370b04503aa8b0c97fa518c" + }, + { + "public": "029ec18218cecac443f6ec410faacf471291b780c5f82f55cf52772291fc13dbf9", + "secret": "afd4cb84847d27adfad8aebf0d15056ace09fe6abe8c4447838993e4dad71c9e" + }, + { + "public": "035497bfeb18f0a43a59558344b986442f3da4b9fb15f62d3398deb5531f2c7d84", + "secret": "a3b5fea0c5953d1b7e783f53d9e2afd9d5e7fbe33ee7ab2deadbd0a5b57b65e0" + }, + { + "public": "03a2c4f3e05c0e6052f99bcbc8f46a13a31c7999e4f85d8a8c1187902b0e5cbb17", + "secret": "5b8a66c09cc9e502e21c6677485f51468051d420de7471490bd19f17458da2b9" + }, + { + "public": "02f43d82c482e42436c98cdb4fde6c987ec574f75fb6a60ab080b7ca1b2d55d462", + "secret": "867aea8ba2ad5405262c7e3311c250f4e3e104632b1dab4a81b342cc4afb365b" + }, + { + "public": "02f99bdfda37597b62655033dc71523f35a064c2534c19ba3f7d0398ca6dd9920e", + "secret": "2ae64c9978c817eda6a60e72e5bb7385dfad37e3c740f01bddf787ba1b1527ef" + }, + { + "public": "0337bdb54d7b13ce45614c43e8c8ed9d814d9664bf41429d95c78d578dc9517203", + "secret": "8b81a930cbe86644cbfcfd505dd5c843b5cc17ccede14257c0e9b80e49d0d3e3" + }, + { + "public": "03a80f1c19a58fb9613826192518c00398d51c6a6b99d7aabd2cb6a36b633eb544", + "secret": "1ae36c5a3866034586cc41e8266e00bc7f620a297632fe3ee68956cf395ebd5b" + }, + { + "public": "026ec014d87bdb73cf0dccbad954125275f5d9a4e3a20a1e6b1e7511108d73d877", + "secret": "e8b02b599a24a7b8b18bd167b0b215ee6d279a19addb516691ab0cd75dec48eb" + }, + { + "public": "02b3e4cb3adf02328143203c99617117272de385610e4221c56b90c8b0ff36c140", + "secret": "eac6728a24479c99161a7ca99661fa3377ad748808e664b4d61744521bfd1f3f" + }, + { + "public": "03921e61fde8204a59a47a4d44f6e826aa695a49f93f253900fae620ee4ca6f687", + "secret": "30b2f9db502c396281dcb613434fed52db7ed183fe296f548fc0e89cf2e6500f" + }, + { + "public": "023b52dfe893e8653d78a4a9a28805a4f8c3e7d59c4b871c6a9ea2d88264536fd2", + "secret": "5102a9b52dcfc2c7dea66eb48509aab97a2d7f709c26d24744e5e9aec81983bc" + }, + { + "public": "026b17ffc9dee1b7fe8033c04578a12e799a7adb6812caecf1a22a1b3f43773e17", + "secret": "5079dbfaf09e635640aeb8f513209d23bb3c9537f79f7e8709125fd15edf721b" + }, + { + "public": "0230080a63a601a54561d0fa999c3c6e24b611e7097cb6eea01f863724340d667b", + "secret": "25692942d7e0b504befd456cce757b636e63bddd43f94783dc99d9719c6a258b" + }, + { + "public": "02738854cb5aed2d06592fbacd44234e7bc1c5ed7c37a4d4bc95f56c214d58231b", + "secret": "d842ae9b6bf641a26987c542579b310f9489eaa7fcd2d6218621de505fb73664" + }, + { + "public": "03af08361b121feca05c70a4be6179ec9dbb3599c6bff5722006bfadcbbde7d88b", + "secret": "da187a426dce79b25ac33521d1ed513cf96d4cfdbcaf28f05d90ec325830c096" + }, + { + "public": "02a8d7affa14c4e45a604771e779c09ef7cbeb1cd2951adfa8367e56fd49bb1936", + "secret": "c9b197f1e26e57c4908903f0d2f846ddb899d0aa846b04eafeeedef75abb0efd" + }, + { + "public": "0318798eea0227c9076c0af1e1bdca5d3ce0f995c26d55047d8cc776a39d633ea4", + "secret": "8b5e2babfe832bbfbea26eb928dd05fe530b4197cf6f6b5690b06196f2b23675" + }, + { + "public": "024cef44aac093f57190cc6fb98a73b3af8e03ae53820be66e39ec270480dbcc8d", + "secret": "760ebb7e496941a89e26d7f524d569188860a75c7fc0a6e8ce29c8478901ba3c" + }, + { + "public": "024d4bb52ec844a5d8557be11eb0d8e40465afb962d0352245339a1813d3d6a5ed", + "secret": "620fc9a9fa057fdb27c5aaec214ad3fa77d65278311ae6b9fffabf819aa25ce9" + }, + { + "public": "02f0c8f6bd2253487dfb8a0338569e086eccdf27f3d7c3d1c3d254ecd44887ad66", + "secret": "ed3b7603a5ecd9e360b52c0319fe4afcf9aff24a8d3e9a0cde7b81bb38e77e44" + }, + { + "public": "0288d6816c87eba46c1b6b5f0a21527ce97f5a30082d4d41dd2ebcb4f1494ceb97", + "secret": "1fc5f583890cf315e76c381ff4dcf974bef19cf6a3e8b196a55448973d7768f2" + }, + { + "public": "036dc5cd8e0e396b2bd3010af534fd8628275c47a1e69eaef3795d3409ec9a51c3", + "secret": "2b7c6ef1bf86f23452367fcbd443cc54ddc99a4ffd087024fa2529ecac7a2189" + }, + { + "public": "038af1a9ebe55182721a6f803fa1b0ddfd52f96d7b4319d7ed66ebe1f2037d89cf", + "secret": "27377a51c8dab4e3a75b99355b815d29d4a16078b4daee7e4bb23c8e595b3c5a" + }, + { + "public": "0378863404bacf5a997c67671bdfd0a447ad3b7b7a8c7c8e94bc0be4cac3f1b668", + "secret": "774501762991c36be721a84cdaff3a3b1c22c404be6daf26d81b2984805383b1" + }, + { + "public": "03ab47378ea0393ee3a24a5e8febed78f0f9dcf2059c9d1191a40356d73502bd62", + "secret": "c5c2a1e00cfbd1973d8129a7e6c7b9a9a5710880bea8d25fa0f7d925a80afa71" + }, + { + "public": "0284739a0b5b51028eed87181583a75a396a710560c94554795dabbe0d53d66f62", + "secret": "3e2d02f86f8ce020b24d71c9f912e3587bdb1978e83ef96071b0e1459f075022" + }, + { + "public": "02cbc6619377a3058ae96718c081ccea0e34103c01bf5653e166de12a14d2e9d8a", + "secret": "ca27d4d68a70d8bb6cfe671dd8e40a41eda66684b764034c33fd98433c868cd6" + }, + { + "public": "02f90585b3f83cf0273d0698c04e804769e627642338860df3921c7d4fc72738d7", + "secret": "ba47e26e932cf30af4acf914a69746c210f819c2ede70f24ebadf1f87b423359" + }, + { + "public": "03f33f1bdcadf7892222b5db52a92e2a39915c23e0b6fb94e57142e8b12ac9ca31", + "secret": "98ddb1de992c752a058b24dd4a41332276c24052464a186f83cedc0ac86a2bc0" + }, + { + "public": "02387db97ef3e4f6f1383dd4377e06c6b9d92c8296e8cafe1431bffb98dcbc6588", + "secret": "9532bdf5aae12cd46c8093c7a8136992dd3bd5e928bc3280b7a3a756cd02c843" + }, + { + "public": "0212dd23afdff7911d287fccf01906475469db6378ac00a47cb2c0ea7edd6beb10", + "secret": "995951ee9d3d56c215d8df2556b1869e6dd4536b50ffb34ab30642e9bb414428" + }, + { + "public": "0212f714c076c37d1a06e3794f00cd1f54dd726fddb18c5661a276de04db623bb6", + "secret": "0019d6e3cfcc8ae738e5b0fe6c0d26e07ea3bea9f8a6ae6d6f85d5b980b41304" + }, + { + "public": "03fad28f96d814f45ec1e09d1a9471cb413119ca603294508286e61bcee701c7c1", + "secret": "bd4881d599ee494a3c0d2ccba301727282aa4f5c68d8e8349a80e172b14ceb4e" + }, + { + "public": "03045d69ac42a1c35ee677a2d001b459cd44ba2da1c30644d7973cbdefdf917bc7", + "secret": "c6545f099d6effe96edcd584e5cb98845ee756d6d9ad2a87e010afbaf5c60670" + }, + { + "public": "023361338429360c13d56d633f99a74c7bbd83419a405bbd18d9118b31946cdf9f", + "secret": "61da2d2d2211b1ff9c3b8d771109da3da855ae27cc2c82fb7d3fe56af69a17d8" + }, + { + "public": "02f3942e42022902d5d1ea01d5d6d3ae57bd0d05e76580f3c18996f20620f335d3", + "secret": "f03d4c4ab3abd036f5c1a0112e72a9ddab0842a0d90f2a70c7b1ff67018d163b" + }, + { + "public": "02c5869f3c309df56096f408d21e1acf11b882b79a6f15bd4e0e0a944db0f95b1f", + "secret": "24182b54ec7fb735d6aad6f841486a2c19418715787854457cd3e38a755cc7cf" + }, + { + "public": "037013990356d77cddfe95553672ec3489f5ebe5b1b3d7299d05b13b3ed4e01aa0", + "secret": "9fc9bbf52ba96f20f69ecc3bd08deef45545f07b6dadc33ddfaf0726fbef4a76" + }, + { + "public": "029af59af633fa5c968595507fa84359c3b6f3f66fcc16f38247fe65daefe68cdc", + "secret": "e1669d5653aa217278fd688c745c22035739122f6ee693f14769aa42081ee41e" + }, + { + "public": "03fa9353ccf2b4ad38a7b59a9578d048bed7069aebe4651d9d730449ff4cb003ce", + "secret": "a21c73d4ed3c8e7597fd33cae453296c31caf4efe94d97059e82bf71acbe27ab" + }, + { + "public": "0367eab4da5dfecda9a2ba9067fe5fde358712aeff935db7a8a226d6ef75a0668b", + "secret": "7f9f4eef75892f0b21871f650eb94ca2d69865df459845592f7dfddfa42512ae" + }, + { + "public": "038cc3aa0102934236ea55b71d46ae35acb6dce82f0eee1031bc94986f91346732", + "secret": "b99caac70ea985ce85cd922f6e2557653ef4cbff404892eba6847fb8f8084857" + }, + { + "public": "031e91a8d59215d8cb367d53c3b807809008e11d3b5c378bc08911994f3ea9ca42", + "secret": "4bda9614a5031f0953af8c14151efa22f53a3fd97e34092b0503dcf7c87e17f7" + }, + { + "public": "035d846f1318796c623e6e2d8b41af95a37491fa0e1730d67163421ae952e98d18", + "secret": "c212f3c5cd51122c827076af43d7d6f44c76e4c4088a6b53a5f8166f19baa779" + }, + { + "public": "03a89536e27eb2c98fdcc77abdba971b323cb0b1552929b540950177067a90beb1", + "secret": "a286750f88a724d12d0c5810c559bd12d6058651e10b35d7264f98561335392a" + }, + { + "public": "03d155335fa57bcef8ea4e139a6b8dee10935298d93cb03054ec295bf4f8df2746", + "secret": "c19e423957fff1504735c924713e2ae40ecfe1f43f24a7b6c271cd3968079c35" + }, + { + "public": "025215514bcd97b94affe23aa4d00bfeeda56d446a6f9b162512faa3f7fafc75b4", + "secret": "9bc192a4888d51f2f3cb8420f62558ef09752251e657d4e72a83e4fccb6d82fc" + }, + { + "public": "02c8b55ffaab5ca56909450b1929939aa4d0587cbe1d92c1f629183011ade8171c", + "secret": "4a5f07b68ade18a968b4f8c96e066a3fc80725881a1380be951125952644240e" + }, + { + "public": "0366094633f8e241bf4cf2eadcae2276715489be91c08260a0eeb6595328b813fc", + "secret": "9d7b59c2ae0f969c89080ba7fbd7d5152ea9d5e8f02def48e5a48ce0c4d6b616" + }, + { + "public": "03d418a480d778c8efca66d558afbc7f0a321a682899da7d2436c2f47ce29027b7", + "secret": "c5a44d4a367607356d13adb4c071c1d821e6f741ea480a6ab300677091173ced" + }, + { + "public": "0322ca0229bc2e4cc6df1a49b19adbb935fa5effb7bb96a79b9a5f2e26d435d3cc", + "secret": "cc294a6f76a01f30377c779ed153da023511548d0917e98651a082145512e550" + }, + { + "public": "037eb505d95f221504185d800d6c051756337784e508777feae1d772d97ee7fe36", + "secret": "ce95ed6ffc07f1444f1db87865a64fb12d752eac7f4e5a83f4cf80a807dabff1" + }, + { + "public": "02cf8454d9d092166db8806c01c9b5c1889964343c522ce52f11477380eccb4f03", + "secret": "f3c70f5b7243eb6cb8f68376a6ef026ad3bbef04dc0f3262d25621cee4dac1b6" + }, + { + "public": "027e5cbc3b7824ed15e3452c1767e923f17c38cc07d98ae81279b3f442767408c3", + "secret": "f6fe97ce9fc87ad979b3353a694e2d63ee7ebeb0c14a9a9d56385921cf58db8d" + }, + { + "public": "03681dc8fef2c38df5b29cf90800eecf2c1de362e7373cee550b3dfdfec8dfb2fa", + "secret": "9c64b224a0abe8fc2d5632b8594018fac5b3c7b98088a97536d3dd969b2fbf3f" + }, + { + "public": "03178f4f5d96bc852b1ca98f7b1c78456901a9397abd358981f5d5855bc776fdf0", + "secret": "32fdfcc9e3f05fc41f6815bfa0bb3946eb3a8a216fbccdbf8cfbf322d620b0de" + }, + { + "public": "0288a6c657264c20fec3aa730410b7f935dbadfd5d056b901e02338626457e6cdc", + "secret": "acb1453ca6950f663d59391f2335c43d021b25702d92ef51901e6a4f7c9528c6" + }, + { + "public": "0314ec878fb5291ad96a5a63ed76a5631f1a87d2127160187526fa0f89debf8069", + "secret": "eabf297d76c5d242c99f037bef8ecef9fada46a29169f1daa9a52f1c6ec07b49" + }, + { + "public": "021d6ba3d1a2dff91c82e6e8126076081a8a046cfb5342e975271b48615e1ce3a2", + "secret": "ba9dd14c8db2352adc1cb617250d0ec8487db391cedb5bb13e57b3e5b053ff39" + }, + { + "public": "02a9f40d8a3a9245043e4e8887a623f2da301238801a596057595763d3d7c572d0", + "secret": "aaea654c95217137c7b2e3dbde2b1e50e2df7e218f8658ee0bce2a3ffb0bdf64" + }, + { + "public": "02f19c35bc1c9c75c8bc52fdd05064c7e92de14dbdb1bbe141943527d4a5034c86", + "secret": "40330a50b9981e22c9722b0ea660470689dc293b8773b701fb729ba6a9cabab6" + }, + { + "public": "034442c7ade83e99556b1acd8c54cb4ee06a0cabc30750b3118a28836be5ec5432", + "secret": "00e9370af7d8ac8e1fa8bb207f2961d48cb4ced48d399daacf2d5b5c9d10e3ed" + }, + { + "public": "039569a5ed2f2d0443106cc0d43d96afdaf04fc21192977cfae89c92a733344151", + "secret": "e51ec8b93ccbdbeb3de904c847d012daf4f4f5ed0d88d39b2f7c90a4490aea16" + }, + { + "public": "021aa508c5b259f6e59f96e1b95843a5f14dcb4592f5d39b826e1d8d21d0dbab6a", + "secret": "3f495caf7a240e5c374cf5094fc80114a3e648254d460d0217baeb8dfdfbbf3b" + }, + { + "public": "03413f39e0aabf71e2ff19e645e50711e389a7372079d340a2e6e6eca568d244d9", + "secret": "90c8b0f3f6a55f41fdc7b5a2edca07ce58369ee98d380b200390af21c53be84f" + }, + { + "public": "036403c8cd68fb89e22bb3ff587250edf1d8aeeb3f70815e02574a6520fc0b99ff", + "secret": "e7a68f6d002734a8488fca43bf7ef960712556d5c61c84a3c2a0b5178509fcad" + }, + { + "public": "0371ada899f5c153bac46c095b950ef01e682a5c027e9c2ab47aa68e7acb35c9f8", + "secret": "93bf75f49d95ad0d8c8c0d85c02bd719fdd80e46ee88373f47728fe0d6827c60" + }, + { + "public": "03319eaf44cb8798d0317c2686bcfbc24f379f4ffcf391e36b7b74085a572eb471", + "secret": "bd80e617dcb56d412f240665be17297827278dc0a7d5a93535209bea11c64338" + }, + { + "public": "03c162d4d901a979c8ef10a2cc929cfeeec17e2ce2a17b31bef11fc0b1b219d635", + "secret": "e4c0df129fae550a1432d894f83577abb56f21ee7e7827caca6aa953bcffe9a0" + }, + { + "public": "029a52c2cae38f91ba463dc1c890406bbbfd742d9d35331dc1a5c7d71b01a935ef", + "secret": "347e1ced09eb386584091859195d084bcc0419abca0a558f52eb58561a633341" + }, + { + "public": "02645995ac9bc769d977593083ef0d295c88d3ebbd01116b7d3ccc5fb3e97ad9ec", + "secret": "f66e5f6d0f5177d98b30a1e13da3c61324eb42a2cf5f1e055efd5b53e3b06836" + }, + { + "public": "03ffd6ae62195f42a34b6c13d563d9b15ca7038357145bae14c1b8769fbe181868", + "secret": "1683765be0926c3d7fffcea40fd17d7b74d55c2b296f2be38b04eeb8a5f2c1d5" + }, + { + "public": "03e9b0f0b8157a3716c78b9b55da2656fce8cf683e4930034920779632b4768c5e", + "secret": "27a0a68e1248e4e71612234e0f5ebd9a6eff7206f065f1491d05d12de037b2b3" + }, + { + "public": "039bc48404275297c5b3163a8a0b12594d2db067b874c7d44732089de357d89c6a", + "secret": "ce3603e7f567f65aafe5d2d18fcd75363639e088e58fbbfa4e706a7e75aa7ae9" + }, + { + "public": "0318e6704e0b335b691c37a07ae1137b35c27de78ed2b9c5b932ca8c1fe7962c0e", + "secret": "0849eef00cfd6e9bf4100628bf4753acee1f5001a9c3f70b8a7a3caffad4f22a" + }, + { + "public": "03ef1f44e9c25b9c0fad95c5e6daaa956e7495662ba5d10490066fbe96fa14cb27", + "secret": "b6c157b710c54a24883be891727321ec67330da5d2eed080b70e7a3695a8ec61" + }, + { + "public": "02662b846f34b43ed865644eea7648e537a924cbbc2841644459526fa90794ed7a", + "secret": "1a0a81c90bbf3e1f93d9f63424e672f0b578a9050a70725fbad07cdc1ae71392" + }, + { + "public": "02c399b5c9c19229136b7a3b4ec724b95cd63b9eab5962d8f1ac5258aa3335b112", + "secret": "d7f2f13eae3b4b37c15fb33b752c1b11bc1c8a2fdf72e6557563e350ecc0f0c0" + }, + { + "public": "039ae757a337f58c6d4f4ded2fb78d7f4ff054224786fee821a1793cd1fb17124b", + "secret": "f8fdd55f46335c391a179af0d0e5e6a4ab0db6801296fb6491b2ea69bbcb73fa" + }, + { + "public": "03b69fac4b3a303b0e5b6ebeb707a04c9490f546db8174020a0debbd6dc3e5fc3d", + "secret": "2cf4dcc374e5b8550098915bdcab4867ae82640f2e724a0ed612eaba8103a62b" + }, + { + "public": "02ba5f2a9dd19c8d23a918036bfe7794a073d4edefa26d79cdd42418a20e54d48e", + "secret": "cb43f8da85d4ab967be28d8987621338af82fbce72210fe123abe9152f237585" + }, + { + "public": "035a20ccca67b2a04fafdc4e627c5faa30683359a8214ae4551234d8610e51f035", + "secret": "13af535bea92b53d481cc4d1ac9fd2a6a032925c56bff5ff0a30cfed8e8c74c4" + }, + { + "public": "03b82d4372ae5b3bb7073d1cb9aeef279df0f9612bf8e0f8ea9642748c4539389e", + "secret": "57a8e301c388b32a19fdc9de863daf28fbf113280fa43a4329e8f055b84b1c9a" + }, + { + "public": "038a293cfc29c01e6c29f04e6d470976ce38b4c844a1ae6754cfc295b374f79b46", + "secret": "80bff2902ce1d84956c5138ded00478d1b8c4696ee33df10deb48efd8ad4769b" + }, + { + "public": "03dc283ff705a15c3507690bd52654c3c6fb7c025855906f3b4da52bcd423d9717", + "secret": "640419d7f064115041d3c9ca1de8d736355c7163104d8f107faa8e90fb71ec44" + }, + { + "public": "03d84c6397fa2b8704701cec2b6edf3d734e8e1c9bfa74ae5bd3ac26df0dab2d33", + "secret": "f35c10a69e25cc99ad33461f556f479ab63d9602d469a13dcb20aa37e39b5cc6" + }, + { + "public": "030c6ff1da272c92d99cdd73a9677c913c4ace1400e94c22d96ab348ba0975f45a", + "secret": "79062b54854f7ae03f50ded9dd4519b82dfab46609f082db898031ad96dad9f2" + }, + { + "public": "03b05feb3c56cdbff2830a3d200d56464143d76e36b53959566c43a84ce58b09d0", + "secret": "2cdb7bd144d54bbe4966cd2d285b81733d5a5d15747d7ccaac059716fda48ffa" + }, + { + "public": "02f1a6e5895c67f1f929eaca6bb6d63216f98178c75b5dd950a673c0c893ee3561", + "secret": "64541439a1d4889f33ecd2ffe93b7b9f9ef81faf4ffcf06d03998f189c8f8042" + }, + { + "public": "026fa46c6a361c5bf4946190b0d982ced53bf926ec2136d6fdbfb4750198ce0c2f", + "secret": "891cacde96211f0b70efa8c84fc253ba2dbf0280fa02619b8816b58bdadd3be3" + }, + { + "public": "035e86334cf8f455dc6760b1ab7dcc5cfb79c6187a7f0faf8b439a15013550c0af", + "secret": "7e64e1c40e413f8038a84b077843a8d3f092b05ffd573ff5eab8ba3dde57ac6c" + }, + { + "public": "037fc066b8e6134abc5aae9f46a20e9f2e48b7b3f1af8fb009907dc1a7796405b2", + "secret": "4bea2618485f906b3e0055b6ebedcff5c3908191a3ce6bce2e84710537c7e8cc" + }, + { + "public": "03bcea9c85ba75cb6ab32b957abdcbf80e02cb400cb496bb250a20a49bb578e2d4", + "secret": "c23bfb63ff711f0e0bad2f18776ccb8ea847519fc0b2bd7e1a640b593ad02d8c" + }, + { + "public": "036685764c25a4b248f63f09bf3fcd4e54551b10aa63a01bcca89f233aca812547", + "secret": "c7a79817d7ca0449326e76c82ed80f87b9d2826e80f5c806e1060b6ee8bacfed" + }, + { + "public": "024df9aab08d8c8aa7f62f3eeb9a874013744f2f7ab9646e63bfd5c67fe6dbd27a", + "secret": "de3e77755e133c72207cd76e8df4bf860a21777fa64181475eafcdf17ff1305e" + }, + { + "public": "031c90a518b738065ad07f0a4b15c28b439a6d97b8ce80d14700b19bdc411d7ed8", + "secret": "9fd45c0127e2b1cf3fc8ebfac57bd5389f0ff3e1ccce1b87dbc77d2773b120c6" + }, + { + "public": "022641517e31ef3e6fd8bc668a291d5606457edf9924d6c8a2d283063537db2def", + "secret": "e04855c24c9a7d8e1fd29a28ff98d43868cd8d325625e4e5ea301c15148b9bc4" + }, + { + "public": "034187a061b91f3c27f39e79e4ce7387bc4cde5805ae03b76697724884a184ddef", + "secret": "257df91cd76c11ee5fa713b4bcce19cddd4f1795b519ebf7235713c390745b81" + }, + { + "public": "0310d7016b936d2fdf05154b0de21baf174d36c8089c121ba41329bd9c8d7766ed", + "secret": "4abfff2bef18f0d02b8fdc267542006cc21d3e53c785ede4eb7ae45446447d1b" + }, + { + "public": "03023b3606d96a9c4069892bc4a7ec22e714ab481d989a194ee69cfe7f2a0e9fc2", + "secret": "8abcdd0c7bbc7c0304774f5ceeea8c0d62c54cf2567f8f12df39d997e66ced58" + }, + { + "public": "035dda27ad9896ab126b335c6bcc37d5f8b0ec33681678425459dd89ac40a68cb5", + "secret": "63e81539635eef5ec61e931cc5cfaccfc35033b926c8a381e9b9b5df980c975b" + }, + { + "public": "021e96330e247dd7590a1c6fdfbee30ba10936be27409df8ac40162d9f01dc9b1c", + "secret": "fb775db0e539b3194e801bd83a1c072a11da5b9a78ff1cd8e15e3157597839ce" + }, + { + "public": "0281a17a338318d32c96b4939de905fa07fdf16b41425e1c4668bc66ffcc6e4f8a", + "secret": "f424302468cf154b91d3acb74d1c134653dd015a0f9f1e9edffe71ef02a72f81" + }, + { + "public": "0316a8f143c8136bf47c331b05105a898183f6b8f718f8f16e0090afb9cc11de06", + "secret": "f2a8bd6ff9cf03ae02b4d4e3628ecd25c8b0ab4dc2ed84f49ba8e2cee15f7326" + }, + { + "public": "02d13eb98566c22a73664167203545b26273c48e08e7ffc97208f7b0f296683992", + "secret": "4e283c72c46098a23549112745a1d01964065d7972e17d12e250072d40e2d830" + }, + { + "public": "0324cc05ea94cf4130e9071487ad4b3c8696f5b871a252714d00cc8a3f9eab12ba", + "secret": "7ce9e71cee76d18b9d6f8bb4ac0637518b357114c4ae57c2b0efbe0830fa76f6" + }, + { + "public": "03e3d31458a1ff3bdc9122445301ee3c471949944771dca3f29c8f5e83610aafb6", + "secret": "b5044203d14903f9b1ee9f07047e155ff0049683b6c17752782b3d3b4ffb1672" + }, + { + "public": "03b8a1559f63e7c8267f860c7d5a437468c982bb9661345121d3eac401d881c893", + "secret": "023b7804e840e74e60a7f8928a17264348418ff8e7f39822f6365010212f3516" + }, + { + "public": "02018035261b11ac19d1ca0e02bfb1ff84c4e28f482d3e73bf84289777ab3a413e", + "secret": "2eecbddf300877fedeb37d9f9a45e80f7d197d570288db35bf556b95abbb90fa" + }, + { + "public": "0365b09a11b7b415ec2dab8414b6dba8dd38f39c199f0a60f94614d40233e81641", + "secret": "5a8bf0dc70514dabd1e9d05bbb8bc653d85dd6e6488da17601395af9703bd66d" + }, + { + "public": "02ce8794964a862827ca664c8dfa89561b0c170657affbb11314e9c7d5541e9010", + "secret": "089371f177096466ab45e3eb759c47fa1253a640f6c83108304582624b53fae5" + }, + { + "public": "037639454a4d562e2e9f2dfb257f378d1dfd3157b19c6bee0d13e7a9e9b434bb28", + "secret": "2153ea4e503da01392ab38f8645cf445e7c2fd67a8e1629e3724dc9046af5c6c" + }, + { + "public": "02c37a0c384514521174ccef6f49db2fdbf7779ecaf9afb9b5915af223901f4a19", + "secret": "485b00997884d4dac342b0fb357a324c9bf3ba4d8c1cf0faacdfdb7d53ea50d5" + }, + { + "public": "03df2e3a82c41252b8f3eaf36271faaa8046da4c2afca3997315105bf946f9ad4d", + "secret": "7f16469b3d590e36618d653caa9de9bc88907b9bad2e2f1bb895894b2160d55d" + }, + { + "public": "03d05cf435bbb9aa47051350577012464a4c56af1d53abe05cff393826058c9b59", + "secret": "762619ea7dd01115d22920076fa1514cb48180e59a09995b7bc3a1706d4c38b7" + }, + { + "public": "031898107a97b1b8955a05b809f7beb6dc83d89e4098e7e2baeee89e58067c3c37", + "secret": "4c963ed553bbeb42e9a81416a58d7ac462d95d11f640c61f78c5ef7149660453" + }, + { + "public": "02ad77d74fb04dae512ad515acfbc3de01615f51280778a3f99bfca82281842a77", + "secret": "ebb5c7ba498cd56105e81eb3aed91e31d1df68b04eff58361cb58ebe4d7bb575" + }, + { + "public": "02322c38c5ed799b00c7c53ac45e2f175aa3a054d7a9ce153cc9774db3fd061a62", + "secret": "cbf4918909deec40c7625446c46da6181c15979b7d10d5e3cc5aa9a68c94571d" + }, + { + "public": "02ae8af9f677c6e145b38bb5a2474211c8fc7beed08d936a80900c1f05656a0325", + "secret": "c47bb08853f4fc3f9b59c1787edec7f38e469c54a9fea9bc6c7de24a6d0527ae" + }, + { + "public": "03d4cb54f3cbca657bcd22a20592ebd3c05d509bb23d6781df330dcc19645ec7d8", + "secret": "89b8d1f151c997b61dd14db49222e65ce2d4c308ba3ffbef1d6c386fb87e012a" + }, + { + "public": "0221a39d27104cde35adb03b91b321213e2a168c0bab56e3de1b7db33c92980a5b", + "secret": "f36e4fc7ea52c00226ba5d18c7df70aa26b8bebae037cdbaa4d577ae2e27f67c" + }, + { + "public": "02ca49be7e55768918409f7ab13a733ed8cbf9ca1af07d7eec56182f1deba7479d", + "secret": "25e6856c4c136f3cb7598fe1dec3be0560350ebff4382d677c0cd64b4f599a31" + }, + { + "public": "029f5774ae34d1cde91f75d95f69f5e63a5d2c06eee8e410159c9740ddc7908613", + "secret": "594bdc0293e51d51a6cfdeac68d1a546d357965990c215aec5652416893f8e95" + }, + { + "public": "02f0544d242633678897541b58cd76808a62197a49a2e724ac952e1820a3568806", + "secret": "a673c0e2a32bae6e8db64a9c7f5d7f67ced1e098971bd565eea2b9a991143fdb" + }, + { + "public": "0390d6f70c7168122afe96466d182584b48325b432da5a164c2d9b6e8e183e1312", + "secret": "44a9b399339e8acca1b0e3097ed61fd8139b0e8f9f4e6763f4136d5e8f921d76" + }, + { + "public": "0238a50e53d7a800d4bdbbdd7258650129bd74ad824b5f1d62451de6610df930df", + "secret": "2f246af52c80bf9edfba5b2a00944861cd5ddde02aadbcf8f775a59ac0209cbb" + }, + { + "public": "032add8143960413f19ebb601748ea23cba5cd21187d6ab6d9d7109a95f6c34fb9", + "secret": "127e1a83e2d30bd30502f91e862e8ee996bb18f0f5059ba1893fbad2248a2a5f" + }, + { + "public": "02fbacead3f2f33b68d8846204f42a318e415c2146fe730757116f70e3935b8ca2", + "secret": "0093a7161219c4e0ca92c547c240ead06f42f40e1dd48aec8254a5f440563800" + }, + { + "public": "02cdbdff5009ecec7de0c9bec2059feff645d00be0b1c53a287ea9bf01a8626860", + "secret": "7d892deb6002a5e399bdd3265e1e5a6b25040ed46bbbb982f52b666a2aa40b86" + }, + { + "public": "02531e5b7ab16d9f4584c1539c0d3457c223562eaf65a673b35e50a1df25391855", + "secret": "60fb83a9c35a821693e851acf2d28c22e5c9326239b2f529d5dae6fb809c963c" + }, + { + "public": "02613e1052116f71714f15b8f06e35235e914b86e28b52193ed5e18f4c9f4b348a", + "secret": "7deb96d8013cbc4c61e8ea4a6622873de419bf3e14bc468df205ae6349a38d06" + }, + { + "public": "02b7a861c3766e251cb76824873f3ab059b61ce33ca2e7a947781fbe8ed8d05818", + "secret": "000ca2e445d65d6266696247865412670f86a9799e5dc3d32d85a7febd1fae1d" + }, + { + "public": "0294d942df66f3cc0548dc8f0e26d1f8a8e06d2d3bdf193be3fa27d627bdcf878a", + "secret": "d9c77fba6408c7c3d14725835c9516275a22f722d11a2ed977ad423b3ff8ef20" + }, + { + "public": "02ca1e42331a1404e0a3fd8deeb2b456ac91f48bb21f2d90317c7bcb9799feb79b", + "secret": "8f8976da7a8ce04696e26e1ebd9a2fd8b0aef2fd766bb6dddb3a15e9f190cb78" + }, + { + "public": "02acd07aca27bef56673837dfa5f4a3c7bf2a62f368381aba491cd01f449df0d08", + "secret": "db571922922024f3e32ae6b7addbe21952f949e084ea28d075a444e949ec3845" + }, + { + "public": "027a8f49d1eaeb1f1101d99ce90655c833fab34262a09b2c6a7e40550df9a273cd", + "secret": "76b34c1cfc2a38de5e2d8880e03bf6c06a2c36401a3168dbbcab40f7d285c6b5" + }, + { + "public": "026ada92df18c4fc501d396368ac3727a5cfb64485b2bd9cdcdb36f23fa2334f96", + "secret": "da026343486fa5e0d49b56aa4388bf7e4a36af7a9bbf0fccdb496e2758e6e5b4" + }, + { + "public": "0284e4e753f36b0c970bdb1b0d01afe8c65329f5d2db39561df5dd5a0718fd54d8", + "secret": "1c7aad6e1660c65d3ab44cf3ee57e66547aa00d8e5ac8226eab89982e618dcf4" + }, + { + "public": "03da1b2d423b003e51d2e30262668bd8f79bee79d9367e0d58620fbb9ba18e1fb7", + "secret": "f2ec7336a0f96a6339186f7cc55dab56e1e5f2b0175d5cb2f17e5b6778d2fdfe" + }, + { + "public": "023d961a5528be4d2bb021c6e77629cfd1d137182c7fcaa159ee69cc73a1571830", + "secret": "c42912ecf9f3f0ae660766d9e73e68152bdf27a0bbc7dd80edd31f11bb229b20" + }, + { + "public": "03d4ccd2f392e0692e8cfe2ff932ce55944a4e74411943d7657956ca15a84e50ef", + "secret": "516d1bd7a32cbd31bd19d3d3a9738554279b13874061456b60d6c9d826161211" + }, + { + "public": "0250f86265a0e43a829936167674d35f48799e72a5acafe0f8fb23275a77fb4e6e", + "secret": "2f4b03d384d2e42f28823f4829baad23bfc8c39d4652e54812c0f298f1df52c4" + }, + { + "public": "0333137ae3809bb90bcce4522d46d2cbd59d00ed356adff2f4cab5a998c2b5d063", + "secret": "31a34aa4f1183dab48c61caadeba3a7b615222c333a089cdc1c87461c3f9a0b6" + }, + { + "public": "02a1d5ca53fda4356302ea7485243a93e4e24eeaca70d3bf10ad2589b8d1edaa90", + "secret": "1a109c7078757b85270cb1bf8db42c0cd784832215fd2d4e4572e96c094dc712" + }, + { + "public": "0365d5a61964d9ce78ee27c3ddcf061ed748984e7715800ab246740a79448a82e9", + "secret": "19df40e18a9b48a019e3197179f94ff4c09b6ee53a70960bf16fb35c29bb3089" + }, + { + "public": "02c4e4b67f356fc8b34995c425c54c041a9360de2243ac3fb0115529b88db51891", + "secret": "e1f5a89933b8836d4300eb090b130db5d9c58d73f8368f3425f8bbe3195ce5dc" + }, + { + "public": "0212ef0c9f959a47f54dd4ff00a6465d0bb21d227d9914fcec2bd55a4c86da787e", + "secret": "e80ef8dbf68a194288820a18e07e5e241856c4307c39803234f9351d0bba8e2f" + }, + { + "public": "03481517ca5f96d27cbfc0d5b336d9ccb5d4662ec30e97ec1ee9d6ee8610bcd30f", + "secret": "0a9d44969f267fbe5bb24f1b8f1f2586e336f73a0f051e2e328ae1c055e42b48" + }, + { + "public": "03d81bc22a85aec3c759f91e0fb14000e053c1c79c04b645f804d6d4af3094bf5c", + "secret": "7153a7c8d753792cfb068c377f145539ea8b19aedb37e12db5230c8c925cc92d" + }, + { + "public": "020e0a135582ed752f3dec3f196e5af792f16d028b8edcd7b92167a09fe996a3d7", + "secret": "2bb6a4d7442b4a2c1fca6a06a7fb1880a1a193da022d7a68d78bf48dad80625d" + }, + { + "public": "03f6120bd65ac1a01e82e1c3019eccf7b575358b103ce9989313d6aeb98da197ee", + "secret": "041267a8d8718022d2422a0500620dbd96cecdef634d88ec2b4d8946cfe254d7" + }, + { + "public": "022d47a4ed9fa5e7198338ad8cff333ee2d4fe647f9d6e5bbbb79424d5b69eca70", + "secret": "b2cfc5e140d97320a3d38daa11549ef64fc588528d767d8dae5832fff45257e4" + }, + { + "public": "038bb22711e6c3ec43234ec1c43348d000c04e8cee2305143e2228a0134cf218b9", + "secret": "5f3d291eeaaaf2ccc7186c34d94cf95f74d9377460ecdb97d37bc256120fc880" + }, + { + "public": "02c89a0915eb202b330dbf8a5269112b3cf8df1b7067a377c9263d1718f8b07dab", + "secret": "cddabe7a94e9612791f99c43242284bc7e7aafa15b119718263e8d8d39924eeb" + }, + { + "public": "02c86849e4d589220b1afde3cf93aa8e44a6fe98e1667ea3128dd04371b7b03375", + "secret": "6b21ac71bd246245dabe127843c1107013e57eb075ce618909e6c8ae1a8979d1" + }, + { + "public": "03ac7ae8b943c8123f59313c00bede40ccbe4a34fe2cda42925100bd61492f90d9", + "secret": "39bbb4542c906aa797a96cf76cceeeb64a37d998fe87c2b604e671b71046c978" + }, + { + "public": "020781d8434d3c343a9abaaeb8ebebd4b3d38fca7004856189ef0c8ea1d2cfb318", + "secret": "ee2d1c29cf5c1b419938797dd5fe277c4a8c8faf267c1dbf5270776f8bd9884e" + }, + { + "public": "037321f25bf6e7dfd711c3d389d41c3e4b5f5ac280707ed54db8780402c8b0638a", + "secret": "addbc334294266c2ba2637f0e80080599744761b7aa3cc59fb2e3bbfd8ae17d1" + }, + { + "public": "030746addc962c772fa1e271ace7cd875bdcc2cdf80fff3cb1df1de4aa1647ab92", + "secret": "9490dde430ece02ccac2cc07b24a850ea29d009d9af3a72d30725e8818a6b461" + }, + { + "public": "02208bf3c632bbd1696f4a2e3954ef65919900d615a5f95750d6c72496147e0dbc", + "secret": "230d5c5bc9217b166c32f65691567b694ae952ba521eb9b3e9a936e74d392c71" + }, + { + "public": "02ce7e22448a0870afdca6e3727fe4acf3fafbfe6dee7bd6112745be1a0e3eb214", + "secret": "9829307c02964d68689ff5701a60dff4776fc2697f1dd16689a80f1c771d293d" + }, + { + "public": "039dab9980e189a7fa4e7a644b02a24c30875bd697c2e4d20d9cd13ecdfaac011c", + "secret": "fadc27fc5c0c4d1f6adafcfa919f3816d9c0cac5fd1f3d1422ece65a3c7d37c5" + }, + { + "public": "02e5ab7edadc71d30c3cc1c89953ce57ef5b85315e04376bbace196cfdc59eb237", + "secret": "cd2256b706dea2c1a9af344e7e11b9916162eb0700ed08ff01a89d5956c4acb5" + }, + { + "public": "0335d2a137f80e64e31fcfa3e6632a5cba28df02fe4c8e0e833783d5eb8e87d821", + "secret": "9ec021b6359abde85c20837174c34dec0d887e98b470719eb3c8188580394907" + }, + { + "public": "0371379ad86024642981706c29981b0cea3b33ef53a4ce335cbceb7865962ea5e3", + "secret": "75404cc6cd4357c50c00a665e0c57a1bfac930a568328951d9a24a820516041e" + }, + { + "public": "02814f5e4a826a713cffaafe579fff6f6d6706817ce02ace266b69acb614f28a95", + "secret": "6d1f7a494ded6b61e3db7917b3799312164bc110bb877db36d2662865e86cd92" + }, + { + "public": "02e4671fd72397cba3dda14b5e332421cfff2a613592a78b30604e617011114257", + "secret": "56277c6b50f0b3cf7cc3e27f2b8e49be99719f0f92859407dc58c8df9f02ec4e" + }, + { + "public": "036f37506b331f1131e4a123a18e23f76c13a2cebb58dfdbbe14a62ac027fb3640", + "secret": "2690382500a731dc2f8a75953aef580f29d49d154c28ee2f12654bb298f4dd9b" + }, + { + "public": "029a72834f0be3d6004483e8914e2c831252b219affe3bcf67498cd326abccbb92", + "secret": "ebe754c28d2bd009da817e87d5d1d2aabe3c0b31d83d6406efae31068d6e4ba4" + }, + { + "public": "036dbb5d21b6d127f06c01aa81b17c78020f327c7e4c656b801c10fc03ed771a22", + "secret": "0951716ebd6987a3ffed61765e923e2261ba5028f9f57d07ac185a1b4ece95ea" + }, + { + "public": "026c20ed6702e87a0e148e50b489fb7c24be79c6a86e6061f54932c4d8d0d805cd", + "secret": "44590f68415717a5a722c046e74079c94453338ff219925383cd69bb38dec152" + }, + { + "public": "0368f158c78db6fee324bde3f0d0ceb7214ce8f8716795f36b239952b2fc0459d2", + "secret": "9dd8c46d7d9296ece9c65b553a1c1b0dd8917f77238a4030afbfb06ae9b5d530" + }, + { + "public": "0254cd2e8bdb11c84e887323fe696c0fdcbcacbfd42705b41d6dcb98baa66eac42", + "secret": "41cc63d46c9903c4a98d780825afd522c64990eda08149e8c4ced9ea93294b73" + }, + { + "public": "0217cb549059f2df69953a4f4e96da2129c3406ed70f4d87d4b66ac58576521ee8", + "secret": "2b7d8cd7d369f5c77ac432faaa4a1a6e58581abaf9df7ee47537916761621d69" + }, + { + "public": "03e91cadb0277cbca2f5492148ec06ce158c945381bf21deb79456912399b8f80f", + "secret": "5e47064a951ac96990fbf0d9b6cdf7f8435ddaa126d8ae91c4e47079448173fc" + }, + { + "public": "02b6dfc3336617876e514ca28f5bc037b7a5b7d1c7ee51391eadc2b87b8e3054ed", + "secret": "fd110f9adf35f43e253b1f17e47243b33ea589f51afb6516570648d22ca1d706" + }, + { + "public": "026a9d3c5cfa3244dab529bcd88ef639bbee20e90579ba350f18fc118bf89c2321", + "secret": "1777326024e19eee0ca7bb977c98558f550b39714df0e1624dd0b061bd992964" + }, + { + "public": "0251d8fdcc544f502bc00dcd0b65701fc032b3e6b87b63de538ea53ae7a383656d", + "secret": "592e88935b01b96405d94d04ccf6267f72357f4dda71503f88895aa77d437209" + }, + { + "public": "039cd7f451bdf706b2407a28128f6edac102d3312ab3b5c3ee55a9be85898176a8", + "secret": "d25567e20c236d3912b03343915e443dded960df4eb70bdba1e392dafb26c672" + }, + { + "public": "0384203ec4bac2107842209e170e1535335a94f574e7282ae6505aa3554005b104", + "secret": "965dfa8f09e76afd4867d27fe5ba7c9d79b44e477c4144ff8b9150d9f57632a6" + }, + { + "public": "036a7ecc0c247df28ab0a7accae48373f5f9aeabdd0f154b618c22c86fa65fc4db", + "secret": "5bc79eb9e82d280c1ded48ed38554253fb7ea04d6339a049b6c1650ea6f40f1b" + }, + { + "public": "038b58f310b82e88456d7dfea7c0c392b38715f91e6fae89eec817df4e3437f778", + "secret": "17fe86898db79b929d3ca5b7a055984a214223a8daf0fe8c3d8232fc800d9a09" + }, + { + "public": "03f9052d75a63c8532d14994ce81dad480136faf94813b47ace54e38e08fc97b54", + "secret": "4cdf3c2aa3422c0ce8f81f781e414b24aff8a246910ac826cf652253367b1f31" + }, + { + "public": "039e9b541bda0fd9dffd3edd59ddcf269155e04f6954f0f6bb21db16703f08f130", + "secret": "a87c4ee23fab65752c55c19a2a3e43a420a4db66663d6e8df8b5aa34f7cb12e4" + }, + { + "public": "02134a3b0ba8899b4d09d314d472e9b6c7ba1d5722ed031e35536fdfb84234a5fd", + "secret": "1c6438db84b952320d3634a2375ebf5bfa77ea1c43f8f9d12d69d06798e5eabf" + }, + { + "public": "030a0df6c44ff76c7a8165c9b287fb532fe736d6bc61c27f5ce2ef6c2f9bccd6b9", + "secret": "dfcc8fc735d49a97984c9117246044a60e845a92271bbf30dbaa391b58f549f2" + }, + { + "public": "020eca1f6b62ae5aac6171ff29a6070748e485b2bb2e3c46b17bec9b0cbc642ae6", + "secret": "349020832833d2c90fad971a4f12559f193b326b5c9f3397adb73fc6dbbb9d46" + }, + { + "public": "02068db4b2e7060f27edbe221a0974a54a9b29e198edfbb780ed2a410a2fbb2de8", + "secret": "9c0cb4595a9f89003e24e69369cb0491050926b43a01aa4735324dcf58b577bf" + }, + { + "public": "036c7c2d40d1ff2e4d68f345ec00992e9808c58be98cf91445c257e7fb9dc96f64", + "secret": "d72afeb789d1cec33c7f24e15158ee3c514769313d90937473fba81cf5793dc0" + }, + { + "public": "03558ad1cac71506255ed9cc7b0a3dc0909004ef23c944bb56ef40a4bddda025ce", + "secret": "c4dab8a22e7bb63f4f8e68c2b0236df52f222f3b819e9a6fdef985b774b8446f" + }, + { + "public": "03e7d2614e71ab78d808c6244f4f25415d9fb64964a6599e24364c84a03cee94ff", + "secret": "ea31108e32a55a91ee62788e20da3956a2b6010cf4431bf65d57955df0341690" + }, + { + "public": "03b2749206ab7a35f43ddb940d7f34695c8da485345cf9fafb68ef43ab998a96f3", + "secret": "7623c47d93f83485a16e6f8d7a216eeda19f69be9a4fa877f6980a0395e65af2" + }, + { + "public": "021c8d57c032abd27234310dff86464215d06cfb864f8250235447a9e9fd669b93", + "secret": "4b5542c56fea30f19e8abb3ca81963a7c75f29d30cb96e14d362d0c047c4c6f1" + }, + { + "public": "0339e0c2e84582b02cb7d07d054e71086306068f2850af6db7c856f6752e251540", + "secret": "a2c0048e96fb7f6e55dc7246b6b702c7fa029d8b78f7006faa5ddd8833b820f0" + }, + { + "public": "037ce4fd2bfcdd5c8c8d3fae34aca3db3223a208da275f1912f04d1628b00cc964", + "secret": "223ea29215c65b0dabdefb5639476b19133db4a8b831c3dbc73727bc33038f7b" + }, + { + "public": "039abfd07c4120518d322b839f144788512ce9a7ab84aa316573ba23d3eb39c3d7", + "secret": "077c4fc2b2cb0c95eaa7dec5a028d1bc39160f0369caf493499e43ba6936a545" + }, + { + "public": "03627019487c9e847f71ab3e969939881698836d5675e71df113ace548df698eb1", + "secret": "f502e26db7a3d1067c8053edf562ed255255218777b36896295307c5433b445a" + }, + { + "public": "02e3a703e64f579097eacbe0a829bc9fd3631a07ddd2494b47cee8bd18403dfc49", + "secret": "7f2ce44f8f766da1f8396902ffac1e556100870fa8266bfa61e2e1f3ee8651b5" + }, + { + "public": "035bd62828eb1dc765306b76b6cdff08b00ebe5a63da843ac98173a50f57bc567a", + "secret": "c442fe421eb9e09f1e6e82ab1e5b75d9c901d3e18d0229c5f5b7161f6043e447" + }, + { + "public": "0270a03fc149b19b7fec0f05a9e82071fd48cfd1a85cbf560fe1e40aab7bba40be", + "secret": "91c6486fa147a141622d3c99b4e01a22171a8a3aa3ba4777269b2f7514dbba23" + }, + { + "public": "02d119b76952bcecf956b282d88d168d10ffe914708dfc46b46391ab1335b7c0ab", + "secret": "d462da8f1dc6aa09ed03d638a8f85a3b39112e4464b84171ff3d9e4fcfd46bf1" + }, + { + "public": "03b15106387b5538dfdc572a2bf46ad603c0a3154fa755cf6f0dfcea14a1c5ef2a", + "secret": "ed2c28993c895c7b0503b469b5e6a90cb9dcfc27126f61016661a67e0e719c9a" + }, + { + "public": "02464d8e233a25fd3eb8b4109d7e6792a8006a1947cc247bdb1ab0a53cc6eab16a", + "secret": "f74d87eaaba1349c31c4a3aea3c0b8862d2daa128291bedb95b252e005362ea8" + }, + { + "public": "0306f427104a61bcbe5f68053c2b6f0195ea98aed335e47b62c81ece46555e7805", + "secret": "3047a50207d25d0729430f8a94559606c13bce462cff07f18ce584c6660620bb" + }, + { + "public": "03c3ef468449c951a330134a1eb3b8da29f96bdb81aea15e1b44d3f45896c161aa", + "secret": "ac0f2295e74fa9730c7076d4e69bd5723afedcb55d431844547382cceb3ecf3a" + }, + { + "public": "02439933cd7586cadb7ad4775fb5a435d5d2cedf620df0a5e15f56c29d1692e519", + "secret": "8a72326d6926c38655aa08ed76ec887a2a443cda129da10e3dfdc40128d0a55b" + }, + { + "public": "035ee9665cdd8180856f7ad236528728f07c75e5de9546f72422981f3a071ab839", + "secret": "a0d2b3a2282fe673f966fbc2df363aef024d1466c7a758aff6e4e9644275867e" + }, + { + "public": "0304888aa7ccf2c8e7b24b9dad11a4a2b6c6f83bd904a82a1dea7a3c5a518dffc8", + "secret": "cf04150ffbfdcd5a33b3c801ce9ba22a733d76f359106006e2f3df788035514c" + }, + { + "public": "03223e9204a6cd4ddd3c4c1e8c204d64e78c8d922e7b83807d58029f99b9e480c3", + "secret": "97892fdba572720c6a6a26d758f5dad9627b1917b85da9f1103691314c26f6e7" + }, + { + "public": "02c2b249e8556f27ff24bd346db9c2adf0ab0b89d671aaac9ead56990abc974cba", + "secret": "cd190b27794641e035d24aa12a0c52742e45b1f55ee5905fd5d8620f1866d569" + }, + { + "public": "02fae519ae95e19cb8783ccbee45ad0b3d4b3b53ebaa524ad5dec657dfa638519d", + "secret": "4164e38437a786f5b8d48738b01771ec6feab7961061aa3370cb19532578d371" + }, + { + "public": "02f1aba1a25f65e58397f6322c4d719988bd49abf5d8305b519f989b7a03d19126", + "secret": "62245e844fdff31ffcc6fa24f044ed7937e43ecac250d89b443ed7813d3b770f" + }, + { + "public": "023b6d32391a493061f1db3f5f1fa3fb674b31ad5774774a56fbffed357d006139", + "secret": "e96f64cdd4e41444de7e91fc162c79fb230051c0c974ce197a6b89f42f81d57a" + }, + { + "public": "03c9a6fca35bd6595471aa6e46b1e500dbd66e7be270b871e2dc2ab401cde3ef27", + "secret": "865f5862059addaba9292fa41687b2fe9fc88d7cd69e71e14e89f2cfb2aaef57" + }, + { + "public": "02c73d3b8df2fb52c345b24004a35ddf6f57a3d7cdabe9f4b87f7a3a8d1520a5ea", + "secret": "ca23513f36e33ea8e033f48189bd11f823709f2ab17acd7d2b8580e98c501673" + }, + { + "public": "035496862b9f30c9b4da31ccef7071bf26186f685dc5e9c9f72fe52bca8001d8ae", + "secret": "5b388c74773e76c7d14670aa80ed6bf98dfda6578c4fa63aa79b2140ec91e3ef" + }, + { + "public": "021ec4d3e4496fe56bd6cc00122b97ec722787a8c001c0c40a2c0aebb74c6dabc3", + "secret": "f06ff358d629c6ba77695e765eb0bf880bb2274d013fa19c80e2409ac28eef99" + }, + { + "public": "03ce5e8de866eadb173197936062acd7991047cb46b65a4521e1b093f191054986", + "secret": "01cf2942d384c0d74528676a0a858ef5093b682a5121af2985c70cea056a8834" + }, + { + "public": "020442d9207a6a98c90915e8c1f639dbe75e8d7cb93219a95b05c49aaac3ce14c0", + "secret": "df3f20553cd4e1a298725b8a698738e82db524836685dc5499d4db026f0e523b" + }, + { + "public": "034b24fc6f0798641094442b680c299e9afa01c05783dd1ccd16567ab3bc993af3", + "secret": "ea2bf30dc0fdc389821513b23289d50717e74b8558766964de53aa45e5f0ff9d" + }, + { + "public": "030956fafe5044d2fc912c927ac7ea17af502379d7d2d723f22debee4df7bc4a63", + "secret": "c13eb8432d2bf6cd9938b6b73b38ee5c47b8b5ea57a0bf43cc8ba642de79ef1d" + }, + { + "public": "028e4cebaac86966af581fe2c3aa69410e5c732d03e3b4a2ee1cd53131e02e48f2", + "secret": "baf8a4fd4755581864f7e9095fd41e529851821ed64b494b32cdc100d53e0b98" + }, + { + "public": "02981c68c30392224a813cb00550fbd42dbbada526ab8be5e2b60d998b428e8da0", + "secret": "af9efcc2fdfa1f3356d502ec0901d9f6ad4cb87d890cebcd1a124f4232f2b15d" + }, + { + "public": "035e645486a47848b8e728525451b89d7c5acbde5fb6e82f86371a6d89bc338173", + "secret": "33f867bd83a2e48f6f46dee0a4313f14fb381416a8ae32a20f8b9cba4835f5ee" + }, + { + "public": "02dbae41a3e89f4d2d5bd3b05ba3c7501c7e8e43b8ea8a7d05931bd9f55b23207c", + "secret": "8e7ca41b47355678baa76933cf1c86a7a7f98a317df7aad5b310797505ff4fd5" + }, + { + "public": "02f9b8f308250242e9e1e0a51b4b4170305e7d88f6f39d24d807b2e079454c61e5", + "secret": "c4937778129960c2c1c5aebfe11c1bcac21982484dd45e082d9e1b97796e20c4" + }, + { + "public": "03924f9e8bc7df2072013596e49095ad090324b69972a1f453b99e02cfaddd10ce", + "secret": "67e9121ef801ae0bbae99ec4d9330e0f3c9026e33cfdce2c51f1f77a9133e2d2" + }, + { + "public": "02d576a04d32658db50ad6907d8d60d5054ae1e329e03458e92051c4ddf077179a", + "secret": "77e6cfe349d366c611e226f1c30f6d20c399eae38bcb3b40b63d67c2ea8bac8b" + }, + { + "public": "0395640d680d135c921bb27e39b7af05703097ddc7f0e79cf3373883ff140f0bed", + "secret": "c65e02236df8379ded21c124813330afdb88610e703fafa0898cb81c7f606299" + }, + { + "public": "02672034f63d05f2dba97b4609b2bbb301b011ba1b63836ca749b51569eb505933", + "secret": "f2bc40f6c555b5bfbfbee6c155171ec175dbebbb7d1ef06071495abfc4047c24" + }, + { + "public": "03602f1e425f80a4ba38e616d33619b0b2ab4961995af492dec63388cf932c1166", + "secret": "6526ed354b850dd135b235d34ad91834b6911f92cd0af2b37a58c6b2ff093ee2" + }, + { + "public": "026747260b8ee8d0bf20cd5609da54339653b59a795a4cbb9e820becc1f4b36c04", + "secret": "897117a54b8ebb294cf7d299ba43632ed4d4d3ec925e791122694628e26411cb" + }, + { + "public": "02b20d7e037f8cc574881225e767a2fd9bf5a21ecf438168273622f70b426ce22a", + "secret": "d3deafd2093258452b2ab3a329810943d3b1adf805d8a7b140c2cb37f2fffe18" + }, + { + "public": "03e5402a7c6b1915527a2d94e746fa6fd6642c524d13c3e040377e9948786e9ad2", + "secret": "5488639cb8a83b8aa0551c0e17af9ac6bad1771dfc66798d4c2df3c7570d220f" + }, + { + "public": "02cf6e2f3db6af81ff6408f9f67945d78045339caa4b0911140c3f40da90e4b9b3", + "secret": "6cc514697a9f25e6a120013fa734c3c708018aa0ed356110069d612cd34b3588" + }, + { + "public": "03d4107ee5c9b3bf01e23f7723baadbba51cfbd96ecc1cd9e504c48b02081dfdb1", + "secret": "a0070aa7ba4632bd7c9993e7abf2aaf3ffe6d4a3cc92b51f30618211a0c7a294" + }, + { + "public": "038a914c47a114eb2da81a7f3294f311f2ffef80b2de1f474805cf0f845837f95c", + "secret": "c71e794ecc69d8b659133821ab133bb9c632e9bcaa18508d938972a3f4e6ea2f" + }, + { + "public": "03ae78ffce1837ddcea2800ccd6be892a907a267216978c7b21ec749f0d44b65ae", + "secret": "47c0e4aaaf5f1f0de52388107e5c4b882fede181933f0216d6c64640a1ec1714" + }, + { + "public": "0204e303a235a67a8482b26365bdcb8b1f79c811772798b4131ca233f0b9209b4b", + "secret": "c55f3aa960479db0c174ce18c85934a6babb7e06b8b86aa6de69acf54a311705" + }, + { + "public": "029d11f19c205b0cd07d760f53b02a4d8b0a2b7d3310ea92e1b5395d87f7bbb2b5", + "secret": "10451defd859bc1cf3070b418a7a110c4cf5eb77350e87b79cfe7da20c60def3" + }, + { + "public": "02e7490b1c94ccc00287d5049a814fcde208cb5c71c1056ed7895f1f81ef62c614", + "secret": "a0304e73dc2f8933735cd565f5b84d513699ac1afa54645643f0e6d474150905" + }, + { + "public": "031e938fac7f6ac24e229f7c4d7467361a606a5207c568ad873995a9f80c1ca54d", + "secret": "c763ec3b797e4db6ed8b85593c44a9dd8d518e52a5aa8420f0910795937a56bd" + }, + { + "public": "02a3714ef6ecf36e9e9cf1afb676c4a7aefb3cac8475a55a08a63ecc8277e4c059", + "secret": "64d9fbf5b0b4d2f4f86c31bd34d48d00b9571177a0f7e111ed30a402fd0a956d" + }, + { + "public": "03c6e6a4c1e05df3795e3e0483247604ca808e1c08c2fc8e95b34e0c0bc5ccef32", + "secret": "b4fc0e7501ac366de72e59ea95a505c7726e7773c19690d2ce0cde281e7331e4" + }, + { + "public": "026062b4a89b4920c73fe4f25700a889ec4a79aeae1bd1b9a62353d62b103a0790", + "secret": "25bf5cd8849f3d08b82be180c664189f8ccb637bd0d29543f4c5c6c071890ab4" + }, + { + "public": "03f2d532ebc65b25f632598e9b222e7ece13c140ca23f36ae1eedef2fc45d5d717", + "secret": "102f188330fc3e30804ef9eae2c90ac1a585e69e67e85026d992426acac374cd" + }, + { + "public": "0232893bdeb942ca86f7381c973511285c7a38f6caa9f7d035cbc7b3089b63bbfe", + "secret": "171dfc15a5a0a677b304e40e398b1271799013b27113f8a0288d96e7860bbe47" + }, + { + "public": "031995695433bec62dcfff0255010821f55ab12e28e6b03a58074ee70ef40383b3", + "secret": "66834c2e6a75670e0dd329a8c0f2bd36132334eaf863d8e58c5d3eb27fa9fa91" + }, + { + "public": "03ed0f62e54c9435d6c4a0d3e1b1d34ee1067905f3b45a9302eab93d57ec978c10", + "secret": "f1aa5870c1daefb9ac47f65424a304b7a7a8a0b5bb178f448a3d2307a558b583" + }, + { + "public": "0372610c580fc7e4716e326dc76396397326084b7fb832945f35d999f847814b29", + "secret": "0a39f8eab6fb7b7a8d8d67db847eb7f9dfe2afc31b7cc1746ad9ece60bf09957" + }, + { + "public": "03d450e91388112e09fffae550d0b259b52363ec77f7391e3a6ef202e3ed661c4d", + "secret": "5637e46c8081c6fafda6aadf025feec97a8d3fd0827bf4a4cea2d9e7c245dc3d" + }, + { + "public": "03cab2ab0397ef9a8b285b0be70eccbf6ee63a42357a30cfbfd438436f0f9bd293", + "secret": "82574afd35c9788c1a853a77a923df3204d75b005a323d5d00ed4a890e3e24ca" + }, + { + "public": "0391e17b1abfdb4e61d964ad2df7d7d66fee3111181f8498cad0e7e08a16bd9580", + "secret": "a78c3ea501b4209923d27a55e6ce8ea8629376e13d814d2b27dac2a5052ecdcb" + } +] \ No newline at end of file diff --git a/rust/tw_keypair/tests/nist256p1_tests.rs b/rust/tw_keypair/tests/nist256p1_tests.rs new file mode 100644 index 00000000000..552f40838ca --- /dev/null +++ b/rust/tw_keypair/tests/nist256p1_tests.rs @@ -0,0 +1,82 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; +use tw_hash::{H256, H264, H520}; +use tw_keypair::ecdsa::nist256p1::{PrivateKey, PublicKey, VerifySignature}; +use tw_keypair::traits::VerifyingKeyTrait; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const NIST256P1_VERIFY: &str = include_str!("nist256p1_verify.json"); +const NIST256P1_PRIV_TO_PUB_COMPRESSED: &str = + include_str!("nist256p1_priv_to_pub_compressed.json"); + +#[derive(Deserialize)] +struct Nist256p1VerifyTest { + public: H264, + msg: H256, + signature: H520, +} + +#[derive(Deserialize)] +struct Nist256p1PrivToPubCompressedTest { + secret: H256, + public: H264, +} + +#[test] +fn test_nist256p1_verify() { + let tests: Vec = serde_json::from_str(NIST256P1_VERIFY).unwrap(); + for test in tests { + let public = PublicKey::try_from(test.public.as_slice()).unwrap(); + + let verify_sign = VerifySignature::try_from(test.signature.as_slice()).unwrap(); + assert!(public.verify(verify_sign, test.msg)); + } +} + +#[test] +fn test_nist256p1_priv_to_pub() { + let tests: Vec = + serde_json::from_str(NIST256P1_PRIV_TO_PUB_COMPRESSED).unwrap(); + for test in tests { + let private = PrivateKey::try_from(test.secret.as_slice()).unwrap(); + let actual_public = private.public().compressed(); + + assert_eq!(actual_public, test.public); + } +} + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_nist256p1_sign_verify_ring() { + use ring::rand::{generate, SystemRandom}; + use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_FIXED}; + use tw_keypair::ecdsa::nist256p1::KeyPair; + use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; + + let rng = SystemRandom::new(); + + for _ in 0..1000 { + let secret: [u8; 32] = generate(&rng).unwrap().expose(); + let msg_to_sign: [u8; 64] = generate(&rng).unwrap().expose(); + + let hash_to_sign = tw_hash::sha2::sha256(&msg_to_sign); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + + let key_pair = KeyPair::try_from(secret.as_slice()).unwrap(); + let actual_sign = key_pair.sign(hash_to_sign).unwrap(); + + let public_bytes = key_pair.public().uncompressed(); + + let ring_public_key = + UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, public_bytes.as_slice()); + + ring_public_key + .verify(&msg_to_sign, &actual_sign.to_bytes()[0..64]) + .unwrap(); + } +} diff --git a/rust/tw_keypair/tests/nist256p1_verify.json b/rust/tw_keypair/tests/nist256p1_verify.json new file mode 100644 index 00000000000..be3e6b48bae --- /dev/null +++ b/rust/tw_keypair/tests/nist256p1_verify.json @@ -0,0 +1,1002 @@ +[ + { + "msg": "3fc3eff7f53bdacc4812a1197a1469c98c5c9d3f7f4885f1dde397ac0b34d745", + "public": "02bd5c3600089676a981d2cdfa0147a5e4abdd3b6f34319fe2b8ea6cd9569880b1", + "signature": "6395017ab93fa645130c21381f66229f0ad31ad50efa613e546b6b1e687362ff591661477e794337b971b466efbc6115d5f35b463cffb6f785dd438f3d9331f601" + }, + { + "msg": "b3cabcaaec7ec718b59c9bb505eef38537c9d094d6f3ff71efc4836bec67c265", + "public": "03e1769d3203a68b05de51ebc9320752c76fdae6b2661ca80754a5f974dd97fc5a", + "signature": "6746a775588d74df8894f888cba28f1ab60b96b7750df10a8db724210d25787069984130ad944827082b13e53a96b64d1c5e776d8fafbf5985b714cb8b5cd5f301" + }, + { + "msg": "b7d5145165f9a93480432896a9a778a524fc4f79f433c0effb5a75b051c7c568", + "public": "03e15210081adc36af63c47b690aa474bcd3704e3c532034c63722304b28d10142", + "signature": "6cc8a52ea475c5ab090bb91f62e1e3e9831450b6941d30ed0600a08acec014db65e7ecee3a3e1f95a53054a03f16f0e44f1d0aa3a44b87a4664819a0f5c0f38800" + }, + { + "msg": "61e03b0d61ddb1c171e8d164bd2d0c1b5fc4e797b64668a5beab255d3e0fc71a", + "public": "03bcb0565985be22b287f1e5b38350e1b9c6c631fd6e63585041b24e148ec910eb", + "signature": "d3771f7602b46c4d12bd784d60aabe9eaeb40862750ddd5d49bbdf6697c34cf9377a7803f627f44f461378636322879efb8919654a2c69a280ab7efb5f2466a600" + }, + { + "msg": "3bcd1aabe2a97aa8867b36aaac5a4b22c1296012aeffde0f8ad791b19f12b008", + "public": "02a9946591f8eb3d5c7160ce92f0b88b1d28fbfb1ce6bb39cc7746a93d2ce6a7ba", + "signature": "40be3646c364a142e7f07cce413db8270eba8c52a2b023ada2f89cc3cd5972c0547799bd6b5d8d447d7ffd850329504717da9305dae85645c54393a5035e9ee700" + }, + { + "msg": "e2487f2d414d441de4b805f7ddc33974449fc4f02c8badf15377c72c92375c7d", + "public": "0352006d72237e24a93cc77444daea4bdd20944d5774a283c1636ac24acf6f377f", + "signature": "b2ebfa1cc565825c15012e7f0ee5404092aaa66a811c1c73e188133da9068e0a43295b7137f53bb47ea605a103f709b367d9d682f546dd4f43030023d93a615c01" + }, + { + "msg": "1937c84233473b755ab2520c8803eeca90c7bd503557a9ce5443f1bf300e3d17", + "public": "03d7b1aec7fdacbbfe8c54a75fea07ab5cd2e0882e17edcfeb2f4f6782782b9b9e", + "signature": "f65c1e6f47c0a1c7224da6fc8aa0eea9449cae1abb32decd121f21e1241bf894374c2c6368585275b595ab18f4b754b6532920522fc821446771d5b721ca0edd00" + }, + { + "msg": "0edee26db4d0d974c63311d3e9f5732bba946d7af2f32c70fc5965d7fcf1dcec", + "public": "02b80fbd6b8044b1a0dbb551be88426848280dbd8a2c5815a03bae9fa5b187c66b", + "signature": "dc2c74750ef6cbf3c66592815ccb3e3c64a6c28f3633da9a5c951c28094d5b9016e577dedc6a1d1e6d13438c7b8abfe2553b7d44597620755b87e68aecf51f2b01" + }, + { + "msg": "91f9815b4b0a1f2257233d4d0485888a1f5d63498fc3a63c1fae47ed56e10c74", + "public": "032b48232a0edd6a126ff656866d7cf2313d9a15ab2165b975f04a1c68f184e822", + "signature": "c7dd5b50a2a5aa775b23a90b0dba80edc6a12d9f0bf87081cea8cb61e3de62e21afff84bb8c656bbf08e68a25f6a8e7ddb8f186f19e91008cafd08013a04432d01" + }, + { + "msg": "d153b2e6928e837ca82d95c8220bc242d78961c07beb7f027406abf4555ab913", + "public": "038732fcdb97ce1516afb530ee6d65552ad3edbc5be837a7e679c52f8e23c06569", + "signature": "a7a4f7bd4990e28110d082da52c7eb68d16084ca36b27d57a235c463bfc2a2701cf453b26dce6cf37dadeea611969f440f4093a9e14be89417a355086cf5ed2e00" + }, + { + "msg": "4abd48f6439581235e5e28d69100daf4b82068e8265bfb2cfa157b68b7e4b035", + "public": "036c55666ec34bb4007f81205dd0e7c729ff5b04ac84d9229fb232527d62c2f1dc", + "signature": "7b41a6c06d61460d71e889db7edbb3d8bb01b3567949075559d6a97019c897e5539d107b38cc72b73beaeb1a69f9898c9e64f643b22fdc06569d5a4a222e11da00" + }, + { + "msg": "9f6864416087761a262d230c18f38f378f4740f74e65b6da2615fbaee0cd71e8", + "public": "032bfdb75624c4ae1f2a3955288dd8909dce93adf287225a5e320db948f0b22435", + "signature": "1c9195c7cc520448504a2a852fd5486734b73ff0f2afab8db1ecb12199a4b03c79a7f2c9ab686fec9bf24e6aeb2875de2e5faadcd27afd6f93e0291e3f1da60801" + }, + { + "msg": "b1ba41975b2f4dc77dfcee85a9b72a3b8ba4dd96e35dcfd0bdbf53055ed398e1", + "public": "032ec852c9deeb23e560e13510c2c628d67b193df9e0452155aa71f5b578790793", + "signature": "e60260f176fb6acd22358eb962dcba83793ae0501cf87d5e57081a763bdc82cb6fabf7e84e8abd4e8957df72fbc69943d6b6bbc77e2a1969a3f79803be6694f201" + }, + { + "msg": "0110cf18650bf4be0adafe1fea013bd8a09fc03f741e61d92741437cfee4f4b3", + "public": "02cfc31fc32508e5ed1b86cd43cb11de3b7bc2772c4eb8f079442b1e368c04eabd", + "signature": "6fd40f0b74d0e0801bf23cebdceb14d2da9a12d18a7790a48482804d5d39173b482630a66e052adeaebb96361c07568742398dd4589854d6b2ecdb32440ea08e01" + }, + { + "msg": "32ed28b7fc122057c755a175ec9fbb90ec3853a44916716fd5dcde5e90b7dca0", + "public": "036dba0f232c63aad24085a0f7595169382bbb5842e1a451279b29cec85825df5f", + "signature": "a8b87d61a848e00c3d990313d0166e89c877d2dd9fe73f2821c2424d875df93d3a67678c89d2115f3b937ac3d31f6e0bdc908d44c77bb7eeebc0a68c2055fceb00" + }, + { + "msg": "15a6ecbd1fd7c29a1767ec4e1d1d0d2af4c7bb88a020c9ba965c8e20d3593b78", + "public": "03fda85841b3444f7e2088a3974945e85be3c8271976bec13cdc0e69de7d79b3eb", + "signature": "63721b96d11c1c87e9ba9fa0a17fae8512ecb4bde9707c2713865942c8c68f2e77400ac8f4f395bc98567d6f1bbdedd3c5796103724e735712675f10a06f98c901" + }, + { + "msg": "7a1dc6e41b5f92cd792a053eae2b0132533743e06577511df56a0c0b944a4539", + "public": "036aeb177cf9866d44cb178d90ea5167426ab4172afe652ebb94447bcafab8110f", + "signature": "2b65b981bc7394a1f3a30fe80a515b28d9a776ae575cade7e389f51f7a14548b5449b3ba65e794e8e78614273d0decded5ea7f08fa8ea8f5935aad0ac08ca0d600" + }, + { + "msg": "a8b1447535b6ab857e224265c0e5383c2f7f73c545d034d1798df9888284ee6b", + "public": "02bdeb1540cecb3e32732becfc864eb4d8bc32ff9056f5d3b6876efbedf31e41f2", + "signature": "52642ce792cb100c6e4d90391d852556de043e2c766fb67b9edf677586b395783cedebe889e600435cf6899ba77888b286f23498d2f9ba97eaa605673f6b567b00" + }, + { + "msg": "6d515e85dde7759b4b695f82aab2fc39b493d027615d6acee6f56fb98d3b2145", + "public": "028280bf538923014130e3af18ed49557010b9a36b2b1162758e77b187682b6797", + "signature": "2d80b2f9c7e29ce0387752d9f5ed018066fa75ab02f3dbcb4b670648c1afde877e2a5dc4118f776788adf16765ad7344728dfabb1ad148146f22a41d2009faec00" + }, + { + "msg": "00269fa879e3db9d46467a407a969fea1e505b88fdb6b6e7218bb2849becef20", + "public": "035df9fb6043633059197e3f62c49f845b1c5d2cb7ed9cb07113acf245daa90ec3", + "signature": "1cf25dd1177165146e6dfae00eee4771f466e2c82b9f0b84e6e437cd5426eaa546da7e6db98a39df39f27ebd93b1cc3ef87a74f62d6d8ce4981f03980dcfa28201" + }, + { + "msg": "9f0ea3a876fd07905f0966f89b8b84179babbda1dc1cabb0b2554f9d387800fb", + "public": "0299d233d1b0ec629bcf53a3ed3258b9ed2d3076c018103526399b617abe14702e", + "signature": "2399ba08f57b961640b59c04ea0d9eee4ba432271ac22b6706e8a4aa772bdfa04d3683a31fe24bf52c3a95bcd7f4f2b40cac2123c84d3be60354261e1300b1ea00" + }, + { + "msg": "3ecbf5ae34bb1d6b6f077493e388dd1fd0401da4055d9c89adf5fa1f0f93ffaf", + "public": "021a4db4d76d6407831b9753c16be63759c7bb6e928691ea8200104ed55d555c50", + "signature": "2fbf7b63be3596dee0fdb1090e2e56019995a47a8038e61451e291d89b84cee658eb740ae1b463c3335f291ebfaf6c68cb89d05889240730d32b8e769f6957e700" + }, + { + "msg": "974bd8323f0c7efdc0539ec16a4879742067f1f9052c97bc2547d21783923c85", + "public": "0306981738b4150e93dd2166fcba2c58c071136d6e882d8acb61ceeff5e3f54fff", + "signature": "7edeb9613910719ba0c0d52ba574158864842d511f1edc900ea1420e91a315c37c846b167e01ebb7af8b368cc0407d8ffd6a20633fcf68cdace05b7361dab1db00" + }, + { + "msg": "aefe6faedb93a13872a4a4421587069c724d290af053c0af8cbb9da6d52f408f", + "public": "022f78d10160a00769fbe07658b7716aaa7cbde788729c9c2f5fd9630f3b9bec61", + "signature": "32efdcc36ddb3286bdd8f79464096d4e70db457dd4ef144555d55d6f4dee1d0e1a0aa3211be35c1666a4c8a90b2f9c4582b71012d2ff3ab00081abaaef9c1bd900" + }, + { + "msg": "046c497c6003c9280bbe2665c5468b4500b89406c568ab8cd6517b641a93fc19", + "public": "0323dc4cdada0f44b441ffcbdacce62541c99a72fddd8d7a490af042ce2f16734f", + "signature": "f171ed4486177b9540eee1c4407212bbb187306dd8efe116901a1d8aa141113f05bed5d27e0da272631b3d485ca174fdf8a66e9a0faa2fc1f5256afd606617b401" + }, + { + "msg": "65b8f1b63cdc4018902faa868c54d44f5a11ff1f761b9beb05416cd85d4ad3ca", + "public": "03d8cfd0f1293d35a883094f6a7edf6e7842371cb03952f6621b518ab3ff4edf38", + "signature": "cc7d33c9cced8e8851af21bbd67a6df71a63050de3116ae3c50a89433254c78f20e5ec4e930e4e3b1d7c059ea4f7b568e1e8042e4291216c7ee220317008838200" + }, + { + "msg": "25467e135350ea79786eca6c59d7bae35618cfeebef9e5153ed2e073f2ee9b62", + "public": "0229939fe834e69da9c62012dd435eccc33505737452d8e3151850feebac2111a6", + "signature": "1b47d5cc524cb49b86d0e74603a75941a57712696f1146ef7f65769418c15b9c306ca8a3676534734bf731113622133cb5b913366a162e526ca0811181c3679600" + }, + { + "msg": "d52391ecb02377e9eff37461be0ab53a78ae1a9471f9a6be2e89be9765a9dbff", + "public": "03075f93a73a5b190532efd4a09fe08e830ecbb5c1305a55382c4e4f905bd6a05b", + "signature": "e2fc7a1ebd5464ab071160a9b8b88d99f4c8aaf144eb184d4577fb03aa305ebb53005d92a2042dfac3c8219e1480e32abf1c69f03aca8021f4b9e5d1490c8bcd01" + }, + { + "msg": "d8114ca952df7809f2382d65adabbab7beda7a25053513a71f6a8064983cbd5d", + "public": "039ed005dbab34510197e26da0e4bb107f8cb64b1b9e862c43ef0a86f05dc5d76b", + "signature": "5e3e3a7a1fba3b46bda2983ed5523c1ea69bfc8752bb88e54a31cc64bff2918111e2e30f8552cc8635c0802ded373875056325193ed9b8ad97b27e9e1e2e44f001" + }, + { + "msg": "f26734f0d4f5105395115fa6839381c70d9816fa818fb9e605634a59a5ea9200", + "public": "03cb130e405ef4bc7441546a833e36a78284ef826b4fa0ecda66c330523dbf96f2", + "signature": "a14d1f583507e6ea09dfcb42f5762b08ce37337c95a07aa1eadcb02c1dc7e8cf241be4178f71dd14ed307665f0e4cb673d907ebf874882c0221f6ec6b128a6ca01" + }, + { + "msg": "1ba610ee3629bf8bbcf1ba30c1ed9f4c4dea2ced0531af6a26ef7f2ee18aea16", + "public": "029832e3af9059871ed0905d44280c828e20cd25bd65c66bfbc3e7150c963ec099", + "signature": "25dec6ad1d408e978c265fb50c91ea880c8e4a1088ef460015d0c48434db61b7017cf33fbea390f584b69c92ff3c47c1e095d5a764f8892710aa20822bdf546000" + }, + { + "msg": "596a17a1a27e7617ffa8e777a0006c21d903145dbc2644e290a33c6cefb51277", + "public": "022c2903fff6eae8b633366f301476b9106a8a0f70f541c19cdb9dbf2cca2b210f", + "signature": "d92d628ea518d56da346c97f444af5d9685274a118467fb0ab3c4070d7968ffd590f12fb0599e4042e37661b32a4534cda84792b394f1d432e1f02ee0e4d43aa01" + }, + { + "msg": "ac2492c69d56aa433e07001673ceb19bf465ad2e18b55d07a1824e5593f51b05", + "public": "034ec5a7377fe20f714ca3eac79ed4629c53563daf59e4d89a6d0acda501b4f33b", + "signature": "71f89cadaf3d92a163267ede71a796923ef3c48a5e5f21d705a222a11164bcaf5efaab21bc657cc0f31d130e6256bcd36d7cdfa26fed351aa6a9fe209a976f9300" + }, + { + "msg": "2b535e4a945059fadefa75b3b1f6b22f8b4100356a63c37f769645dc8c87c189", + "public": "02271cf2a6263bc94702c725ba4732ae041c300196cfa6dedbe5cb37f4c717fdfd", + "signature": "3b630d2965b2682c406ec57adbdc9c4866345517822f8ac53510e17632b27ca54302d88b6586a653b4a97a5f5981eba669a73de2a140e081c5fe52603695665c00" + }, + { + "msg": "6cc3e765e85f30c1020e671083f7224bd1084649c568fabcf1ca1123bb8736f2", + "public": "03dac2cf067abf1b2d521730b85a65a79071e9c5ac9f2950ac5bd44e2e850d8278", + "signature": "138725f707643147214361cd5488782342e0a8a7110fdd329b920138bae2e06e4fe7c588a17f83094d7cd62f29562a7eb6c30d507232882e9e842312cf85ada600" + }, + { + "msg": "0356c52fea3a008d8041d3f1d170c5bdc8a6d08c694b44e7e0d0a8fc6f230d73", + "public": "02a9228fb8f3e82801442e9b04482c5f9db4fc4f280da1c6870d5cd3d15196f13c", + "signature": "9a275425a2a2bc2c60de1eae59975bec0783b1a933718e6a1a48f919200858c71b12f70610a0d43b955c87be21b83e34ce02829dbb6335c34af674195ce00bcd00" + }, + { + "msg": "6325bf73910d1c5f96010cec3c77115f59c7bc822ee4c8989549c26f1fab9127", + "public": "02d1b6d93b5bafb24ccbbab836cb86c2c69845b4c82219ad2950ef02113cbdefce", + "signature": "bfe8ccd383777e7bc4b92ba2bae37268a261072b62cb9a44935cdcd3eb1b26bb17eae6ef3f1563943b9e86e158c78a9915b1539c7da59b4a321d32622cd56c7501" + }, + { + "msg": "246b49aa5e4df5ba1f72de7c1586a6f1bca3a333b167a8e784f8d970f93ad7a2", + "public": "038bdf68b59d6a9a971471363308fed7eeab3edf161a7a0ab5ca75dc45895519a9", + "signature": "d0b1740e9031fb6cc2ef5c6442753eec6fafe4720611f783eb998cd3ec41b0276e167a54f13fdf45ff4b95dfe7df151c682c4a1d605ea96479dab5cd288f9a3400" + }, + { + "msg": "12fdc2e910238d41fd3310581ac3597c4700d62956199dad122b82296331c372", + "public": "038ea7c99a61b80f6c919548b636569e06a4994944a3278eeec476a22a9daadd92", + "signature": "61fab12d9952f5409ea7d39ca504a5fa3ed800669b9547dfd162257856e80f763afb00758dcea39f89388ea6dc5b62bd5f2aa9708cf6444287df7d3a6bd1d0ef01" + }, + { + "msg": "1e8866b818eb815b08d97cbffe5f43d34c57362d703c9d84a7cbd919291c05d1", + "public": "02ed631d277945c7b67f1cb4c80d30a42e2e173384cf682526cb4a748dc6713944", + "signature": "eac977f9cb5ac1e48e8b654ba37139746a95e31fd8a0286d531867a7a68b2b0362d57a28d434428ff4692dbc78a58e602eed24cbdca4ed55e888c4254bfb7e0001" + }, + { + "msg": "26fe270945afddef41c3b950e0881d87aa77982daed5694af80b84d51eff7b07", + "public": "031942a43382ced8c22119b6f1247f27881bf72e99386923f62836773cb6b62368", + "signature": "678bcb8f52a48f17c4e4a2cc77efd555b9e7ee3980f099cebdb61b676f9884c80310bbddc46aa8822d2ef8b0ad4d0cf535f06f841c0a117176c4bcd84800350c01" + }, + { + "msg": "1e43fd1be84d7cd94b67565268eee393ddf6b05ad6e5b51c4f6ee230114e126f", + "public": "03979edad0c1d333d6635f3870ba4c138aef2fb973b7ba05f894824fba268ebb81", + "signature": "b168c3dbe98662e458f03c7a6eee6e026380185b201caa458d599267babe486622caf00f0e38468ca2f3ee4e710b33c14a50555d0abb2d17c56177eaf580b69101" + }, + { + "msg": "12f297ca2b124fef4088eaea5b45c10e4ccd0fd25f3a92acd7c67a7f40036053", + "public": "03e9d39f299da31babea8a4e6d90e06248221c1c91a79049a856356b8e481a7060", + "signature": "4521499cda4daeeeed67909a6f6f527cc879be608cc5dd80ab21500c8c6997ff3e1eb47a861d5c64198d897833649ec8a7229f077000989256bddfa95ec50a6d00" + }, + { + "msg": "04eb1bb739c3b0624234c7f1fa4503ef27ff7685f68864d83dddbef77fc9e9f8", + "public": "020e3b20515c80fddb5791d74559956dda0a2e64b34b22261b74065eac86564c72", + "signature": "1104b25f2ec935b2d9bce6c5ef6c3cb8081ec209b5a40b2b6c7907f4bc7b1ca3164cfc1dec4bb8d038c58438ba5f5592da90ffb34dfd639baae99d7419318e9c01" + }, + { + "msg": "0324578f1b0a4ab5836c7a3bdf763790cc53727eadeb26cc46b72e7ac8e4d805", + "public": "030f65d4187fd1531b9a3c40303a9eb500d15c67645107c475c763b4e844632fbb", + "signature": "bff3bfc1a67326ff2e0f4eca75b792df1e0400f81b3dc916679676cbe02da5556c83c4dd7481b18eecd55870a0eb5db0c60a779a98c9f3fba55ba422cbb2c76901" + }, + { + "msg": "958a4bdbf9068d1d5464fda8b4c6230e767a2ad1ab3c558d5c264df657995990", + "public": "02100ebb4188fb7217879958f725f8ecdcda2b4080e07641f17893cee6fe7beb38", + "signature": "e06abdcefbf288f7a7f22495fb6b97dbcc013d84d958e8dc429d3e53b2a9195f65eee8ff4513ac0524d6285c183c2fb8a66e30119e7e267f58b515f201d3cf2900" + }, + { + "msg": "445fd9e57a60c2633a4b8b9a8c110acb191b357ace066f221fe8e7449b840210", + "public": "027ca4521ce6505deb2a602a7eb895db03bfbbbfccdc3e5a5be51df3f4b667c43a", + "signature": "7f184573a04b75e5f7c2910e7b27a0ba3902975a46d3f4d62d4555f682acfc6478f8676e9ea4a2d511efd9e0fbe3fad5f2a0c6da6452c78cc82dcde17dff80fb00" + }, + { + "msg": "cb5126e7bcf90ac8fd8e16425be182b1bff823d4c9bf78bc15239ceff3652006", + "public": "02421807dc097b43cb1393e58381c2e034296b45d023ba6bd0e75a21dc3e85d0f8", + "signature": "96d68316fa1ff9d1cc22816b269476ad79df1540184c83008bf578914fdcb53d231031fdadfde32bfff363764ee02849b7c8f527b0f7f995b5364b5971a19d5000" + }, + { + "msg": "b6d91681e95d5f0197ce9c6f665e4a7fe76a3735d9fc3563872b08dc107558f2", + "public": "03b33e7976f663d11f7a704c6e3ccfc70d3b5933dfc25ddb76b5188e878a284ee0", + "signature": "03b78ecb1335039becb02b4a45e4f21b7e3a3e1066022eea4338dad59b676d2d36d59af875db0759c3293c973c28811a04c6b64ef30df8e8c9c18c01a4c8d5ae00" + }, + { + "msg": "9a31efa8f36c52c6c7da92fb2d1f55556ee2dd1bd3853cdaceeda95167f67336", + "public": "02596617a3f8cc872b91959fd8d4867f6ca1d2cd9a5e3ba9766a80a1f2c77982fc", + "signature": "233baaa28ea121de21cd8b4c6183291e3b539dc2f4a2c85d83aed406a499a00369314651312fb4a76cc7a50542d810bf4009ff2458589abf371d297cdc26c82e00" + }, + { + "msg": "52ab0863b4f9462ee4736fb56d15c650f89993d62bcf714a162abaf39ef9efcc", + "public": "023186db1431fdfa794bea08caafe88de583b293b1be86fb17d31a0f0a5ed48f63", + "signature": "c1a1aa7b6b24928ddcb8f4819172347e58465c5876c743fdb90075674a234984549c8432977e3a4e6029311ccd9137ecec0dc4423cd490476a46a7e6249e212e01" + }, + { + "msg": "c7ba381632e0b824d32ff392359c77cc3e6470903754c795b2e7cccaf94c5830", + "public": "0374c18faf24c8d348c04a7b38b06e4668d286526b53bd220d9d9f61c376cc946a", + "signature": "f9cd79cb37a012b28383a25e6a4a57c4df81c02fe4e2c63f0cfcacc7fbaf3362061a61e10b07a06c9269bd0d211d2115a6fb413f7086a48ed835c9463c2f955401" + }, + { + "msg": "83ba7eb93c29a695c064c9ca3402c053076cff9fe5a69970c0eea8d5629a99fc", + "public": "034545e47fc15bcbbfa0946efe5d9f0ba8d36bf985e0d83652c2889246ffeebffc", + "signature": "b10e9a888646dbcc44600f76ea3d848b939e93bccf9ff385928604e478518a1e39f38441005f149d0b17cc3fcb8401b831e3a673cc5df83a15d17c47f1c9f9b600" + }, + { + "msg": "7a7d76893d52f5651ce15095f8a2c9cd4164dd0b87352f4a9089d7603664fb3d", + "public": "028f307ae6d8228aa1fffe5ab5431d411581df74499c439b0b6dfdd66f00d9c7d1", + "signature": "a47a6d7145e88cb453a5b69023f338be9e46903f40d2c804c19aeb79a4b6c68f3f88a1079f42a7a3809c65666d9fbbe62ddd2baaa0fc8e5cd617c8ed85e4b2bf00" + }, + { + "msg": "8322ab3cb08859f3d22432fae44686db628eb3dfdf96a5cf74db4f81295d3f6c", + "public": "02623a9c2b896fd8fb803992518862310ff2c5406c9ced7129e3db123cabdcb8fb", + "signature": "f539666bf63d1bff626af3f46d7075f223e72b43210fbcda6e4246d713b13108756dfeb7e4085bbf201baba3c2103c96a209445f7aea187c4994dea3a9d2e17f00" + }, + { + "msg": "4fa29976bdeca39cdfcf4db96bf45b6c6b5e5a15b3c188ea7176d3f418f39d51", + "public": "034ff218bf704d7429c5193daaaaddadb6f1750837ca2c6c91a4c3d55fb21470e7", + "signature": "e41e02a928dcf470ef82467d4f82ad1054b3aeb478c703bc320097ad4cc6c1de4ae58dc297a3cce8b2ee7a292591e2469a1f3e138d7bb6c41fbfdf6a41bd8f8001" + }, + { + "msg": "4ced4df2ef23a8b1826b28264d13de5808e48c60b0530642c6d4048801a75134", + "public": "03cbbda178c8df2840316e913cb2f8ef088e97c613343bd6cc4f8a5e6fb6c291cd", + "signature": "ed2c4e662b397259d1f4b8cacfefd663d69cceed1b42e5da589e9d130595a43e7343325e6b2bbd976c4f34d74e0624239167ffbb9ae3ced83c4c4ec7c203a32401" + }, + { + "msg": "9c3437dd44a0df0ac7465740723eb8f35ca6203e560debfb8f195aba563e7e1e", + "public": "03253928f9484105b0652364165416fa9a5486f07cd1ba95233aa60cc087b3bdcd", + "signature": "ab78d6efa9cf6cac1dd99dcec9c76b6b48b51bbd1c8b9fa7549b60b550d9ee0e1de99f3437f1ba440b9861399fdb862a463cb762403053f5c9dd28dc966e4adf01" + }, + { + "msg": "745525a42229775504361c878f44251d54e72fb65ff47d2d7f1eee3968f9f4a9", + "public": "0297171e016a5ede6b3393748a6f68f10b38fcaf4a85d547b4a089925528a0200b", + "signature": "69d2880009c1e98f9274c5b6e820291cb9d7d1e23d0b60cf846ffe298759e37702bfa506863316c332270403d2b7e46282379bc9093890004f8637ee54bff58300" + }, + { + "msg": "a4ab6cf2aeb5e6541e915f24432b3d50c56be51f19ee56044b4a6892e1c19f7f", + "public": "02b9fd033daba6f286f66b9acb80b8c147de8b1fdbd25a8cb382fd5a85ab15182c", + "signature": "1f99bd3048e56900b19b28a9968bc7038fb69555ccd585c41d37c41b7cd90b240f6e743d5cc6d5f3ef3f8d37a3d409c699c9f4f760bea574bcb2af7b0af2e0bb01" + }, + { + "msg": "738469509823c190072194bc6f9cd2f33805896d6917e6579b79eabc1c21f7d4", + "public": "02e91e4371a7489a21ae304184d0d054051baf11515be7d2c17af9efce261b235a", + "signature": "4c8234057e3d510179cea17dfcf120e1111192008b3e54304c97406552cf086a55b73a896ceef4ebdbd62b77c6b7b04affcdba537c98badcedddc186648999e301" + }, + { + "msg": "c300aa5fb20aeb5596d3566ea834953cf183864968e3abf3bdbab814a7b2c8a9", + "public": "0223963e1f63eea649f1f353f83a76d1c04283a54a437718e006147b06c800dc82", + "signature": "727dec3fecdfa335e4960b2d4e34c09e8d44250c89bf37d558842273d7e5304d2c04af0b18f197dbff298c4c140beb5bd34a6ac0849c63ec7b9bc7f8cf3b767301" + }, + { + "msg": "16e53ba6d754baa5aa5062211f299bb7a75c111361b96a429c0a07d75b68bfcc", + "public": "03446506ac0f5fc1cf53575935f12a5206edaeb37ba1e7d8cfd03a7e82c73e8012", + "signature": "f746494dab76acee6f8129921c1f0a4e84a95abef258462a236a69addbdd1dd765019b69a83bb3f9ddebeb93335ffedcb15dd9d7ad21c5becc087bd90ee259bf00" + }, + { + "msg": "bc18bea21e2891b93ec98a0ed318e2b14360cf4b46294ff4cec272b411645b0b", + "public": "03bdbafee84f686c03f0035bc188804a53615487a0bf1c3319b82d9be05639a5e5", + "signature": "b6f1e22b1e1b0e243cb79f248d28db9d33dfa3a03c2e979b1f47780bba769d7246b253cf66025a3b1f8ae1deafcc99e60ddf9d3a99fed46434d775cd80ca3ca001" + }, + { + "msg": "c339fef3e89a271229482bee60c34678f4cd39447159716a2172668bab0b6243", + "public": "0398bb95c5f55d5515fd401d2652b1afe872adcaa015613bcbc8188c80dbb3f2a6", + "signature": "d2cd7d2e692be077ee6a74a6308c34e94b2070451047644377aa6003f89c138e6cdf177bc30273e74b69de5e0f585b1d8dea3d88df44287249236685ed3980ab00" + }, + { + "msg": "a47efec21a497c7901db94abb40e05bed3e62c6b051b4c1f5fc67fe56baa5810", + "public": "03c502add2d2d771b74156a4d168d1b56178b5325eba22e7e227913161eee98e6b", + "signature": "15372a39c3d8e9181ed583a9ad0535d11e7f8b6a965e09b03be91a8ba46662f106c5f84c7a7eb84b466698f00a7f8100e80f96345763b202bc401bae85a83c0d00" + }, + { + "msg": "94833030239a0e64ab35a391ad05a42d7b28b22b7572594dbd8e3bc4cdbe930c", + "public": "03842965449136edcf38bd46a11a409fa45748479f6059de428ecd7ee449d59a1b", + "signature": "fa71c897519debe5f5f4084cb1288566b99e527188b440e5c5bb51db49280fc26e9c76d5b8d5be2cc766391e08ccb8dfa0e4e916bddc904a453799ef38f7a23b00" + }, + { + "msg": "a05c4bbfa8845783a15c2690dfbcca9e03f0e3aa044126351ca78e6385cf46fe", + "public": "03441b1e30551ec2a5b5508d09b117b50f7b937eaf7f95edc2bee6144260a15a50", + "signature": "eeb5036392639c038ec38f4751386a6f4271805badf2170b02fac188a53957183f5ac281584068dd535e7ad2b5b2eba4e03c57d38950f6830e0d9296b1cb87cd00" + }, + { + "msg": "f08c11d380837cad3d72b22a464559267e1261b9d3ab9488d87ba5347d3d2b9c", + "public": "03ae84d572ae6f36c6a3f1cdc629f5f91ce0ef478c9e19c0f96267c32ab88d6141", + "signature": "7a1b4c7a3fe09b127086bce0f41e5995a83534198624836b395c7dbef1c5b2ff1ad4c0e999b3243455a3637d25a1222504945889f126be37ce5fe37e917a508a00" + }, + { + "msg": "7d41ad3cd3f270483ff066769cc3fd687e29ce4802cff89dc3c4fb2167375d6b", + "public": "0261074a43044b4cf5009ea56c4d2d55b2a4c1e58883755546f03ec356d23042de", + "signature": "5cf77ca9a3c071e41e66ec01630d5ff6fe21851a33c916d5a038dad02f5f456206493da5edd0d2e68e181bd6032ed13c6166b36581f1bbf4c6faa03c6281859501" + }, + { + "msg": "164c895907451a070d7b96463be7d5d656b7d4e7a3dcbab60c61c4fd44f3680f", + "public": "02efe37cbc78550d5da1746c567dfc1f25698d05e3b5973ca8aa5c80a5aa04dd86", + "signature": "bb768a43b3c725211b48271c2c057c068be7ab0ab91e6811e555a9cc7609cfef4b353c22c3de5784757e735e6d6af5f8a5aa152404f76af089c831d98890b8b700" + }, + { + "msg": "e18462a1eac0891c359bc8a5eb8b17cda65fba96a8b2bd9c67a95eab8e703586", + "public": "039f439e6e37a6ec6ffcffa694a0cc5ad5225ca5aaa0598081646782f44e6b0cc0", + "signature": "bab5bf26af49ef94b8071ad2092510fa5b480ca7396520da2e5b21fb622656234ad6d15472dbb7b5df4a3e8884b3c9177f4546aaf4a52add02885cc9bb22c68b00" + }, + { + "msg": "7f00101af3cb669314371cdbf64e4fc7df938d8a15420997900a8f277c40a075", + "public": "033884a47014b9592152d2855c0026ee85f96f5e672eda3093598bcdb103815b5e", + "signature": "d18c022a0d2aa444529ec85058ff177a30c74be3e0784345d97d702142e2decb1bfa9b328a5917cf38a958ca7e6e97b195babcd15d45a22281f50ebd9ff738a900" + }, + { + "msg": "a1d380fe9121aa80316f774e4d1d009f8918127c65a62e2723d8d41c9e9f4952", + "public": "02e9004374af450c60fd6c2be52f230b9451e38476ab82752a1d8fd6089af0e5f8", + "signature": "c2d551cda73f2f85306ce6ab5f62887fcf09a65c2793d5eb0f8843750ab6212911a55504844520f8183cffc29ad0ca8027d5f2793a5921edf5b0c261ae06735601" + }, + { + "msg": "fce9c71cdf019ff3576191ac1dccf95a7b45907cd850413df60aa5875242680c", + "public": "023109d150cf515759983a54f59da9d20d8bc5c2bfaab5ccb2658449f9bf24d1a6", + "signature": "78568b8230691ab4e6b43a28b3aa95cbd4a368d82f9fc11fd17050092ad9f3db5e8d9b6b0057f76d1f91679936a0bba94edaaec9ae2749ab4910bf1ec533f2e100" + }, + { + "msg": "52edf7e0aaf718bb1d4791cfcf1a6e3867ffe35016b78816be8e1cce3652376f", + "public": "02adbc5454569f0af4c5fa0c1bcc6b411c600d06265498702dd250a4b7ea51a0c1", + "signature": "295182a6931a3961d11ddddc7061046288768ea6669994fada6a8add08ea83de6dc610cad3ef29573b5a25d71b44707bc58b290a40c23df4d58b525a0324aef300" + }, + { + "msg": "679af3abb13c4dea52e18202d7d1bef32922197a64a0d57e5ebaa71b33e85b15", + "public": "028fbf6b72bfa9723e019f0fbe99eec6baa73f9e3f83928ef72d094a126bad40e1", + "signature": "83ee54bd037ce225b8e5ebe7fb40c0289d3ee6dfc6c898d6102e7496cdf4dbae7e4d839a11e8b77e8d0859d6c57b8fa312957948be990dd69110744e4d5d84f901" + }, + { + "msg": "cf47123f40d553e05cc4583fb3aea115d001b64bcdc51b6d414e23e4f0f1e174", + "public": "0228130059b895889dec3a6ae4a3e28a2bfa3fa907b32436cfcbfc7cacd62db729", + "signature": "1eb5e9b43bd48edc9cb878fe980c68615eff3ede4704907e76268b403c770ef60b1f0efbfd5eb2797d294b7348d678d353fa332a859be8c9db8f7b8acea9a18c00" + }, + { + "msg": "1960f75f57e258682ebd79f105046e2bd46ff9fcacb8e22e9912034fef7c827d", + "public": "03e0bc5a7ab830686ad70f1163ba8fe78f303c47b7bbea383c2ca425b2862b653c", + "signature": "bfe67188d0947885bb5adf38be9d2350dc80433f82aad285b0cfe89ef8b4a3d26d7a7d758a73d7602da22722e1c0d2ee4cff40e4231adb6420ca473ddf8df29b00" + }, + { + "msg": "926e804c36ada479d8ebaff00f966fcd4d1cc043b0ae0f107737f86a92d5f81e", + "public": "0390de70c32f27ef20ec94f07c580fac5d47b0d422a07c346a5e635b0473e588b2", + "signature": "b6adf78048d20e1eadb9709d069d106576d4b3c34b16e67d9638182303f73a7a0afa5e95633ac83d29c528f705944b6f3180fd96654aeb42e263281e57feb68900" + }, + { + "msg": "3e450081494f59d063fb04e5e8f49a0f0f6edada3efae4dbe97c966167b75e10", + "public": "036b34461a70809d475eadbec9be7a015bdbdc18d2a4d0a3b805fe036b7e195521", + "signature": "1c7632ffc765c162560e9424b172f83fe3e8b0f63158bbe6b3fc767634f409200616bdd26bf3b0f9152875ba52ff0246be2d160acdbd01cd3bdff1bc2618080101" + }, + { + "msg": "d93fce08e59fa8f1dbfb09c79226cdfee8c1f1b461a168074452477ff8e03e5e", + "public": "0289a06d50ee94e23086a20c1a06d30d0b75f92850fdbad79dbe9b87ad4d1a9548", + "signature": "6e8bdb887b54cc16186a3c3a82ec20e42e88505b561270c6b9b5acf672b73b1466319db312712c8eacfeac568e97aca8c5dc6a776df4cb1423c7f695bb8e29f300" + }, + { + "msg": "ddd1f5c6be1e2116bdeeb1440ddf654cd4dab727c3ddce7a91733be7d1c3a6c4", + "public": "025f53e9d681ad31f5372e21e1084daa464e9fbed1a75d1717008ec5c3fe5c6ce9", + "signature": "aba9aad274bebae8d376a1fe3c65e80ddd5ef36e1539c69c04ed5c3d91757a471be76b11e1a066a8a146039d15491d1698483e4e6a055659503deae716e00e2401" + }, + { + "msg": "19f0ce1f203d1f9eb946efa1e2f1cd64b2baa25a2350d5fba56e6304ccd678dc", + "public": "03218a7e8f36fad9d767a76fa210f4b96fb01e242659e9018c5375bbd11ecee3cf", + "signature": "a15108353a10377f5ef6da5cc6f9b0153b0fd39b55971e58c51d130dbf19979204180bbc1aef2172131e353cd14f2d036c7ee178d31d42252065062cbd2e10d700" + }, + { + "msg": "22436e4853d30fd3aefefef4b98d67740fe2253325c29cc685efea5df0b9baee", + "public": "037af1932918cff245304b903b1b90865546e28c9b2c5e8029ea2df02cfe43deb8", + "signature": "7bfccd2afadb224dfff4fb70fa7804d68dab72abe6a3b9f1eb732830cf8702d035eababb6f8a146b37bd19c6948c991d1dfd703964bbe77dee3a6713741ef4c901" + }, + { + "msg": "4fd33dd117315b01607a833e7c01a3c27f535f5a8e2c7934879255581cd5cbbc", + "public": "036b87f3e1b60398153016574802d98c6f184988135dc089e5b9fb0bc6dcb2cc82", + "signature": "2afb7af9ba44c9b1a654f998e3e29232f8b094ad9afc753daea8bb131c5af3e6146b0acc91f5872fa5e8d2e0ea0e1b8a39d76cac9eab0a4a0d41fc833dc83eaa01" + }, + { + "msg": "7d485e9430416ca249f244327f62dda7abc163a6e6c0d20e6d9a74572399faf6", + "public": "03fa7e3904278ce777e81dd254b76ab2a108992c656561b8a43a2eece48c928046", + "signature": "874c889a41afaea6ef3784d02dbedf6d0f0c4130e003c6bd2981d4572ac84f0c5819dcac16daae8dd01232f0c68bf5a06700bd99f15f7a48d678a7dec0c8b1f800" + }, + { + "msg": "bfac57e436c155db299144a8fcd216b5d694db3a1160fa3a6ae574d9389609f4", + "public": "0378b2c943f20677467941624cd63355103b11147a2d70e486832191126b348e6c", + "signature": "ef4ae9a5bd14b8d236b88163f42e11ea49d828008fb79212deb7d1568b1ad11d6075fa382e5b61318966781e4865449b81652be1e6240a587b96e96b5291880200" + }, + { + "msg": "69c9bb3ae6974d1cafc56dbe04b1879dbdd17934afa59f5936d7af30073e70bd", + "public": "038a762606924c83031303f5f0ccce531583213ae04dc0f44d6bba06541aa1092b", + "signature": "771e724bc76a3f8d99758a5f9f24a3677ee8730b3c3dfda5deed03e9e37b7e2f706cbce8d279ab4266ecc432a2aab0219675d4eee5d3ca929158a1ac26d18b9601" + }, + { + "msg": "c52225b22b8d542d8e6616f325b5c96041919bdde2ac447f9fe957570317ea3a", + "public": "03a20c2bccac5ac49668312b2da0145ed1eb92b1de2090c667e0c0d8cbbfbb6fa9", + "signature": "434febbc358c48cef3a4efbf1212242af07cd0939a5e810d9e65c464bf363d8d5ab67b179e067ccbd554253e9ffab425a84071f555ca76ca60cb8d65a098b1ca01" + }, + { + "msg": "96b9ce54d0e85e7cc355c1bd727d45561d99ecaa1cb697ef2a1421ec764484cb", + "public": "03525d84aaf798c74909a17992003193166c07fb15585fd0d3b1b536b1b4a69d78", + "signature": "2f29105f779cbb92685dbdae22fca4728476c7d1c2061f3e64ee2357863a3634236b435de0e64684d5b291197ba02558818a0182cc3bb65311c49b538e736c6a01" + }, + { + "msg": "6b5688fe036c8eb9428c42710454a3d1cdb32932fd2855ff8fccf1df33d510b7", + "public": "02833f9a790408bad18b5b02e1a2abc299d4e43412dba2d508e6d4dabdcd4af4b3", + "signature": "e6a14a348e88b5b88a5bfef99ff875bcb4e3c319f3a3e6d88fd62673e662d3f729b2be101723a507332f40827a296ee3c4b2efdf82c3d0fed1bd7f71fffca92301" + }, + { + "msg": "5ad229445332b1b32883fa32362150241981378ed172f20de15ffbb4f61ce282", + "public": "02157cd2f9c5e10005694b8275321c96faf4fca2ccbb0d4899d1b9e0a2004948f8", + "signature": "852147d6bfc915734ce5045968b2b1f6f65ed68de9314be249fc3e9844dd2ace635e13fe5f7f850331b3394700315893cf241b2fb78734aa4a13257e4779fa4b00" + }, + { + "msg": "d49f7799263d64165e501fa14556f130e45e9326ff7d5653ff97f78a341f3d7a", + "public": "036982b1c5a6976385fec62cd00277bacbe0c77640b0e950248d5eea71d3b155be", + "signature": "c85c84c700cf2e45deffb02710fffac594a3b33dd415e5e7de9725389b5e3d5f73dc40bc110154c813c195f83c55fa33096687ceff7e8554f4a2c188c659510500" + }, + { + "msg": "3c5004b47789981a1da3db2140495fe19feaebbf3c826c9e9de3a2fe28ba0bc9", + "public": "03fc56103d4a480e78d0005888fa2ef7157d77498e3b9020792c87cc5b3def9de3", + "signature": "44f788b5b664d7d99f4cc2d0e6e6332514d1c91f20cedc1cf994973a1d8301555c551376d14eed7c171142f70a22b0b566bf64fc4458e0ebdde7dedf321c171c00" + }, + { + "msg": "41bfe75c9172ff886b548d382994fe8aeae45aebd91f5e592baa2f421aa2096a", + "public": "03ad8c5fcf627e52b8bae873d1e3a0b519fcd339ce8e865843341b7482d3c87821", + "signature": "3fe4b20488103d94a1bbc8a58ced5142663dab8a1dd6db893a8a06353b3393834cf60178a7baf6f7204b827ab7d91858303f5646c5c9cce6de102e32ecb155ba00" + }, + { + "msg": "89f50c90468973e4682535cdeda35c04de67a71df0309159774a1c4dd84b19b7", + "public": "027820e600dc174ac71e319fe0828183154a34c27c5ffc79fcf20acff1260af002", + "signature": "50b7543501bdcc9c93ba897c24f7af1fb815e46edba7e6be7be8a5170faf421f18ce5bcb9d707adef7522250b80c529b0166da10fa2872a153766889772a35fd01" + }, + { + "msg": "4b09ada99a7a75b2aab3a3a42f67662d804dbb8b66aa3fd4700a8f7979c8b1c2", + "public": "026b481fa0bb6e8ba10e9111a5197038166f0a6f8fa8a61bd0e46b1eced4be799f", + "signature": "08a0324c0b7d6d311339e2a46e923f4902644d801edf99ada14bed497d595da46b2027680cb3e6dfdf790ae82d0d6ca9a4d7fe760476ce092fb5a7ea9e1c5c7d00" + }, + { + "msg": "dc773edbd2177cb77b64a1a942ded630a2c1712bbb48b8a10349a5bed8d22807", + "public": "03f4d032cd68079b44e775d56703565224e5a94e277ca4055557a8f09ee14ec428", + "signature": "51894016718e4b8a279cb00a451f10853a1e74a10448922be297d6eb5050fee4680d6df8ebed164f5797a6ab2547e61683bd4f0c104ae2dbcce8028400ebc3f900" + }, + { + "msg": "c6293b9694947257744622fd5425294933fd3512fc0574b75ad4da96b0b3a525", + "public": "0347fa429023909fbac3c32b67ee2b705ea7106ed9ae0bbfe2e35781146f6ab351", + "signature": "eaace7d9a0536f410fc8ed0ee59b620c99bddec0aee5b55fceebad3a839f76300bde708b0bba9a0fa663ac49043786b7c6d328536b0f8cd9bc732ed0819fe46801" + }, + { + "msg": "5e98afdf4a518d715bad40dfd751f09c36ef762445be9cbd4c26799033837778", + "public": "030b18940240b7a9d06fd73c2ce2611de315126c2b2f5c8b93be2d5bdc02fe0e3c", + "signature": "374f1731b2f86452a6a88e2f7842bd18b6f8e2ba32ca168a85429e845f583c3e4ca70eac76b87974c10c61f22980b7806aa5e3321cc030c5b4a7e54dbc8e908501" + }, + { + "msg": "94d239a1f39d9f62d248bdbf7de0ac05884a1ce2c6b119435127301f15c70136", + "public": "03d879c5a58bc2619b31143c5502a07ade0a73680761956d15bd4b456c80574957", + "signature": "88246dd3ebea2cfe956c7750fc9897520915a69d9c0a7bfc2eb991fbf8cc84b814de2e2118ff4909e344f9592d78663fbfbb9b7527a4a40669e9d6f099b7eb5200" + }, + { + "msg": "c770ef59fffea0e9cc4acdc4d943e08c73d0700aac3e4087b825da7b0eb6f1de", + "public": "02b9f6fa9dc7c39b378a4d31f5d01f7b93c7397d45bc67b56e4c2616b264c7d144", + "signature": "f6f49ac0bc5b75f4f3f11ba325fd4841e6cc90cc2781054e56ce1467eff8082a47879e2275404720bba1bb337fcf07c14b14c41f9092800e87e373f91268215701" + }, + { + "msg": "76dc6d34d093dfe1b1c1bbe794dc405d761f27127cdeaa3ef0e6aa829a81dec1", + "public": "03932e4c20eca4320a26d60eb3682b733067bb9842913c2e8dff11c7992e939199", + "signature": "0805d0eb47424994b76487edd5e01800176e573fc9906b27b0c671deb60edd092060aa7be791cc422b03c7e88d9e9425e68e1b183d224ce1fb944aff82c017e800" + }, + { + "msg": "9dc93362931f5fb6195662d3277b4c92b1a2842eaab1d24c22fb138feadb74a3", + "public": "02f58a86c599be85e6a7b4103f4d2ef6f01b44bfda7493677d3201e92795e7641e", + "signature": "32a4e6c9683b3d45e05f18485bcf0c2047b26ea756c0b6a290021cfaf13f50a953d9427b89d19f21c6083fcf17a1962a12ed4fb4298561d820400786f5b727f901" + }, + { + "msg": "c7db05cdf5af763077f13fe2e714a409896a7e4351120abb42dc0250a6e99d1d", + "public": "021419d903b1da308722df3553550c796ff0b79112820a422933490bac424ff432", + "signature": "7915b2de75d1bba17a81ac468ca4c41ff2668374a83ee25da216aff6db2f280c6c2e3c68e1943dbbcae24d5db15248685d2f16a27b2a9ec3255fedda6e11655701" + }, + { + "msg": "93d39132bb5263f071ad4521eabb5839bb017e1bcd7b9ea55fbae7c79147f8e4", + "public": "0323a990aad046a4df424fc18b77e79a8ec75edc212a6b78624a9ae5aed8c79f29", + "signature": "1b36a13885734933ec6a34670b2a7ed413819ce8f2f16a718ba5794474f3dee56b8cca0962e3fdd71bfa0c6cf98bb47c55d5943d4935d5c7c4f429e98f2afe5e00" + }, + { + "msg": "9ec2939da635d26604c9a677319456dc5dbd15558353255764279e5247ecb61b", + "public": "02ced242803f47a86555de266465899ff7ba279ad567109deef0675513f8367b9a", + "signature": "b2d9f9470bfcb03636d149790fbb9072135aea1d43478085e0aa78eafefe391d13650a4043a9fc98958553203e4b0ac8630fea75419a1573b1001bccf01b558401" + }, + { + "msg": "025884d79596bb826a6f96346ff461aae6d48fb391ae0b59444a5708bfdb2518", + "public": "03eaab98b4e5791560dcb8b4de021fd7c89a92f5191395524d1737d46e3a6a7072", + "signature": "76f5af50171eb4c533d08aa027b17e1a6f0c9190f1595d5ed88263ad278b5c48103963250caafaa627eb0035fb6249357c6061ba63c9747bfb626c460a5460f200" + }, + { + "msg": "c3e64dd0487f7d4ce65cd4b0bc5aeafdf8f9008fd29441a4aea50bb7a7f417fb", + "public": "0397a4c2176af8f993e3e46707db836704775f528a26c337721e7f56f43781ecc0", + "signature": "080ed37b79d046d487e905e9dbad2a7b5d471c8be0493a90459a9d54c3c1f3d77d7de97b55fde42374d2bd7237a8d69671b144335d71f6b318eaf464f1f4e11100" + }, + { + "msg": "0ccf0788cdcb12810d0b1454f7c4c2bb0184eabe9a2cfe2531c6000053d49f06", + "public": "02a0cf8aa724756fa8522ff369b3fe96644d42f10a02e9f7c75aac6a416fbf689c", + "signature": "857ebc4c2a2e7be151e0d3f6dfa49b1c3f86cb0e6b74e3ac3420059f2f3c26313942c04819b6e5731ea7037ce9010933f3c73bebbcc13385ca6a7cca0e12b5c001" + }, + { + "msg": "4c860154022eddb3883324f2d51d93b7ab741c4dd25b9c3bef799db0ae5344db", + "public": "03e6cb13eea9391f87e962399deb6445429aa5b8082edadb46041498f5b50afff0", + "signature": "88744628f0a48cd12de3d2d75085ec753c6790ad61eb1a1dcac1daeaa7f50efe568b8aeede479a12081a6e38e1b208ecda08ce81b0b5685f8f5254b6dcbd8f7a00" + }, + { + "msg": "0113e04b61c67c5671e12c50a62b495685bfadb86c40b2c693cf4538de12e896", + "public": "023ae3efd821937e856110201ba21bc625499e37ac27041be02952d09190625ba1", + "signature": "5bbc2eb1e91a41f4b6dc6561667757169aecd36199ea349da6d0b5acc4d1ecaa77e5bcfaf2fd59f2338021528f9ec4efc76ee36fc6cb9ab3ae62d7f645f7a85000" + }, + { + "msg": "b2a9f64d2d5fb589605efde5ddf5d92df6ebf5d734aaae43c160979f08ee9a54", + "public": "026146398a533cb1e4e05b74190202e6850f7a5dda9367d613755f34329a7d17c0", + "signature": "95218bc7c360753c59dae7ffb27ceb3e2639193412a8e2aa20d3f395c93473165ef48df73b0ab42921c8a7b350ee14af68da829faac9947cf9466e829eb9675401" + }, + { + "msg": "5c2a3b4b726e51c3cef5e21419ab5dc056ca662ba0806cf9581491044eb13a4c", + "public": "02aa5ddba756ff435714c80d1a2a3f36b734a922c5d21978f6ac4c87955981a539", + "signature": "9eac9d57909bd07636f0a6d64361c2d277f588cee9741a4e221790535682044c1d2380844a98b37165eb1dd0fcdd691059a540a9be5b7de5beeb5a841f347cb600" + }, + { + "msg": "0befb28775b8b678e1bed23cb3d6df6a696ef4b019915429ed9a84914beb45e5", + "public": "0267daf13e1a8f9a9be56351e5a370edf441bdf44ceeb07ee2fd89c5186f35730c", + "signature": "e2b46c68cf4f17f60976e6bc21906c6a8431503a45dbbf2819f0764363c69d3923ac379a3d1508aa8993507ec2d26de125c671aaebc787f64cf5c43352c5e4a801" + }, + { + "msg": "ae5f4184a9d51956bfde709c0d5bfe0285dcddf75dfed7dbdabe37cb2c7c5dc4", + "public": "0275b2b37d6a8e02d2a383c01b3fc48300fc79fd6c4bfe5b1ceba73206a9c9f559", + "signature": "0c2e9e5126b7af7acbdbd88e34f446dd6a0e616aeecc6d1d79a0093a53f1c0c10fc309b368e745bb6dc589c508e28f3d0f3115a954d9c85ff422ba8d60b225bd00" + }, + { + "msg": "d6dc458027dc5b0ac41464eb6cc3afddd04d60db7130829451c0b2980b08d5e8", + "public": "020a7c721a860ed02fae18746acbeee4b9509b23cffe0bb63ff655e78b6e74946c", + "signature": "f952c51906c56367d7f1fb7525300333ec056af52a5e84f52cba27025a6a23420707a32b622a323299128e26d15e5c50e55996c2fd9c5b22e106d9483cfcd1de00" + }, + { + "msg": "04f12387e22c2dc381f32acf0b8313b327229af93ec59b4900eebf7739cc25f7", + "public": "022790ab589470a57dd2ad49d1aff25835d19cccbc5b7e636a51225cbb46a3353e", + "signature": "6b8d3980eae61a814d22adf2bca71b33c83f8532f506b0073e16cf7cfe8afd7e4c00d84f114388129605d4871de5370d60eb83f6c9050e9ef8334749999e504001" + }, + { + "msg": "dcfc5cb615b41c0d66579dc6a4197fe66edeba028755948c354a826f7fc6620d", + "public": "03536a1d56ebf0dad8e7ec223adcdeac47acaa84dc660ad10f9325a65e34411a58", + "signature": "f185d2320b7b9ccaff26d971dc695115e956ec1629f207dbd7bd90fe5875fffd61adaec5700c7d725da91da671e995dced8d3dbd67d78a8981b8a515f862a8e101" + }, + { + "msg": "a5db20dbaa5184cdd2e3017314df7f44481ca3cc991cb4fded693e7c8234c182", + "public": "03ac82fdcb9fc2b0a3d9428b4e789ac9da7f55d2ddcc50e7899f3d3ce115b42d3e", + "signature": "ec502b6cc06351eb1b66387b94c6996afe75a4d1fe24a6f1c3f1d89fac928df83fad94e5fbbe643b28fb444d582bde42880df6bc6ddd40db276f7388bb6b18f301" + }, + { + "msg": "933848e70c2cbcacaab64661417a67f8f46f6ad4effbe38da473e2721ddcf8be", + "public": "02b86afe1bcb784e96016bb462873bf64c6459a25f3bed71e837c2d9fe7c7b2da9", + "signature": "356b5824c70847dc44e139582109cce7ebc0348e9e255f2e3edc27375b7a613f41f616fed2a7acbd3b123e64986857fb51dd3958acbd57f484f3333d247db18b00" + }, + { + "msg": "c45524b8c421184b77c74f563f14a0a3238b9033271193084c47c1a5ae675cbf", + "public": "022ca9d5c56918eb4055e67e2fa8e9cc69bde096c5aa1bb0c8107805d86a866c03", + "signature": "61fcfc1a6459640df7e5219c652d17b2ba8bbe28eae8cc53b94724b0aadfeee3752e501ffd98c0618a2daf53a682b27f1f8d1ec827bf9a39655323a1e0f68c3501" + }, + { + "msg": "ee709aaadec038c6ea52c564c160e2e2b55818ebbf478b63f5ca5a95b8745527", + "public": "03638856e9889eec9cc92557c1281cf7ad47afdfaf9c47b5d6816332fe061e5000", + "signature": "1730d3af72ab31f1e1becfa2811194c6ff1c7345583fb64d8357b9bc4ff9745f6aa310cc5ecdf951a6f58000ab9c65a32fdf6c0cf4a2c45d53831f80371b435101" + }, + { + "msg": "286b2f3b2ac538a66d16a860d8a2ab3ee25c75da8f3b0ca68ab75285946ffd6b", + "public": "0285c87814cb6458a2859baea328d3a32e83b1800fb5bed8e47f8a5be9afb7f177", + "signature": "889dba688dc1bc8ca810093efaf2a87d997029bdce3b4a4b0cefb21464ad83282ab1d9a08af713bb65273e5daa6000829ed4ad8f8845a8af689161c49476d94f00" + }, + { + "msg": "3386de44ae7abe91bf4ec476051d0f3d55b97c1c8d4d8b7a92d53935874e96de", + "public": "02b8fd730a823da73efebe11a7054ec0afe90e1ec00739e55648e486e95a894c39", + "signature": "14cad8e1eec609f64f43058a21a4b4a80017dc155d379fe8845344279481b0a46f9c9a1f7c122bdfdf321cf77d0b2df9a516f832e00891cd10c5cc263457012301" + }, + { + "msg": "07e587cfc7fd5cd3342cdfa4d6774753372773582e990d2ecb077304f5adcec1", + "public": "03f556c5f8f8365b3f6f84c95e353695dc9e55d2a121d09c9669119f314aef170f", + "signature": "adb0d6e32ad324c19b2087a8ba5db60504d2b9162f1287cbc2109cbc6720fa6513de578eacf8441a7d6254b144ea47d077c4fad4077395ec31ce5a950e0509fe00" + }, + { + "msg": "853a1f6d070021b7c85ce4460500b5d44ee38610249a2e0e1901acb390fe4ccb", + "public": "0389a36a6bfcd54af9e519fe4d3e7831835bd819af17546fdcf63a011c5c98b902", + "signature": "d190ac27ba07414f970f6307dc70bfd62d35a3c275048ed61e87dc0042631d294fe6d7d6e5d767a20b7862270c1fd1bbb4e92c844135517771efe72d7b5ee71100" + }, + { + "msg": "1407379ad990df947fa6f68937d64edaf3a54ece6f3b38cbe01ca7234bc4304a", + "public": "03c5c8c88a198092929e78f11d486f48468c4b00c7cd968a6027db91473f3390ae", + "signature": "fb8b664702bdd29be4d6523505bea7da45d2e486a45e8cf54333c2b90682e59331090cd4639fcb2dafcc15b3da4d0bc3daee36b1635ab942f3fe3646753cf30000" + }, + { + "msg": "c9683e514178398b2b2d2e42eeb007bdab290e978ee4d2dc436f5c45767fb272", + "public": "03d136db028b07a40d4d94d1b9679d22b60276016104f0be6fb439e3ce27d7ee45", + "signature": "6f6e4b846fc946b0089e24f27979889b47b6f83443f26464a16f5d1e9d816c0b329b4c29b9ec7c2e192a52c3188ff9c66885bb9bf3c54d8160699972e9842e3b01" + }, + { + "msg": "1d849c1c2530a799af707957ed2b271939d4765d412880fa96473fdeb7cd2ad9", + "public": "0212b62cb9288d72aed1076b84e30a5b4625c2f01cbc2529fbfc6771859adc2910", + "signature": "11e22cb59dca2b574415094b0f042db9cbe4b6a2c54c9f693bd2b0b601fc9bc31f66b30ff63fe8496026cd4f039dd90b07db6aeb182f77d749ca39271ef1555900" + }, + { + "msg": "9ab54651db79bb295e682764c4ecb923c2e237db451ed176ef0af7bbb21de1ba", + "public": "026c8e922f52cbe0b6ed9c92b5b5ddb6b65d194d035eb6d8a9778ea37fa4603f98", + "signature": "c5532f626356c235b94f7a1e0d2075ccb0b38300e24a9a56c7b6548b7cbbf9103e00846c0f3c3b8da0773a0ee6261e1432cfc72f30e9696ad5260bfc975e857901" + }, + { + "msg": "bc02dc328a6a279122370599e8cfc2a8e01a9e453ddffb84b9408cbdf82c5f6f", + "public": "03a01795f2a337723d699ea1959affafb3c00169f5f76993a52fd2f3ddfdfb859d", + "signature": "065a0a24b7fad521ed6cbb8c6540904a6683c21e73e6216fa44b031dbc6b29d4069f6c02562ca547a1bcf3b75320b6eb944041113952d46979cb7d2e5a958da901" + }, + { + "msg": "032a86fdd8a9a73d979f0433722f831ece0235d538bc08aef1e8a48705332402", + "public": "03a48a22cc332106f20207320693114fc1bd50cfeb6687c4ed5ae277bf3fab3351", + "signature": "a6c9dfae8d57c1d451fb48844e6e86775f6cc207b197a59369e988368d8e7c2151677e22bef5cbca142717b524a2d2f088cb366825597ad60992d7fc13953ac200" + }, + { + "msg": "c4aba052904d82d06baa56ccac0c8d4560d91cc0fe390967d09f21ab19a825ff", + "public": "02686b0eb9c3a8c05220bd3833d1098e4403c40d9c44cda29f38338bcb0a6732ad", + "signature": "a7725043d2988330501e5bed3d278f325ab8e78333e697116f0c0ee626b6cb5264d8e58002b97ad0e7a50d64ff451db028dd7aade0b913fdcd415f33952f470900" + }, + { + "msg": "de1f1838f64215becdcf6abe9848c065e43f36adbcb71fd942b7f7b7d4a83637", + "public": "03c2f8a402a81df49a9032e098424e256e6ac7d0aa2c30624edfeef779ffcb2ad9", + "signature": "cd8ec780d703262b2f08726082adce5670e3064b8918c9b7bfae2cd1de47689b641d4bff92924b2b01f3785a4ba2d130d199ece4e05268ba30788b6278708e0d00" + }, + { + "msg": "b041cd62f589989af4a9ba2710898544f3b891d8a2831efee4710f1cd2e12f4d", + "public": "03724e0e6593a43c5778f71f02481eabcca8c2bd7259966df427d57e76fe9611df", + "signature": "de7d1314b8cf60e0e4f6953fdf06a1d0cf8b788c263694baf7fe3893341f3b86094ebfc4e26fe68b21fc884676e0d9886a5ba2e260911f78f0b9fa156e49a57200" + }, + { + "msg": "1b7db2d3218ff8ab06e747aff00fb6b064ca2470fb72ee8048361038a75a5a22", + "public": "0337274e38041176819a3d3d9974b45b37e95131f0cf2e89db6693e3ffbf6f2239", + "signature": "511b00146f3f71c41cd777afe582c9a3f959123b36b11fc69f056e11e73355c1520de8723a532d887d0b02ef6846ca93084f82c74688a30285d64a43bba473a401" + }, + { + "msg": "93f51b4f331b338c9248e43edc6695331a011d7f22213096c77d092ddd8aa379", + "public": "03aaa31287bf17fe5c55b8c3528a8f4debf154ee8972af92f1e357d28bb93a8a67", + "signature": "70ce706cc6047df9d2dc1758380b1670a879eea8a72379a4147a5dc2edb425aa3b34684a14f54115e36e9270ecce6ad44a2594a8c5aec09c2d68da83e815c28b00" + }, + { + "msg": "31e42d26d74bbc7915678bceb4c8afac84b199d1db8894f660db59fdb1b84295", + "public": "037ca7676a70308c461509d0e02f81afc1708a2e0e6d8b6bd7d23f418c772dd167", + "signature": "54527fd21a1970764027b2a063a4a6f6067a073d0ff4b185a550e6c59f4eb29c2acca45978cabab73cb63516a4615e2508bba9cc3c55daeb5e17b4c2b2b7e7ee00" + }, + { + "msg": "15dbd0da6716b1eec5b4b64d453967e6badaf94da86204e6283a832524ccf185", + "public": "03a055f06f9a0ab65f96a95128cdc2d835029583e1a107309e3572a7b1dcd57a7e", + "signature": "d388f75f368c787e293d23a9e02467e3610dfbf39e7bbeea3aa21ed269c6622b69b173ed0e9b09aca5179e6f215a3ed138b46a85ff4b7b7c1ba42d58eec87f6f00" + }, + { + "msg": "ad7fc731d2f6f4ec93a44ba84be8219e5ebb89b8f125b201d7476b67e20156a0", + "public": "022b3ba1e116bfa297f880dfd1460f1942a2adc51f428a24d47fc8f13e6389a3b0", + "signature": "655c90c009643d0152878d21c0fe616196962cd80a89c92cda83ec02047e4ed4310aefb52eb9e99526d1523a3f106e19a5a731c24b003eead8ce9b8b91c5ed7600" + }, + { + "msg": "9d9f11bafb363386b0f5a5931d9b2602f0132bfe12867030009d142a75f1780c", + "public": "03fdace9a987f2b7b03c82eab9d70585fc4df126e81274b0523b87021508cafa98", + "signature": "f234fd76b9648bee6831b3fc770aed902b199460cc80688605a51a9546d19058570935a6ca24ee0b80bba9cb10590fbf3e3033e387bd910629f21cacb579c1ac01" + }, + { + "msg": "638509b6e3bc6c23f7e5dda3c0402d1f15e544ef71c233d7062ebfbd064e02e7", + "public": "038f9d6a94a697bce2786366a735858a9144b7415e11a7980592e0c79f0533fb8a", + "signature": "b6175f8032bf4e0aedba3d459f4779aee92c6cdb5808b70db1b7367ad5a6b6c137bffc4c977cc8a5e744e11ea68bc4a310b97d5f41b44fea4bf808285987d03100" + }, + { + "msg": "a6a0069c8e12bf403d51c38a1118aac395bd9979a1a33b4daf84c5ba7482b970", + "public": "02fa879a673f860f28ef8431031639fa43772c3374c714be1b5f0dddb7dc0e93d9", + "signature": "391c80b0949b3df3ffc350225bbaf34bf92239dcc998efa8a810d17af6c3e18e5ae4b85160be6cd9bd0554d0d2349309747bbd55b901a5cb786c609b24a9be1a01" + }, + { + "msg": "d3ded209637fa67fe6490ac2028488d3aa88d82fb6f538809a3544386e9339e9", + "public": "0226d232a4730d0ddd40cd12b0a5079999371b913a9f73f4d08ff92db600237ae2", + "signature": "ba59f24c5c696a1e88461befaef5b10f66c3aeae09bb24ec14b4508097668dff274b14d445c37dfe84fe52d19a05977b1c76c0341ff99933db2086cf9904b0a400" + }, + { + "msg": "c1a69018668ec96c191e8cd3a0be8fe312938935edf9f07908c33d656c2b5496", + "public": "025e978f74992d9434d2b5d89b1f3729a4f974f00c869f82b2902afce67705d37c", + "signature": "fcc376c1ac54bfdf3523a1fe4be0cc2b3bbffaab1268881da4594c4c0aaa86370d903c0914259738efec172b19fd15da509a0028bc292c0f2c89af78bef11f1600" + }, + { + "msg": "71a733853f250f18e12719ce54896768979dc3b8c5fc8797c9a8e0946f051943", + "public": "02afc1ad2a570ba37ef881f8661f2da86a3e60d9315aaf0b5a7d968deebdd209bd", + "signature": "42c4da5534204fd7989f1212e129aca62db4430fb55bcaf1d7ab0e3fc913fa9600ea8193dffdacd100141e3ca962d2364362581482448222801e1a4998ced1fb00" + }, + { + "msg": "44dabd4f167123937d08a182a35dad7e74de17d8e1716bf15777b46b017eb6c4", + "public": "03a89b1525844d2f6f6a092591ebfeeba51c0f8127e676f3f8f2aff1d90efeeddb", + "signature": "a026bdbe7a209135e4fbb5d353304aef4ff7fe45413deb493186af31ca2fb0893098952cde4edd960bfd26d8b8ed7ba94ea5c5bc65088804c2b9b84f2be06d4200" + }, + { + "msg": "640cb6fb7defd56c7bf8d4dc44de2d6e7837aa93f79ed1de7088301a4447ba19", + "public": "03b611f1a304682fccfd3ab2248fe10ae97c897b701b78ac9c066468fb2bd4493f", + "signature": "7c90909e29537ce8fc6d099a00093c7365ec1400739887224c503787de5350fe430ea3b7d80c82254f26cc40aaa03f713aab337ffd1aeba0b78e9454f92ed69e00" + }, + { + "msg": "9554d3a89a1aa24cbefa13cbbe7bcb57e2b6bb668f99a2734a0539f778a6399c", + "public": "03fcb16f66edfc668e2a47447a35fed46d25ee3e54f2a67a774d974101abd6a03c", + "signature": "838f0d8621afb2ead38ed49306b43ec6219f32d5292dbc61b338d56663df02fe228e396e35ba9935729f38c16940894a1e34cc6dd21f639594ed8ae7ea9c1cbd00" + }, + { + "msg": "7830c6494e4d78e40f3b921f65a09d2d473f4f3925dfdc59386c1f53f80a70ce", + "public": "032b86992498c182b23848cb78d963fa164261890a2609e27764251d7989d2be20", + "signature": "daae45a67ab6a49b2bea152045bc2f00d7af5f4dd39520a4ec7b3d1d20da353666f4fdb79dfd0db62472a5057192719cf3eb6f3883e33d349b6c85b33c8683b101" + }, + { + "msg": "ead59e94a77a16366f9c2f1a6b6d13e128933df61dd358c494d66ff7ff15b2bc", + "public": "036667429c2e3474ae8bd90b703931e033dc74e71e4e7063ce487cf5ec150bbc4c", + "signature": "c92e4fa64b93c4b9c8dca5512c85ad4f86fd129fc961c58580e23c2e81b50b512211a729775ed4d4eb2c2d563436435c01ce49002175b84c93edfef356b84fdd01" + }, + { + "msg": "4887e01f47e15782c1cd1854e6f9d35a4b7058208e8ba28d1eb3a1a7f0a48534", + "public": "02736c00acf2a700aeb019f50686ba33872b5da1c6bf2c0971e7271861d7a26a76", + "signature": "afc1dcb43fc30e365f2086aed106485b8e8b0a8f8b950229799416dcb56004856d10b7a2b81d7e117408ea18a287e95dccedac6222447f831f198d51a78f1e2d01" + }, + { + "msg": "4139cdeb3da2e569059e32083f4dc334e9d8f57123bae99a42f9ed72469db2ac", + "public": "0253e98979b0aaa096bfd2c356513b1a319582379c75c6b959a1dcd1bcb5dbc35b", + "signature": "57abe21cd5fb48a1958ad6d343a2da588b01c896a7d8b8e57218180eab1b504c30e28ed102bfeb90f2323d1322c694f1ab0553ba02cb5eb34624805a02436d1e01" + }, + { + "msg": "cfcbc6fc3e89c4b872f5d974965c468d04e8aee52ee5da9e4a43439e143efc4d", + "public": "02ff53bdf008de96d46f770d8d3b55d2ae3b4f407118181234ac6f464dd0a0c11f", + "signature": "8096e006b8c7ec91a5a8b1c395cb825a5aa7ba22c3bddbe4ccb7a822a239bb7b48d41db6b5eac534a7b955d64fca936d359eb6ecd3de75326d6c8390a9d9907200" + }, + { + "msg": "70eaa7e052ab72438cbd1d214512d6f3f7f61670ff01db555fb7abe2811aaa3b", + "public": "0221f5c9e5da0357b8f97cbd56fb5f3ef973d76f145fc8d9eff78233df88200dd0", + "signature": "3fd1029a3c2c2fe299c4f6546870d03f76745a9d4af56e8bc6ba0ffe405702795a95dd0cde69801deefc721f4069ed106b1b5967768590d825b22c97f510550b01" + }, + { + "msg": "29f97816f151119fdd90f9120eefecd34d2388ec8ba781861072720c52a0d17e", + "public": "03f552f0c96387cdbd390123aba84ab06ed01a327b983b8d2eff5a193bbc95da71", + "signature": "65674a756f23ae12282e5274cbfa57214c9157ef9552095e7794f09dbe377ccb0854efa0c0ab4e9f8a6196d349927f06119dfde257099c5082568b9fb76c1bf401" + }, + { + "msg": "dd1a7bbe7b0be2891e1fe87d05d275cbc95ccd6b078f11ab167ca578221112c3", + "public": "036e369722ac3a6530024e955f165963ba755672f4c252768b3d930ad99dca28a5", + "signature": "d5b2536dd7dfb43d03e056d67a52989078d6835bbcf465527bf202769f1a58071c03643803b72c95b40f1541b60bcf6a91c22b2bb53482389b23691c0019b8ea00" + }, + { + "msg": "edaaff3ac83e27c336e2e6149ef23c78c79582688c97c3665fc695af9fd81615", + "public": "02a5599c94a1458275e351bd8abc305e02d71e49fa5b076157ac3444dc9b27f40a", + "signature": "e2f3726f2799d8adb519c6917e46ddef5df9ba2314d6ac01d7d193fa099325877663f27184c7148a3e551304694b0cca8ac17427c48e8546e0b9a3a6b109943e01" + }, + { + "msg": "5d074e3a702bfdf6633415fb6b64029cc4b6869b36134dafce4e3a7beb2c03ca", + "public": "02695a27576a29680d55fb975fc85109ebf1c6ea90cca7baa92cc5fbffa41ae8ed", + "signature": "932fcfbbbd8f3393780199a8bdffc11c0aa6623ae12ba934e698a2e5a9543a926dc7d7c1a16dade3ce303ed64e5f771b8d2c3e05f61620b8971173c0ab1d09ae00" + }, + { + "msg": "c9a49945fed5087166d2dd110b1212230112be21a68442717209f6300c97e6a9", + "public": "02262ce2f806941fc69d31854954cc3e07d483cd57e67696e7a8ef1c3a547d77e7", + "signature": "6c586e57098b2b0b2ba9a83cb84771ea8a8ef2656a4df84e2363d019c0417da62c4273d89944a5b438f5f916663bf2cda854c6dfe94ae1d1cb5b868c4e847f0600" + }, + { + "msg": "cc2a09e8b0944d5cf91725d8d5a3b87c968dfa3d0c5d3f22b0f77dd110bf9890", + "public": "036b05a4b49d3cbd8ba9301c3a4e0351fc19dbcedf37de190e6e54ffac9af1c9c0", + "signature": "371c064f1801b48e4d739ef3348ef086a4524e15c1605a99478e942f747058647f15e1d49aead9d3f345424f0a07ccd4d36628127782e2fa27b3333ce97e06da01" + }, + { + "msg": "a1cfea85fda469414130156b7fb2f6efa8686bcb836d002c529ab8249842f41a", + "public": "03044976c1829ca459f5d400ddd6d6c756a12cbb9d1a00c9a2dc59a587d9c62b31", + "signature": "bc17fe41a937e9b2fcd1595f3962f4077c7a72a343f4f86fe335b8cc6a6db3ba473c32e9328ff81f08fc8c79e342f04196ef5e57fb6959de5e61137d649dea2201" + }, + { + "msg": "d2991d9cde6be3718a2401ca13b17348c992e02672ea18a5203b948b4730e902", + "public": "0285c681ecfb3a07e50ffd807eaf1e84b3c780a279705ec57bcefab7562b953e92", + "signature": "a71d46c77a339ca3a1f3292edc292473f92a7fed07dc9e58413f3cfe63cc8d7f1c73b826c2be69076b3bfdebcb6bbde07f462d7d30f710afe38a8b62a781e69501" + }, + { + "msg": "b07cc9c52fc2aee15cdd38fc5ae1cc3ae62c1fc8f104bdb06e88898e819a9db4", + "public": "0297a73d8b2c36895ecdb1989fb3d1b24eafecda6bf20e40d7a05ef6f97a11c1f8", + "signature": "4a34e31972f3a6fb8afbf32d893f9c3fb08c97e0aae2c1577bc9aa905ee7714722d3eede66eb55eb104a54650220953d94102e35480daf92d372816e31285b6d00" + }, + { + "msg": "c926456121e620bafe4ca8de0837a50f2c0f1e365b71233bcbda870f24f78e91", + "public": "034c71da194d2abf16f1422bbbeb3b4103839e352aaf6aecb591eb57e8860b2b24", + "signature": "5d13b729f521b259c73535cce9ba7d013c6ad16b36d8dceddcca46751f02e6465d805a0762d5c4dd9d9cf56c89dbb88b82bf588fd6795114bcaa5b0208b6c5db00" + }, + { + "msg": "cd6e8424d5a18e1974f09a584596e565399897edb345f8d56888225bd1f4f90b", + "public": "039f1dbfbfc119896a45a2864b451633785875d445c6a8d34fd94c7c743669b557", + "signature": "5895ed11c6f3688c8f5e7ad341af46e6b6dbe7de54618e02fc6d41c507eaedb02874b578c594427f1f826e50187ad0fb896f8348a2c2c3c7def1e8fe855d9c8c01" + }, + { + "msg": "1b099a065ed5448c599a09966d804b7451a08a6a7f66ac0608f66fc958465604", + "public": "024f6464a3caad876d300c3a733441e0c9ec104d1503afa15d8c6b3d1b96c3b411", + "signature": "73b662a2c80ca75f54ca4dab84aebcdd8dd77131bd02be947a3e936b54bd263d1e4a53baedafd3e25c0a7902b0434aeb534625801dadfefb9b84cfa32d7b705601" + }, + { + "msg": "5df9c4996c1f4db5972d75d963c08106cb1620cd51b6756734655764dfc49722", + "public": "03b6514ae0c0d7eaf2bd968573b375d751fc5aa5937cccefb9a81e47831549a7bb", + "signature": "4919015c4fef75648b24e870c9a05fdc2ea4835ffb846cca397694279b56a4e85b338a63b66cfada1cd8a9f4a43a7302c7ace1bb53c211b83e5e7f5849949d2100" + }, + { + "msg": "c2a252397d9fad25d7eaa173fdd3b185620c3601263e19b2974f75d32d77c566", + "public": "03c84f9de9480fdeaa0ec28070b825e96a31a81206e4ea3e8db4b9881b03f1f477", + "signature": "1a85ce67a516c89a35e85e32e23d2229a026fab3f2a470a2b14420d9b969302f34485feae37a5d11a4ca28946fd469515a2f742ac5c0e55ba6890689c250c78200" + }, + { + "msg": "aaba181ebf3d4e020710378d3a0525e9e90a858a6680a59777017dc4360a61d3", + "public": "0354f4be049e42e0a244ad3f0be1d406f0a53d6300f59f9a96272ab7ee27a950b9", + "signature": "1e56f54882d54cbd975c4ed963dbc717d6ae5fd0a5adc690fe85243ca69c6d6506a53a3b547497e335a20f835f54089977ba34d5360eca2eada2a818d5e7744800" + }, + { + "msg": "74e7cd4dc90a51f3e7a24036f455eac8fac3a0ac122e5ec89e724d3d2a24c6e4", + "public": "022d2d32b9e2440b8c1775d0d61208942002a3839282f5c50fafd34ab47512406d", + "signature": "5f39d0a4edd6a4153fef5d6a718494862a14283ad7c5700260ccc86273f5af7537bd3a920b86356b0db81bf11daeb4592b1e878b30cd93576396657adca8adb101" + }, + { + "msg": "e4203fed0e7eeccca4c7c0db5ab8c054c6b6562ae431dd027bb42f0bde51a8a8", + "public": "02fd3f9da44e91850f37264552966d28112b7f4ee58242d4a1dc7a694249aa36c2", + "signature": "bad8d91a1bb9fcf46c08a0441ec7de319a175a117d737a983bbd40ff159335c551ed0fdf5ca2db8727bec73b09113e319da4590105e1be1158a19bacc93e3b9400" + }, + { + "msg": "7ebb53b846dee758813ddfaa80d1b352e8a5a404b4b99f3cc71611f18d1c9d17", + "public": "024363bba43a2ba7e524a58393f00e5dc59dc1aebdc1d03b4dc3e79b13b5478ae3", + "signature": "fdb3564389ae66755530055dca9c7c00ebcec3c20ed7562e09d81692cd244ea04e76fe615d9f677e1da81178e360136e67be49802589c3d8fbf2d3572e0e663401" + }, + { + "msg": "0c5900e8335f0aa1ef3c50fad6f5d15fdf154188fe05428b1b36b0a564c3160b", + "public": "03742c266730be91b922933c2fb016292223c540999559f8ae744eb668d8cef93f", + "signature": "a577717ce057a1643e7cbd5d4262df5325475c541d44793690820a59dae775b750a610ab0a2a45e0cec3e17010ffb2e612ee1c055d15c8d2bb904ee284276a2401" + }, + { + "msg": "cfa5c2bed201258ea8bec3f63caa83349fa9707b26223584d87b383e1a6b011b", + "public": "039fd9b76dddd5fce9ab07aaabc1a44d75f488e3df3328125fefb387230fcdca5c", + "signature": "948d6e20f634774e77d1fe5ccdcecfd46fc3290084f9528d1ed3c9155d9f9155274d2d93a17f4b3d6324eb0d97d36c9cd461182a3ac61147f3381203c72ee04700" + }, + { + "msg": "9ad75cfe7e400ade47f8c608bb8f87b1fd3adf0fcb2bf93b9bc71d3e2f4c2ebc", + "public": "02a437757a6cd3f86aedf0eebde30247ada9f49c2006a101af919b37c871c2ca79", + "signature": "32ba9cf2b53cc809cd9d07a5b34660afb95550d9c1bcb8b08e0023fbe26fb573787458b92f2022e4e403a5c0d1ea0061cf595efa0666ffe4c1c2e446d09c592d00" + }, + { + "msg": "79b20edf913b547f23f91ea6365bb1cba64757e6586a44efeac139f863a4e882", + "public": "021386b7c193235bab9c6d3abddc7a303d32f18a77e8c21ff54056b2ec035d8ae3", + "signature": "db22ca7125252deaf6505747bfe8f029699e978b83c0c4576f59a119ae681318407f3abb83b4955db429d8cce8fe7785c42507da0338fa98f07e2cc64c43713001" + }, + { + "msg": "7fe4e9277997144b95cf3170c263fb2f995dde699f0f8267339e007cd38dd35b", + "public": "02dcc22860f9faf3a05ef478f9f79aab337cd2bdeb9ca726b5554960b7a587f781", + "signature": "c88a4da89205c15e56fc1929ffa4bd415756d013e6d20f22fdebf61fa8a3e6283b89311964bcf1777b2ec6f64caf3afe34b718ed0a375e7bdf3b58496505aee401" + }, + { + "msg": "1b6da9500d3387cb95b0bfa612414e7ea7cea45dc4664f4965b94636a1e5133f", + "public": "030b06ad28c89798cb8910f4b5938ad4a14b0b1576e8c7381c8bbc09f45d5dfea5", + "signature": "13f72cf3428cbe4971b84718079948f3862042a04229809a3f1bad0ffbf542652db40fe879f354d3412c76631a7fce4d8c8c0f73b5f5fad4e0e7c6af33a03cd500" + }, + { + "msg": "a427043ccb244f6a5d618a81fbc797cf380918ea2b50211a20a8b13804cc57b6", + "public": "0212961fd2d031323f6f100bd45777b393666676b6b8763f99b52d6aef4563a5f5", + "signature": "b9e9a08c2ca913f091c0257edacb524c17fe99fb8620d56aad2526db5436615a0c25b6e6f151f96e68d4a3a896415caef08a686b6ae6b9983d60627a872f1de500" + }, + { + "msg": "aa3fd54da89092eb3661e293b62d0d21533d5494b4ba190132033130f8de6927", + "public": "0271b5826c54df70df8accea20767c67a1e2cbf3b387e1b3f9d76071bbe1bf8824", + "signature": "cf4265f4dd8c2a59c8a0d98daece29b90757e23272e297c81c633e9f22465b186cdb062352411ba4d4736533605a7125b55f1258f8d8fa14516c0d4a7dff8fa601" + }, + { + "msg": "f6f902a83438757b667cd2caff21f87ca916e5f7068dd7ecef781992f31d2980", + "public": "03831409e8b3c894bf4a8a3cf5461642751a77fda4aa34836acf72a01505debd87", + "signature": "b32b0e2fa5c1312ce6257bfe30e5a7d1ae7b2bfe28da65116abf51aed4d66d7d323ff1dce58d438f6f3dbe164178ab2f44f08e2c967e54f186d9d5662465e57101" + }, + { + "msg": "d3fd3c44ee37d86a45d95a3ab85f85334432f2dc207d819692a15b6ca0103899", + "public": "03ec2cc840c200b0282e23c1bcb3e34c319d56b29593792e0d595ef370fe266240", + "signature": "5c9efc97ba2fac911b3e36a336a847ed53bbeda85eb5f44b84609aef16a1bca72593465905e377e7a0183ecb35a6258aac79514716e888c93fcd3868ba1fe64b01" + }, + { + "msg": "afeb30e2f4ebc983903610df2e874ce013e033ba77d02b6c93e2d3ca4236b572", + "public": "023355ee3cf4d8cc677d7aa47afa2c4a16b10f7f9925b8af4c847014769115abd6", + "signature": "0148a3eb23c4374f4bbaf5ac747c34f8c8d7e2050e4d8d8175ea3ec4160912a25e64303645c8c7739512ab2b7ddc63c833d1bcb8ea0e958c2e5de633948d1bb600" + }, + { + "msg": "08ef80a7e1877311aa0d6165f4a0d6add54307e92b2bf8116c17ee36747e70b0", + "public": "03eb0e806bdaf027c57e2f0ed9537daef0a2320e38ba9c6f401cbdc69ce477e8cc", + "signature": "a7f69995eef81cfe65277458246a9bd4f35215f5096f67b5fb0e4f47742c98bd053a4d3d9b54c6beb11941b77a04de740a9f66d1b3f6c1c0545a47ee0b0e83d500" + }, + { + "msg": "c31de17e163cb83444a631525f5e96b4bcac4e8ed62884b8c0c5e916bf5149cc", + "public": "02736f27236545a60767f86440436106ff8f4f8ca13c0483623679bdae813f08f4", + "signature": "28955e49d5259d388e817335aa49dd86b91d4ce948b44bbea9e407dfa984ebd0023f4306835d7b08f7fa0473b264c83b8c82192a05ace5758c1fd56bb744b5f800" + }, + { + "msg": "e413c3499458047a321e25198e88a9e92938232f4c21abe8b810b54ea2ccfeac", + "public": "02ea60309fa72ba7040a56ebb02cc20ff88a7b586daca1b1af5d1d9f0cb5278c58", + "signature": "bfedcdf4639f26244730a6022c464c7d57ffe08b1b42539c3d06aeea6358cbde5ab2a9750390732c441194d2a81e56d5e1bfd0e7ea077d749f576019186c2d2f00" + }, + { + "msg": "d970fd9c7142128ffb1bab8db0d053a1a382c29b11c9554d8e9863b54a82f310", + "public": "030f7b15dd1b9e0c1c698821f9672fdb369485f94257b4ec4ebe982f7fd49b0441", + "signature": "54ef9d78d1fd3a1df95bee9fb5b8606c08be12db26d8d119388bf29a659d20362b968c5e4622871036d6446e06929ddede65d26181a3994e455e4242fec7f90000" + }, + { + "msg": "8c608f2b20474c24ab9506ad40d193d7596b79ed37fe71a94a15e2313f808a45", + "public": "03332c8a79698042e961a8d896a092186eb1da519d7816fa707d0ec215b7d628f5", + "signature": "af46e873dbaafcd798dd945d002e44b325d1f62fcb1e43709c86bb06415492154ba275cad180422552d718e6efb0b7cdc70294332f2168cf6a3a4439e17983ee00" + }, + { + "msg": "6541cb0acf1d394a2c4de1d7651070003f56680d42f22c3ac9372862b4858637", + "public": "02773269d3c3160bc36c120508969d815d59648a92b11c76522cf4b391d70cb10e", + "signature": "70f57b1f35b8f9c3059f4892c774badfeeef7de98b622f1a90541fe85b273f363d2e9149aeb45ef4c6c561b27dc4a582284c9adbd497ed67dcab7731522c9b4d00" + }, + { + "msg": "5157b4b9d2ea016981c24657332b0c6ab58bdd86bf4715a4ee4015a4c84be4a7", + "public": "02dcdd7b9c3ab33a2f38f0b072edf38a4d9d8c329b590f04bc6fee0e69317a0bdf", + "signature": "090fc815decb1703feceb0e4770b7dae4b966eb712c20409cd9ce0dfe78b9cb0015ece2b85aef0a5f7170016533ce29c525e6a9b62a4374f47e2e81d2000f41d01" + }, + { + "msg": "3e38d8ef65935ff97c6f4bb8e1e26a2c83dfe54819f6a36013b6b2b390a28a37", + "public": "037af3e2f3315d6c2e19e2c3a78ecbce27d0d5bf651367421ee3e168ae728de75e", + "signature": "b14b95ed8833f3ef04d71c9e5bbef0e03d1b464015322ed9828528f014fa385931f23a017f6ecd201fe54b8f5b8dc2e907e226047296bca9207b25ff71a1225e01" + }, + { + "msg": "262b95b3a45338fbf224aa641b28735bd714deda1637d2bad3cebf4b8f76afb1", + "public": "039f0236f9bcdbf1f6ff4d2f683efb0370c00a216e86fb1b27bebae2bf52449ef6", + "signature": "bb6a6c92c8aecd3ed8ec2936b78558f452ee2366f7152ba601f1e39fd8d4b12178c2b52e4aa7f7d98a8f7674e8564a8ca81a23a3320bd7f1b9f72be6f50b7d1300" + }, + { + "msg": "d4e015addf1574b6afc6a42a37632fcab008e993506ef3313aba6bb7a976e056", + "public": "0204a85da62fecc3f902d84f920f6a1a71736df3c48d56e17ec43c71877a59cff8", + "signature": "4bf07f5c7e7228ca3a4a179e06590cf4eae89980d76a2d1b4f8859a870ca533b341df4b24fb991d993b6f26d5d62fe9071453690f6e25be6482ae1c8dc4c5c5200" + }, + { + "msg": "c5e55894dcfae05f0a0caf0392a0307833cae1303936522fc60e80ff5a3bbbb9", + "public": "02185a34bba433a3691f27daf47b20a721c1c5c39fbf648417cbf4c4e8f0b1e38f", + "signature": "705d5d3570765b181434539cdc6d200c933b2f3fa786d879962f45ea71a8c1864e1d085e5b8c67e83253f064b2876928484e398922d2015ae78098d76af0698e00" + }, + { + "msg": "b63d5ab659ea147e1bacde7a4595a8416822f9a32afda8305d7a0de28c80ae5a", + "public": "03bd1f84e3e9ef3db1447fd5f3d5dbf5a13049790bc94750e4c844e2cf618953b7", + "signature": "de0d6663be255ecd5ed52b2e21071833ba8455abe3667c3720aba72a0548d1c32a4bc7422758fd53492ccca1eb5acc0091f13af3f77ab9e29232350f4aa71a3700" + }, + { + "msg": "dba2999a5578177f5b93468f404be90447b1633bd7626caaa1975e5cdb741e26", + "public": "0295934c21feb5c14635b3d0231b3def9a055eb459f4157103d0aca731ae79deca", + "signature": "5172095511225a04c7f27b29777f3908d3d9703481d56b703448776dc7bbae4f636ec4a62a84f3e8ee0b4e9021e71013b2827598fb8055003ac10f1bbb8a09c001" + }, + { + "msg": "b767d4e5e8cb0b4a8db8d0730e89a8daafcb370316cea5b2ee201d11fde1bc92", + "public": "02812f641e155989a0479747194c849e6568b7ee22ff2cb3b5ce2ce6fb59832d92", + "signature": "8ab2c4c03eb2b56ec47519022e72128b12546c32794050aa57137d4507ea0d701cdab1b778a520304e51a2012a84d71a51adecf078c1f24f6bc13f36f31277fa01" + } +] diff --git a/rust/tw_keypair/tests/private_key_ffi_tests.rs b/rust/tw_keypair/tests/private_key_ffi_tests.rs new file mode 100644 index 00000000000..7092f7b7407 --- /dev/null +++ b/rust/tw_keypair/tests/private_key_ffi_tests.rs @@ -0,0 +1,210 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_encoding::hex; +use tw_hash::sha2::sha256; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_keypair::ffi::privkey::{ + tw_private_key_create_with_data, tw_private_key_get_public_key_by_type, + tw_private_key_is_valid, tw_private_key_sign, +}; +use tw_keypair::ffi::pubkey::{tw_public_key_data, tw_public_key_delete}; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; +use tw_keypair::tw::{Curve, PublicKeyType}; +use tw_memory::ffi::c_byte_array::CByteArray; + +fn test_sign(curve: Curve, secret: &str, msg: &str, expected_sign: &str) { + let tw_privkey = TWPrivateKeyHelper::with_hex(secret); + let msg = hex::decode(msg).unwrap(); + let msg_raw = CByteArray::from(msg); + let actual = unsafe { + tw_private_key_sign( + tw_privkey.ptr(), + msg_raw.data(), + msg_raw.size(), + curve as u32, + ) + .into_vec() + }; + let expected = hex::decode(expected_sign).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn test_tw_private_key_create() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a", + ); + assert!(!tw_privkey.is_null()); + + // Invalid hex. + let tw_privkey = TWPrivateKeyHelper::with_bytes(*b"123"); + assert!(tw_privkey.is_null()); + + // Zero private key. + let tw_privkey = TWPrivateKeyHelper::with_hex( + "0000000000000000000000000000000000000000000000000000000000000000", + ); + assert!(tw_privkey.is_null()); +} + +#[test] +fn test_tw_private_key_delete_null() { + unsafe { tw_private_key_create_with_data(std::ptr::null_mut(), 0) }; +} + +#[test] +fn test_tw_private_key_sign_secp256k1() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let msg = "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; + let sign = "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"; + test_sign(Curve::Secp256k1, secret, msg, sign); +} + +#[test] +fn test_tw_private_key_sign_ed25519() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let msg = hex::encode(sha256(b"Hello"), false); + let sign = "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"; + test_sign(Curve::Ed25519, secret, &msg, sign); +} + +#[test] +fn test_tw_private_key_sign_ed25519_blake2b() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let msg = hex::encode(sha256(b"Hello"), false); + let sign = "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"; + test_sign(Curve::Ed25519Blake2bNano, secret, &msg, sign); +} + +#[test] +fn test_tw_private_key_sign_nist256p1() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let msg = hex::encode(keccak256(b"hello"), false); + let sign = "8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401"; + test_sign(Curve::Nist256p1, secret, &msg, sign); +} + +#[test] +fn test_tw_private_key_sign_curve25519_waves() { + let secret = "c45d1ba60a5d929d228d1b69a8f91bd256262498e81d32b6411d6dac9a60ed3b"; + let msg = "e6167d"; + let sign = "05db2483b8107187448e7f3d5581e48380a83338d53fe70c59e88b281b995216085518e5d331a2b698f2d0d387529dd94437157df88cb14be8d1925afbb6e80d"; + test_sign(Curve::Curve25519Waves, secret, msg, sign); +} + +#[test] +fn test_tw_private_key_sign_ed25519_extended_cardano() { + let secret = "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa\ + e0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"; + let msg = hex::encode(sha256(b"Hello"), false); + let sign = "0a8c6e36f9afa324e9065d185cf5df2815c6b997b2e1627e8612ddba8097dcfc325ad6b2317cda2159407463cdd2706af97f299873e940f43f986951f8809108"; + test_sign(Curve::Ed25519ExtendedCardano, secret, &msg, sign); +} + +#[test] +fn test_tw_private_key_sign_starkex() { + let secret = "0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"; + let msg = "06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"; + let sign = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; + test_sign(Curve::Starkex, secret, msg, sign); +} + +#[test] +fn test_tw_private_key_sign_invalid_hash() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let hash = hex::decode("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080").unwrap(); + let hash_raw = CByteArray::from(hash); + let actual = unsafe { + tw_private_key_sign( + tw_privkey.ptr(), + hash_raw.data(), + hash_raw.size(), + Curve::Secp256k1 as u32, + ) + .into_vec() + }; + assert!(actual.is_empty()); +} + +#[test] +fn test_tw_private_key_sign_null_hash() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let actual = unsafe { + tw_private_key_sign( + tw_privkey.ptr(), + std::ptr::null(), + 0, + Curve::Secp256k1 as u32, + ) + .into_vec() + }; + assert!(actual.is_empty()); +} + +#[test] +fn test_tw_private_key_get_public_key_by_type() { + #[track_caller] + fn test_get_public_key_data_hex(tw_privkey: &TWPrivateKeyHelper, ty: PublicKeyType) -> String { + let tw_pubkey = + unsafe { tw_private_key_get_public_key_by_type(tw_privkey.ptr(), ty as u32) }; + assert!(!tw_pubkey.is_null()); + + let actual = unsafe { tw_public_key_data(tw_pubkey).into_vec() }; + unsafe { tw_public_key_delete(tw_pubkey) }; + hex::encode(actual, false) + } + + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + assert!(!tw_privkey.is_null()); + + // secp256k1 compressed + let actual = test_get_public_key_data_hex(&tw_privkey, PublicKeyType::Secp256k1); + assert_eq!( + actual, + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" + ); + + // secp256k1 uncompressed + let actual = test_get_public_key_data_hex(&tw_privkey, PublicKeyType::Secp256k1Extended); + assert_eq!(actual, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); +} + +#[test] +fn test_tw_private_key_is_valid() { + fn is_valid(privkey_bytes: Vec) -> bool { + let privkey_raw = CByteArray::from(privkey_bytes); + unsafe { + tw_private_key_is_valid( + privkey_raw.data(), + privkey_raw.size(), + Curve::Secp256k1 as u32, + ) + } + } + + // Non-zero private key. + let privkey_bytes = + H256::from("0000000000000000000000000000000000000000000000000000000000000001"); + assert!(is_valid(privkey_bytes.into_vec())); + + // Cardano private key. + let privkey_bytes = + hex::decode("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e").unwrap(); + assert!(is_valid(privkey_bytes)); + + // Zero private key. + let privkey_bytes = + H256::from("0000000000000000000000000000000000000000000000000000000000000000"); + assert!(!is_valid(privkey_bytes.into_vec())); +} diff --git a/rust/tw_keypair/tests/public_key_ffi_tests.rs b/rust/tw_keypair/tests/public_key_ffi_tests.rs new file mode 100644 index 00000000000..9922b0fd8a3 --- /dev/null +++ b/rust/tw_keypair/tests/public_key_ffi_tests.rs @@ -0,0 +1,124 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_encoding::hex; +use tw_hash::sha2::sha256; +use tw_hash::sha3::keccak256; +use tw_keypair::ffi::pubkey::{tw_public_key_delete, tw_public_key_verify}; +use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_keypair::tw::PublicKeyType; +use tw_memory::ffi::c_byte_array::CByteArray; + +fn test_verify(ty: PublicKeyType, public: &str, msg: &str, sign: &str) { + let tw_public = TWPublicKeyHelper::with_hex(public, ty); + assert!(!tw_public.is_null()); + + let signature_bytes = hex::decode(sign).unwrap(); + let signature_raw = CByteArray::from(signature_bytes); + let msg = hex::decode(msg).unwrap(); + let msg_raw = CByteArray::from(msg); + + let valid = unsafe { + tw_public_key_verify( + tw_public.ptr(), + signature_raw.data(), + signature_raw.size(), + msg_raw.data(), + msg_raw.size(), + ) + }; + assert!(valid); +} + +#[test] +fn test_tw_public_key_create_by_type() { + let tw_public = TWPublicKeyHelper::with_hex( + "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992", + PublicKeyType::Secp256k1, + ); + assert!(!tw_public.is_null()); + + // Compressed pubkey with '03' prefix. + let tw_public = TWPublicKeyHelper::with_hex( + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", + PublicKeyType::Secp256k1, + ); + assert!(!tw_public.is_null()); + + let tw_public = TWPublicKeyHelper::with_hex( + "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", + PublicKeyType::Secp256k1Extended, + ); + assert!(!tw_public.is_null()); + + // Pass an extended pubkey, but Secp256k1 type. + let tw_public = TWPublicKeyHelper::with_hex( + "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", + PublicKeyType::Secp256k1, + ); + assert!(tw_public.is_null()); + + // Pass a compressed pubkey, but Secp256k1Extended type. + let tw_public = TWPublicKeyHelper::with_hex( + "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992", + PublicKeyType::Secp256k1Extended, + ); + assert!(tw_public.is_null()); +} + +#[test] +fn test_tw_public_key_delete_null() { + unsafe { tw_public_key_delete(std::ptr::null_mut()) }; +} + +#[test] +fn test_tw_public_key_verify_secp256k1() { + let public = "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"; + let msg = hex::encode(keccak256(b"hello").as_slice(), false); + let sign = "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"; + test_verify(PublicKeyType::Secp256k1, public, &msg, sign); +} + +#[test] +fn test_tw_public_key_verify_nist256p1() { + let public = "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"; + let msg = hex::encode(keccak256(b"hello").as_slice(), false); + let sign = "8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401"; + test_verify(PublicKeyType::Nist256p1, public, &msg, sign); +} + +#[test] +fn test_tw_public_key_verify_ed25519() { + let public = "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"; + let msg = hex::encode(sha256(b"Hello").as_slice(), false); + let sign = "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"; + test_verify(PublicKeyType::Ed25519, public, &msg, sign); +} + +#[test] +fn test_tw_public_key_verify_ed25519_blake2b() { + let public = "b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"; + let msg = hex::encode(sha256(b"Hello").as_slice(), false); + let sign = "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"; + test_verify(PublicKeyType::Ed25519Blake2b, public, &msg, sign); +} + +#[test] +fn test_tw_public_key_verify_curve25519_waves() { + let public = "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"; + let msg = "0402559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d00000000016372e852120000000005f5e1000000000005f5e10001570acc4110b78a6d38b34d879b5bba38806202ecf1732f8542000766616c6166656c"; + let sign = "af7989256f496e103ce95096b3f52196dd9132e044905fe486da3b829b5e403bcba95ab7e650a4a33948c2d05cfca2dce4d4df747e26402974490fb4c49fbe8f"; + test_verify(PublicKeyType::Curve25519Waves, public, msg, sign); +} + +#[test] +fn test_tw_public_key_verify_ed25519_extended_cardano() { + let public = "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4\ + 06465638ee0e1ca1a9f34d940a0c8c48bbaaa0db124107e4c1b12b872a67511fed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; + let msg = hex::encode(keccak256(b"hello").as_slice(), false); + let sign = "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08"; + test_verify(PublicKeyType::Ed25519ExtendedCardano, public, &msg, sign); +} diff --git a/rust/tw_keypair/tests/secp256k1_sign.json b/rust/tw_keypair/tests/secp256k1_sign.json new file mode 100644 index 00000000000..42c29431309 --- /dev/null +++ b/rust/tw_keypair/tests/secp256k1_sign.json @@ -0,0 +1,502 @@ +[ + { + "hash": "e47a0e9d0444a58e4aa423b92734280d9c754dbf05e488f69c496105236577ea", + "secret": "cc446e8442f76121d3fc366e0d0ab30920ad4d1f02010f2d715622ab261962a4", + "signature": "bc70bd3d2e8ffdb2dc6ca44e19696e505b3c4890d51963f5eb387b1d21b70d0605f22f354e81e9821c1ce5f6e26b88b3207b030ad455cd27079bea14b3fc7d8201" + }, + { + "hash": "8b2f77ece11bab3cb3cd62c24a655af6399d87d3fd084e50c4d8aca31fe5e6c1", + "secret": "7057b3fa5dd6d46ffa291530630a14c79973efa3f3ae9b7393bfb40caf90887a", + "signature": "5305bc280335e1ac1de5814e7079810ad5f1ab6e26e9ca4aa1ff91454889ac495176bec4dc64967e90215a7e5b13adc2fee0c6914f27093ab3c27250d861101f01" + }, + { + "hash": "1cbed9daf28b8baa54c91578cb81a7fc0149287ad78a392538a6ea34d382908f", + "secret": "36a45e08fae0b1bb715e2a187715343727359bee1a475f53f5f107d9b911f554", + "signature": "672a72b22ccb35129defdb671c2f3c11070e0c4fa912edfe2e30130640c3febe5ae9cacd0b44533a4492b3d56c276399f727fb4bd9c0f48cc91bb7cf464558f900" + }, + { + "hash": "0a9f9273696da5bbbc45c169defaa5b67e1b907b0554400f28ff1134f2399f8b", + "secret": "b304f3a5467454e71141afb281565b1cbd45b56b719e77605b8cd9c28217eaf6", + "signature": "e62704eb552e0c188e15e1d67e2f4e707d3562ca72edd31a11967109923ed07c06f30297cf701e7a05e0873654274ce79aeb7de6355bf6577ca8d42f280c65d401" + }, + { + "hash": "1e02ef222b0dad8126c3e67fb9e331953b2b7a838396eba93d7cb300dce250bd", + "secret": "d287cbca0ba0e5e595137c138e74cbe623173aab9b5d396307bdc396adab1ef8", + "signature": "34c7da02b40ad41c887581c4e0978d50fb9eabf9a7bb22d7cf3809de33f3ec737561f9965a315566fe0216b6b9a629af373c1ab897ab16bff0320550cddd3e1300" + }, + { + "hash": "7016b6f5e3527de8353c8b7406683dd27e8afcfd7a132269d1bb1d3b6c2abece", + "secret": "20be2b599db8e62388a88e04eac527bf5b66924b1fc5ddee41bc76a2870115a9", + "signature": "2b2260a994488642b9c5c40630563b0f0adcbaccfeffe98e8bf4aeeac5c9722d15878d4df619bac21ce9bbecabcc185522bc4adbee5cdaa31e53a4e4c0a591ee00" + }, + { + "hash": "ea2d778a76d19bfa4fef744485582291587e7c117c092055e0ec250b02a1dd82", + "secret": "2a8f9d590eee3785a12460029d16c7d324a1102e30af5183d77c70b3883354d9", + "signature": "9fdcd89858e8c376ec68c9099587370a6418beea052ea6562fe0ee7ee0532873620d0e3c6ab90ea0c0f675f11311e2ba509cd01d45ff4c775fc7197a85f35b2501" + }, + { + "hash": "bf8a405154b95076dd08e0eae0e1cbde8addc8c03ee9685f8471b1d59b97c10e", + "secret": "50ef8cff6983498f713823b7c39b51ef75066badfca1ed6af1951ea5f3b0021a", + "signature": "11d811bac7a9a37dd84b1a5e5780775797c35f9b6f217d2d36bf2f28879441f60e304299405918a8e6ef9e7775557a2af30548e6af32b2561392d472bcba8a0e01" + }, + { + "hash": "14a880275ab266a5ccad44c0a7330092beb2b6e355bb0800841abc7a22fec160", + "secret": "acfd831f61dac04c52edca91e62400764b58eea62876bd7491faa01be9955d18", + "signature": "dc9e5af5d74b7c09e0c2f8752296f5abe0103d7d77e48e769a3b75721dc8c41e109edb98d19ee4d635f1c56123f87ef4652d15fc9e1785c3955f286a5f599f0301" + }, + { + "hash": "a2c9b0b7cdcf47e43bb1a0d55f29cc7bded2c20e3c278bb609315e771265b8d3", + "secret": "900a33439cc91e06f10ed016a9b77fb4a2bcb9c0b5f5d64e27f23b224326cb65", + "signature": "5acf06a23a4d7aa0efffb6d17f81aa3c95c2c4977cf44d72a962c98342e1bbb808f43a2a654dcbf64608537744c61ed2e910b07375df33867ea72d6e9f136e4800" + }, + { + "hash": "6e00f7ac0f2ce58900b37a562313b526fcf35f0f2fe91b8b813876271a394231", + "secret": "67fb6806a97821affb20e960ba5dd57e9fe565cb419f92df85f03fcb6e353206", + "signature": "29ed122ff7f02cfdede11d3a5f817027f003bb94cde9b6a19361f3e4c49a150174614ed76ff575b909c835e7053776a35d593bd064373d8e2d682d1d1888cb0b00" + }, + { + "hash": "63bcc7f35ec60b4660a4af4ac568ac7023c72b7b94cb69ea561bd0a51b533208", + "secret": "d5567b440186aadc79fb41bb9a5595a6d3e9ffd1b31de5f319a056c55c1a5180", + "signature": "0f8bf2b4f1c32829ea7b8ecdbc4b1d920c5ead58589f895445aa9db05a726096636b873e4f33327e19db035ef1037f2d7a46b325d72a961c34fe5bf49ef70d8401" + }, + { + "hash": "b2bd1ed10c343ab83ac0183f3d03f87c4513e22337b07f31a69acd57614454b1", + "secret": "c6703284f87452cdd53e5224b4ff6afb8547f89f42e105c28d7aac35ddc0cfe3", + "signature": "5fb35b052aa058c673249c90161e9ff8690886ce3e7fbe158260fe3a0e0bbfed7171eaddc33251a4243fef892985bcfe05f94ebde202ae899284f7d72741001d01" + }, + { + "hash": "76c0016f9efcfb0f3114e30f7e69497b6be26c9b17068f195ec085a981a021a7", + "secret": "be44fd4ec29b541947cd7b8e697dba1ac42fe1bfe879a726e8a1bce894f81251", + "signature": "af56b85d029a78b04aa720a4d7113d457f67d0de422288b817f4ef2e389933f227a29153c399a9a417d9506750c51fcc20b31b802e54acba141fe33fce2d165f00" + }, + { + "hash": "a05f60c79781ee69ab3d656e2408c88fdfc96659ad16bca61091c435b3d7c33c", + "secret": "d7f53418f26ace9f24aa5fadb327f65fdd622f4b3bfd4638b713b50c362cc742", + "signature": "7dbbdb81fcf9cc8b963aa5e3d9cbd3863f99fbd048e81341ea006bbb6a7c4e5221bbd317baa74557129c35b2cd1443fec4dde78a2b5064fbc57e53c9d5b7d9d400" + }, + { + "hash": "3fe350ed89da6946d9291dfdcde0a55febbdab3eebd9010e2804a3fd1ba9d647", + "secret": "a88c8001d825d7c0019ba16a0ce7c89941d4bffdbff4cba2a348fedf4f992f02", + "signature": "abd4cc167b48215c1e82a7dc25c8cfda5962ef8e4c3d71f16877fa1d0fedfe240039664a8442f3cb636a87564e700c92ddc06f33579e3277ec5edf8b2118b11000" + }, + { + "hash": "ed9d574fd453b5fac1b0a93a80573d7df064b37e166b0c3e4a10c016ac6ecee0", + "secret": "9f75127c2d635a5fd27ef3b613ab5011db8b3f5e37537629cba43e37aa4d4489", + "signature": "7aaec22a07280b4425410d154f43223731d2c606a11282fffe6aeee84d8184ae7c0bf88fe4ca6c628c9488f67e45c3a4ad0c0edb4d50bfec3c0656a65b44938e01" + }, + { + "hash": "2624913cb516d997d6d042e5d9d5b30a4131ba3c31f82a34d35018350e9ca68f", + "secret": "9d802bc343231cbb7004deedfc056372e6fa4b02b48ac56285941ed33761bcd4", + "signature": "6b02fdf137298bc5b6dcf9494cd6dfa7c369dbf7b3068a826abea8fad8cc65254bece3c0ce763f5fbf8b157cd9cbc295a63ec2c0d2bc24f5d9d0e024b01fe7a601" + }, + { + "hash": "44b4a8ae135675846b8fa732eb2622f63f95fce5b2b10730d20283ee7a6aed63", + "secret": "bf9e6687a24727d2350a5da6211a43fb3d44a27ba81e1ad9c5d1b91526752963", + "signature": "4fa117306368df1170d7fcca01e80353f77f52db807619ad824e5c4898a0663d72af1dbead5f3930f377b92cb9b6a502fba73eae09baceff500a0a58b2325d4301" + }, + { + "hash": "6f7878f3ad75aa55ec6c7599ad1400101b656fc73535c9e685ca602f8cd20a15", + "secret": "1532a5657dda80bda879f601d8a021607d381714256503ab616d2326b9941eb3", + "signature": "c46940f33634f37110d801358db10471b89be637fc082a0ce7da2fb6c8012a9622df557358ef2a766ad81381fa02182fdcd259e263452ffae50690bfc473920401" + }, + { + "hash": "3c014d188be81a9c617625ae2d9bf67108b260858fc4a01e9b835630b1f0496d", + "secret": "2f5d0ecc962c457e148e2858a56faaf873ff86484f50ba73721edede01da6c37", + "signature": "1adffa5c92d00be99358ac30eaf77608f6c335594f7d2cb698c84675b7e5ab5b686a35dda6163fe469465a6e97e949d584906e6cf19f7786cc45269e38301bbf00" + }, + { + "hash": "c294789679f2e887db22b0c7cb240b911468e3a2ab028878bec2d8357ae61900", + "secret": "10bc10b8a45e77c332dfab667e413f18ea3dca01af9a3d0f687d4158f2616e54", + "signature": "73cc844db97306c77cde84719815feb9a0f6758332956ee1fee809b69c4928845920e6c820d38445ad770a462444f23bda87f8b1c7a596617ed66f9c08975a4101" + }, + { + "hash": "afdac9c303e05371802bd0054f0b01a90d5fbf0df8acd41659812eb0f0fbbf63", + "secret": "3ed0724a7874a3642c5a8956d5ee63bb1ecb683383af7cbcfbde3e6593d98e92", + "signature": "7a3bfe06618e30c936a42d9977955ec8b2ca9d5bb3ac64e05c963bef5f65a3e938f48fb000cdfe86a3c80161a272c8636d24e92fcb753735e0f209b8031489c501" + }, + { + "hash": "f9d60b3db6cbf724a573180d7cbbacbac3f64da7b4490b73c1bb7e6b45ed13b8", + "secret": "f53c65f3da3171b9ad12edac26e56dcef4db8859a933c6b2a25d34c4c89ba8ab", + "signature": "0ffed06a951920df34e47d81f9cb2e22266a4d676dc62ab52eceb809688a5bfb0c8af3d4097ee1b73379fe0dd98b84d37f8e5a3e7cec51aab9ab207a2ef80c5101" + }, + { + "hash": "c5ca565076b7e29aafe596bbb7fc62e35705dc15d6b9f4ca81a57e4049fd79fd", + "secret": "9f3953048042a019fa38eda94fd3a4551f27f7c9561b2172b026dee63a0c4733", + "signature": "c5b92f677aed396d95ccab20dc3dcdf138795faf895840f7fd487048aca2400c4616204d45c3974d917250cdb255979b7f81cb1366d6468d22ed5e4ab022180a00" + }, + { + "hash": "dba0ffe77b1e38325881bb24fe9eb2cf997e1f6a14924e2b876f5238376175b8", + "secret": "4a8d2fb9a95a5d2371da8b0eda619acfc32f6f282992033b3ff445b51669ab70", + "signature": "443be5107a6a48f383a36330eb07ec8db9193314120b5bfaf31eaa79836a19381daeced7e560aeb4c2a568ad8d2389292ff51b8a8d3d368b399b755717c5ef1e00" + }, + { + "hash": "4b18609c56d06e1797248e227f3c6bcabf117275d14afaf23980a7c12ca1bd68", + "secret": "178c69840aa469159e2c36d456a5074363e7092765177d1e363e9bafa66411ac", + "signature": "43414a346bac4c686fbc1c9dfab55ae91a4cc0c323e742d5bfaea3393f0e301c18d6be934769ec7f492cb0718e4d7fbe66a3afd5b0f0572cf49a0b72eee9e95601" + }, + { + "hash": "04b09c6a84f7a6f2d307fc568a706e69544f5391a61656a0cdbc5531dc50ab0e", + "secret": "59f1f81572e13525f4bd23ffe8cc0d8a3f1e61b8eadcb8b7f4007ccc5014773a", + "signature": "77c3de54e7f3792b1f518ec687df2cbe578e732f47266cc6e5345792e07675bc696e2441cda375f5868e8e584db548fff218a8b66f6d56daad84128be0b6a3d300" + }, + { + "hash": "8f9f8950a3c69ac371c1111a45b09f9c97b47eb298b78134b1c8f2061262f58a", + "secret": "d75c60c24f7ec9779f8e4784708e250b18c37feed155bdda66c39f43342b5edc", + "signature": "5e11734434c570813a6eec42226e4bcddf94c60829333fea8bdc89b802e912520ab53114e73c70f541079e833a483aca59a9c547f355c870d08312470ec759b500" + }, + { + "hash": "fbe8b8c87551c171dea9aa9f1398d160652ea825339adbd6f85dfa0b58a86562", + "secret": "87818f8318b7b249a23f0719fee909d8431ff9e590c86497b1bd271efc3f79c6", + "signature": "64b9be5b2aa9a5325014e11366bcb13a06710ecc0d763044802c84939b8fa4ab6e895868df160b7ce836252600f3293309b7b4cb75dd7bd0194edb4d657937c100" + }, + { + "hash": "3b3b081e02323e29f343d08848b43eb988cc76e0985d25e38e8929c7005b8158", + "secret": "7ec81368d0eb5a50a4d155558d0fc4f59e0b1469ce92cc8b607db929a6b08903", + "signature": "ed6583c58c9d3303340b2ce0b254d8cb105933c88a62ee222c60ffa14a44aa335d8a7db8cf17412c4c5383d65b5f7ca2e094baa0124da78ffa134f47acd6d68c00" + }, + { + "hash": "72cc90e1a27aef0e63afae2e6b2d6adaee0d6cb749683f6a540082b718a611f1", + "secret": "3f7cf867ba530c11c2c913e61951bc81b0326eaa088561afd70d5c48ad34162f", + "signature": "51ecc9b8ceb5c699efbb1563e28879bb21800ab95224ad65a8b8680576c247b16da9ae06f2f36dbb714beaaf473db13c2363f5c26d449b2902ccf8d98c16e2d201" + }, + { + "hash": "f9c6c5d2ae4e6546be1ce7de502ca88f2c4a187240963f967fa4a23a85991115", + "secret": "476b349bfdefb01e41779a96e8b1ebb2222be3ab19ce9fff5d128065f1b837d0", + "signature": "91b9f72f12b1616849badb127384ff8f4527490418706da39f15bca4d7bfc4b8564d82702ebcc2f70c07457d7b59240aaeed0919a95780fde6975dcb599b925b00" + }, + { + "hash": "72d98ac91ff1d66f2e1009e61ba2b44c53b38d67b137e0a6128f68b6079054d7", + "secret": "99b5b03cf2dc0c3ad8517cba4186cbb286c77ce26bb71e23ff1b531021976202", + "signature": "1895371c41d18068a95d12503e45a58a00973892acc4289424728284a5f926e06da7cbc921529deaf85d5a148f730b842468f0d51c525b979f10dac638537f2e00" + }, + { + "hash": "a5ec417f9eac2510c5443f21b50513bdc86ce6c4a66bc14359890dd5cc8a7b40", + "secret": "ce58ce540c3001a2fd28502e5ca892e7b7eb00f55c287062ec843ac7752fa6e2", + "signature": "4fe00b7b0ef1c0a9a8e057a512721bc3ca8052a06272c94010c266b22343678b31dc281f2edddebb9b64b0d174a97ed5611a7f7c12e6f4f06a151965f2f30e3e00" + }, + { + "hash": "ca74b92c4e9cf5a3cf4de9268ac60bc9866e3238966232bc6f4e4d2f69a62885", + "secret": "b96f178ca927f5f8e8eaa6c45441db5060dcd1a09f272aeafda0bcf0c700cff5", + "signature": "7f12290968b60841be92af8e5d9e99ea4a7565eeb58130c2ec6b0ae4acd5466276d96e5806beeac99a25a67be8164e31eca830a5389d101a8c24e60aa06909e200" + }, + { + "hash": "73c75c6d182eae3e423af7049e5b18f8240b635d9d1b694d12342da08f6777e5", + "secret": "826a2f08be69e49d8b09b29db3b4aa5779a6744332725fa5e1814110fa4cdb96", + "signature": "a2137c4d430546cb04cca3c0299d6938deffb9e78df6c5a36296cde6584cb02a42f2d6105700f1772752a1dfcaeb113582c6999a9f44da60d50f0ea8affc321901" + }, + { + "hash": "77a033035f47a54614ab135b507e5f2989b2ffc62793ba59c662f744f3f7d1ee", + "secret": "e01a232ba20a238fa1725ae95a08d5d0ec15379f362ce1b0ef59ad209bcd06e8", + "signature": "e7945c17883124cc85134043842fb236510993134d9975c8e32eaf23e9877823255c257b86334488151dfbd65360bf534418f76d53050f9879ad04881f71394501" + }, + { + "hash": "45a2c86962e9e408a81f9f687795fdb71b3a000b62731f438d12ee40d42cb374", + "secret": "9b8c1f300a7ff483e0f6d1d72cf87b88d10fe27461399df74ed9dd319848afd3", + "signature": "1a4452b0ccf856aa29ac2b56d7db74ac5bfbb9cde6fa71599717824c9d1305a23cfab403e7f59865f33dde626e220a3d59597c050723e0af44a57d360c2a19dc01" + }, + { + "hash": "49c76133ced2492f275f0b6fb26067020b1919691c0956c9ddb0e4375bfdf4ff", + "secret": "6e6ed3ec4255c4194f479d05b3c7c2e1bed82ff1b7994d0692094dd2637c12a7", + "signature": "71d128694790c1dd91632b2866e6a6840c79b24420105e3b74913a37f799fc616a0b0d5dd4babea6bb01d11f823ea66a740ca249db6e19086565ee7279eb2db101" + }, + { + "hash": "387eb0cf09c1a00c17a6ac8ece3119b34d116733e3960fb44e50056c4d0a0aaa", + "secret": "58e1ceb39789cb4fa05d3af6f9172cf6af53fb8679bf32979b28f4ebd173d72b", + "signature": "3228a0322861193e10664780df8c557267f5208904afacb9035524e2c16c7df166ed796dfefe493defc5c1348cbf9ce727f603c65a3c5e500da8a99f88abcd7201" + }, + { + "hash": "bc8e5887ce6847ed67be2a781633b06dcdfb816038ca0739a105d0bc80a56d75", + "secret": "30b94c4c87c770bd21620791704408291b86b171e939c2e2a05ae618a6fe94b9", + "signature": "58e1821e5cc088e26980559a87da62b6fb9d5b0515f3a245fde860472dccdeb22e8d460802ce34174abe3f138f78ea7516fde8109f24a7c2efd5318e2240891001" + }, + { + "hash": "c428d4f949aa6fdab3fa5a784451745bac85fd81c2a0d9c5ac6f521786cd56df", + "secret": "af86222312d73b593e911ba7a929f3e97d567043bd0078d443206b7d0f3f931f", + "signature": "0fdafcb6f9037865092a50ce95ab9891f9b478242aa206ef4ded9f8b61b86f2f0c22d2e545c8dbe7f24a1a5137e38e325fa926c7f17c15ccf962aae8d962db0d00" + }, + { + "hash": "219f55f3012c0c7d16e0172c6ff1b21080bdf3d6e867d32196e3b79a5b4a6b7c", + "secret": "fbcbefbf81c033df501309e25f39ef3e15b22ec2061c97c3f8a5c784a7149b87", + "signature": "3d0a81e647abddc6d38fca85acadf483835471e5d8820ad51891ef19a65346ea6646b6f3d9d8556831c48e289a23b277b5169d9ccea6ad76c846b74e5ef38a6a01" + }, + { + "hash": "1b8840a329ad8bcf2614a06c3d6bfb9385cbb22a58952fe6eea67310518f983e", + "secret": "a1632ce45d4025bff643ce1a0b36a3ca7a7298804602679eb49be6bfc6553c92", + "signature": "059d90136fd57f372dd12fc1e10f2d3a3700031c4f1e4f36459923e4fd3c5d6b6d83e3914a3eef24b951c38025092303eb91e786a2ef9a23750b54faadbb00c700" + }, + { + "hash": "d17decd2a4b042738261d6e63ef012cc6867e729a3e6f8ba3e1bc79532bb463c", + "secret": "d2247583b85dd27f39ca9dc8893966364e8a12699753f6ef9928f2d9011f9cdc", + "signature": "f12be874105b61217869cf6b49239385cf1c3753b5551cfb1acd358dc297780765f0c591dcb24f142a5045ab0fca4e90bb9cbb4c08195a34483b8ad3bce9cc2d00" + }, + { + "hash": "bafe579a79d46dc918dd15b6643f312e952ca60deb5bbfe2bb361c1eea9c4a4c", + "secret": "9fab92109e06a36d1d21002b020c1f9171003fd463105fa8896970496f56c13e", + "signature": "bda204f249f0c0d2a60ea4b4e87bee941d356224160f2a769881635d5363700963cae24a2bfeb616c3f72d4be2128bea3972ad14ffded72964a66546e8383cb200" + }, + { + "hash": "eff6a8531bae03050f6a62f7652b6213e1755631191c363826ddcb185e2a43b9", + "secret": "fe756b78c5425405092aa198c7104069343e0c890998a1e699bc9f908c7cb264", + "signature": "853d656c1d293e04f7d425c13412b350582c23700d4975e302fdd761562bc9e00c0bf359d360a736089690e1e3f517fdb4db230f7e76489fc9e1eb58500f8f3e00" + }, + { + "hash": "4917e9f4dc97968151de0bf71b5b2bc6ee2544b49b368b305d503dc781ca8cdb", + "secret": "6b3a552cd49db3aff943b090dfac7e41a820d8b6319fcb66e5b75c2e89dc4a8a", + "signature": "d571d17126ccab7734c7d3b348117fb105f45e6d1b6b6898a489cf8ef99e1ab95f0fdce993b33123cfddbbeb7c6788655829b6d9ddada341540a2629342f061000" + }, + { + "hash": "738841a6a624805d5cc0670c9508c72f2bd3bd41e3a652efe536ae67da39525b", + "secret": "a5c50ca0b63ed744d10b3579fd187f2555da46350fdca74be052ef7f19ac2ac8", + "signature": "3d050cdb5a335a85907be4a660f153cce2a18f5db3e3db6671e2a76daf3d02935bbcc06971462f25409defa49250d30bdb55710cf69a5cd1a486926a7fe5ce3d01" + }, + { + "hash": "44751c72517dfe4fcf5c09b84926f096d4ef69d6c131aab4d3d2af88798fd0ea", + "secret": "67f42cbc42437eb15d699848783c4295514c9fa8b57c5f4dc9284f1c421e3a0f", + "signature": "18bc8c522efd14dff80a0c5c68db20f0e3947d2040fc7235fdf60038769a6e8c099700104432dfa3292985b5f48939b36fe83d048835fd71b36f37b034d280fb01" + }, + { + "hash": "ab3eb5d20f2b4a616e9b2a94b6bcd586bdaf485bb78e444505ea94f379e53892", + "secret": "73ed40023e40bbccc49e26497004c6e5347af9272382ac1d5a9a3f2f78f9bf23", + "signature": "b7c4af4bb53af7ec1abcdec919d344ba06f5d4623594eb380def2215d188b9c63c62b6cd1cc4eaf7967a3ff4e50364d2591e8966856c15bb4efd2fc8d75fa25c01" + }, + { + "hash": "8d49d2d75ca11e719941259415524e2a2d8858a5e1c98431b0ca4036b0af6f27", + "secret": "d8a771b23524fb94766078c3f458939ad178a05caadfcc6cf5a9410dc2ca7cd0", + "signature": "3fe0f65840cc2795197740e3776ccbabbaa1c68111fb2250979889e7cd663a9141f997a117a30ef68164a8a57d3ba82f58d7684cd69658b47f267a93ac09807701" + }, + { + "hash": "100de6c9ccec037ab289812462506507e615dd1e7ad4fb33c724af9f1eab1070", + "secret": "ed7fa7580d848a15ca06018acf288dfe7547e0e44e175d009f52a47e40e5cc17", + "signature": "c52016c525ed8d7f7b7345cb55c98ae9bce55afffb517a19c06a31b68bb7bfc77b580affb42618f30bdc7526d42e6439621311407bcaf12d3ea716bb4cb20bd101" + }, + { + "hash": "0b629bac6290cb90bf375893c324f7e8b3e1dcb76c6defbe9be1b0953a5eae38", + "secret": "ef44bf690abde5c3226dda3aa133b77a81f0082542d9bb9b439645d63fa0003b", + "signature": "3e5a814d8e201cca69cdc570697fa8dccbe48c04ebfaaadaf8343749b5e7fe200a384ebfad0f54c8cef939a549a2a91d4039889eb9034f259648c0ea203fc4e701" + }, + { + "hash": "98fa86a923ef8f905672122adf722a3c56ee0b8349fefa5b703b7d0381ccd421", + "secret": "8c9154788d4a19ad4e081cc243be741eafd48d166ac6fe110dfb3dfeeb10feae", + "signature": "803095e5097c048b12026e8d25b513ec7417bef26c3b1bf910c9773a2d37895e0dc1e184eea9ffbe7f8640e7c98d7b893512336f8125dc37f9e4b460f6f58cd000" + }, + { + "hash": "976c107451a19191481fb3f413a42ff20066cc7e0d8a3809ee614cdb1728ecb9", + "secret": "64c008571f89e53d19491e7e63e6968ae08187a9dfa6bd99c6ff5c5f79f88004", + "signature": "7ca7c754521993153d08d0f22f65bfbfe69ede0900ce759e4ba7fbd5fffdde631b864769cd882c37e559af447b0d47121f32b8697d13d3fd3c504d687b4cbc2c00" + }, + { + "hash": "d8887c6743c97980b503c9a69d0a43602db596094553661fa1df2a4a6ac18a53", + "secret": "c364018454d6ad29523a425e83ffed1d0b5d17e52fd16524e746d860a5634b92", + "signature": "a7c22af6a315bd946cb8f933a29dbdd6a80192c4147b4d208ef3a818e208edbf6058c36a51a31456b16e853eaa830d0d3cc075371b65188f07ba6220fc9c906801" + }, + { + "hash": "e3590881f4b0b72083a8d7ec269d3c11ed8daf2a9a187348b04205f1bb036548", + "secret": "e1aa8dd19ac8b8bfb872d4340241ba096a16f44417f7425bc1dd1d20d5acd35b", + "signature": "731551bcd244e4a05f7cca7aca9a93f5029bfcf8f6cfae30e8ae610c569985f634a6c871a775033480f00263fb138b5260acdd8b93df9647db6e6b3cc20a4f6301" + }, + { + "hash": "a43034b5cf06b3d905b376e178ea231208cb830aa4cc7679ec49e7720ef873a5", + "secret": "0d58f19d5eaa6c3e8659507395125ae60099cef7f1255a13c217cd61f2e6ae4d", + "signature": "f5bba2ef38972af254b97edc3b24982dd24a505ba1f339c18fe714f8fdbdb20b21f9efeb0a019ff2060d14b655687ef70b2c09e6e8596cadef183de4d6c5283200" + }, + { + "hash": "1287ac880b34700a5689fe03ee7fcf6b13d988680e69e9272455d3fb38a5e246", + "secret": "0f91d900b120b9c6960878f8985772d8cf29ffae77748aff0889183ed2bcd618", + "signature": "c5bc42118301c8ff4631e652b4d6bef8c13b521bd404d5e7af710c9ab63c248048cfedfa380ca3d966a718a36fcf3c6a1f456c8f8b47b2fd83931fcb4f02d08e01" + }, + { + "hash": "5c67aca884da5f738b1624c501bf4063540a2153d602aaff56ebac89e11dcd1c", + "secret": "81f54e72796d6a447bcff6b1022bf6cbf48b2b39160b9e2a4fa07116783c8bf9", + "signature": "0e322a34ce048d9497562c53cff8d3266e889513f489decccdbbcfce99ffd478796852b4b6fbb07e6f58651347d00b99c5a2c5633f03baeab5314aa6cb58ecba01" + }, + { + "hash": "01133a236bba209b10a710a8f70a4fce5eb043306efa3ba74ae6bfe016e0b7de", + "secret": "ce469cba4dd74fff54257d98deb1ec0f4b4d92f13c22ccf88327729fb20a91a9", + "signature": "4e893c2de72f2b9bef8adc86ac6da301a0e774feb3ec10c6995262b3a554f209732bd0ae41bfe5fc1b863cb08d74ef15fa250264649030b30c8fe44e894c171601" + }, + { + "hash": "c02ae83c3be29fb6e9ee687c4f01cea32ce8bedba3f5441a6cca28b38ae92c54", + "secret": "209eb71c8e9e5de35e67440e331d2f2f31aff08410baa2e0ca7eb29cf20fe003", + "signature": "882d92979f3fd4df2b19eecea6b4ca3104898774e83506d934736a80697a19366f4fde413d1fcf73adc00dd3938be427cea62a2aa166eab209703a3f63bd77fc00" + }, + { + "hash": "f536d9eb429ba0bc5459fa7960c7693775004ea9e57d56e3e94c3b196c49bbb4", + "secret": "359484ee9ea253e1f791d1a17f790f79f6607bbe26146aa8ac542d8612858e1a", + "signature": "7c78ceba9be9ddf18f2b5bc4414d795d882564a768a78f28702570bb04d0a65e17184e9ef667a2ad8e689ae8c03c8eb73117436035653481f297cd84a1f67a9301" + }, + { + "hash": "0a67a0dfdb30f0a6e5863a62b6b7659d0276c7b12c8a8ea87478fd921c3101c9", + "secret": "29aeae7ac718964988b35969113c14b39c81e2064794d92af09b4a1d27804267", + "signature": "63982045a58db529208243ddeab9b5835d7ce4d45cc557f9b7ecbfd1e9ceebe5567a65ad3f50a8e365146a7fc1490dc5bfd53bcb22e6256ec4c18047caaa4a1b00" + }, + { + "hash": "a8a12969ea054930d0898b91184929ac78ad0fd313ac7e809deafc7e01b9a368", + "secret": "75b84e21a454b7e454a17e054d0657d1c9ccb801e8579f5b9cebe9249e020fe6", + "signature": "383cd3371af70ee93ddd440643196ef945a2c9918be5381f6c41052f6e0279730372f224b2d6be3fcc0f9fb1e2d974923e2ff6129a1dcce73d2ce2d3442d30d001" + }, + { + "hash": "5ac6af499cb44bd57dfc9ceda8316c43e02627f14722e2ed6d0051e9539a2406", + "secret": "92292d68d69cc60544f58f51a947bf3b1b5c7f1015bc04a8a3b7ba93d74191c6", + "signature": "100380c86d1e6d552d5d940fb9670814b45ef2eb5818a69a6ede13e64aad9d7f36245f21b758aac79b46d3df520bdd96a62f052762fb371e56b5aa288c0b849e01" + }, + { + "hash": "07497143b21ed20745359b553d106c25d77304995206d3df2d0248a8ddf23089", + "secret": "650ad13e7686f91d2c92f7b8fd1e8d61fc9143774d3bc1b7826b1642d39bd6b9", + "signature": "a77d5774655680c3920641003a86a5be888c45a9da3f8f40d28f08e2925409b119cdf82f7ad7f7b954123cc73c96256373461d949e6befb51da41290f79608ff01" + }, + { + "hash": "06fd74e0d7c1d5337c6a370a8c6b7373449521af7cba1b7ef7959b23e711a054", + "secret": "d528eccaf41259f212192f4c02e2ac07f5caf759b927c7b012ea15639476fc3f", + "signature": "0bb24124b2eac1ce7f9acd56c40c8e1ebe46534ea6d2bc0f4383e11084ad2783772209e0343be1ad24e5dc2c5f57d301f1c73fd34cded83986321c4360a502cb01" + }, + { + "hash": "883771ff758909a7599473c3fbf94c24244c8cda06c1bac871a18d293fce0bbf", + "secret": "e151f9e575c9e0540b68dc277a2223539a8344c3061d6727599c28363d3b233d", + "signature": "73fd11a7b59ddbaec26cd5bea43a54dca96dd5af9592ea953217e46354d4e3cf27bda4fdae1faed15039179f7c7bcd4cbbc513aaa6a9486f69219dd7f70219e101" + }, + { + "hash": "0df5e39277381f18a0d775d5d286a764584c69dcbe32a9a5c7e507efaeb27e1f", + "secret": "00c4a04d39bb8c57322973905e3c8c4b18c87246cc848cf6a8cde7a0db70ca63", + "signature": "970b0bfeb72b54932915c8b435755af319bc2f06bc855eb2ea1b9d95651edf6b181e7972820bbc0baa82537a6ae223558e18476fbd0a01bdcd495f3955dc0c8300" + }, + { + "hash": "6df9a6205bd51fd47b464010bed49292ce0c11ffa4b71dbf1d138ad085eb0259", + "secret": "2dc5614cd6016205763887255255caefe7d45fad6533af075923ebfd72826bef", + "signature": "3d662fff2fd66147b8d26126c0c733113f2e9d1d1538513c6f1fe1e24dc6acb73668517cdf18edabf93a8673837e15613f0f5e6908e374093bc3ab067bf976cd01" + }, + { + "hash": "f3a83eba1d296ae5f2ee8187a3bafb895d1f2a15aa0aefb5f8e5369d2a37dbf4", + "secret": "41065f52afeceefe3f0c3cad6b17203c1588d467179c51be86b15a2aeba12abb", + "signature": "950ae774809cc3b55a36f1545340ac5e7a389b7f3a36f1a4f0a91b81518d748c47d9aeef9634f11296abea27336dbf36c5aeb515b497c6ceb8439a14238162b701" + }, + { + "hash": "a7f45622b70ff8d2fd31dd7440cebbf75294ce9429da31ae167473a90f6d9fd0", + "secret": "6d5ec055def0492d9fa613b0fcfaf80f7ff01316bfd8a2e0a4b8caef80df2f5a", + "signature": "448a6aeeb7163b08fdc0b9a2a2f412ccf215feee8d98c76c84b157c3a204c52a041ba06ffe90dd39732ef07342fdc4c7f03a6d895036a9208455732cff6effe200" + }, + { + "hash": "31ad8350e908b50fc1962b8d92fecf3d1ffee7d04a75545b1f4c5facd18cc6ed", + "secret": "408fd6b154cd826297934641614fd02327ec55ad86fb0440c170bfa82ab55025", + "signature": "7537f706fa6c98bb0e53d45d0531cea5ac2e3d92327fb867211a316ea15a2ea611e89049859edbbf78c0a9bd04de39bae265d6b23b4ae5df19c3529bed05be5801" + }, + { + "hash": "1466a7f0f1836ff904e250cb68ea1780bb36f1f91b26fa0dc46cbe20607ba3af", + "secret": "b393312821e73f19eb762a8b4148977236eec4e62cd49b27d2d9de7d1290a2e5", + "signature": "8d94d905a984fc65e9ff50370c30f1eb8cce5dc373c4744d6e77ce8caad957cc06e2c895f32b5f14a63c71b78e25ac3454507bc5b709aa84bf80713bdac6ba1f01" + }, + { + "hash": "b7b9eb14091e5b7c58275b94a7b18deab7e15511873e5e0446d0a7e33be29a03", + "secret": "21ad5b12aba0f73925faffa0dfdfd6a392e5fdc25e622c90be0a9e3996e875ca", + "signature": "8e56b3cbbaf6aa67590e1fbcf08998a0b5d224e90462b7fca645d4b16d056a7e20f8f4dacaa635c51b6ee3cfd71fa54e71ff25f167b35eafa1c6b91686274d3200" + }, + { + "hash": "dcc962043b56a88c57829ff8711fe930aaf4d5b536a2a77d4ad5872d56951d9c", + "secret": "59d21db2cfa096c2f12fe6470331ed422f8b1523810a8602217b1e8b30f4c5e4", + "signature": "5a5ac663960d84b3e0432c6497d5b49c6e1852c839e19169ab48fd0aeeeda28f10b6a106119ca4a73328d264c301657d443b77d01ae19737f7cde37ce128a81501" + }, + { + "hash": "ed80800e8c54311caa90f6500919e73c74e8fe63b274f77d1e0510c28ebbf794", + "secret": "0df0a1d3c6fc9c2998eca608d63a9227b345c3ca5d07b033ed270148b980c1ce", + "signature": "c59abb615cfe8e010afafbb5705b1a82017ef8804c423d17be59996476bfb14a108f77fcb6e1e591100055b36e5ac1932413f4c8eb696065b6a0f00c88ce1df101" + }, + { + "hash": "625e6831a31ab003c8fc67f5966ccb4cfca04ea958cffa62da790cb9c709097c", + "secret": "112c9ca4426ae3c15bcd2bb09c29e9ed1fb84d3e01ef9fc9cf91d8eb02a29781", + "signature": "bfa82e403eb4b5d93f3dde39170975b3ba8df615b0fd81fd7963f32c1ae6fe976a43be2e74b17d4880da0f4a437ddc6a3aed938c988f096bfc321316caebd99900" + }, + { + "hash": "297d4f7b3342a3379b6ceab7a00299cb62a57b1c1a242f26bb1029cd3330e834", + "secret": "47520e3ba5aba9384d865176422be4179bc800b95c620fb086b67adad7fbbfa3", + "signature": "0af9b607ca5c239449e6a61231e625f2b910d6e91f06b2c67fa53c3c005abb4528a982f4078b18b0b97ba9a63daef121059c8489116d9e815af65c18953d118a01" + }, + { + "hash": "77e3814bd70a03368799cc5832e56c013c56b9c0b0bd3e2b567fcb71c32ed28e", + "secret": "20e5e49627e340c1b69bdbd341df97626968bfdc338e4e2e8761a4b95029c2b0", + "signature": "8aff739ee3e77f81c2fc8420c361d26dcbfd7721879a58b902866f8d3d40b2bf33793318ffeea1fde04ae92c62d115e9c9820d26b438ee3b1c4007794bb4432800" + }, + { + "hash": "998a4f780984056368e0ed5594ae1baec3cea5a265f425d08147e6fa0574399e", + "secret": "00c1f966de7e9a697eee6285692c89405936050e5ca515e3dd215d45a9cb30db", + "signature": "31b0d5bb010ea160e52b3c4dc65330200af2ceebe84a36185caaf1d0fe3e5df22a9a71525a4e7ece31a280cb6c70256cbd397bb3b01350ab3cab0e1b9512b6c800" + }, + { + "hash": "712acfad93cc8a23b6d2025f8e9fa7de633c757d62414b7cc1c9cdaf4145f014", + "secret": "3b3ed15fb5fce6ee908c674d3473c3b53d20486b449a6ced8c63b35134c558ab", + "signature": "95cbd1cff963ff5fb12ac9b88c5542d9e80b6598a78a6a483b8821cf461108b4208f482624a168bf446df32695d0460a6a699a12dc8a7b66f6b1d56b668d106b00" + }, + { + "hash": "bf03271a3d3cac46a2f4af85ab4f28dc94b60845e8e97c5513cc26d33029c583", + "secret": "08b9e31e9593601e7cfca3418efe530b4780b525a08af0d7b0027ec8af3af148", + "signature": "6582629fbd61152183129c373519d0008384284928ebeccc367aa99aa1405cf821db10ada6fec1480de397fdedf924b7d845f318a3e01e1f40a2eaeb68f8815d01" + }, + { + "hash": "0054212f799cbc36e84771c779dc23018b98ea415311428bd6b4788eaafbb24c", + "secret": "b66c1c37869c3033dc1d5f47a50bac9b0addc121b401bcf0b5a9113080711dbe", + "signature": "73c3aa168c21b93a8f126b008bb0abbeb5130ab50cd30195d0d2bdcfaa87031940dcad39d4e7b2fc93768eb218683ed9ca34ccc7556b3fad6aee45f1d79fd6bf01" + }, + { + "hash": "f5e6eb9c9f0b22b2d86444c1ed8fea751ccb8b5de0ca90610380960198808301", + "secret": "a2af24e33b9995b4f2317c22094d7d564516d5a3b9d6613d988403d704ae2dae", + "signature": "5537b5fd15e0183565126b49799e87c5f322fdc40a4eb8bb96641961c86fe6557662a9478ec75b25617beb3f317b394d896eea6552c96a10b43ae08c2aaa56a001" + }, + { + "hash": "df2655b91f9979e7ef9934f6d42c70506e9fdc4c9a631c219d5c2cbcc5b2fbf2", + "secret": "a582bfc617c3d71b8f1d36a78d5da56a263f2cd4402a8ac3d1407fa7570d49dd", + "signature": "1d0c768064da2f755ca3027070315cee087ecee60abe9552a0c88d8485fb440c65e837a2390a68389996bb24f7aa8ec1414c8d05721531b76cefac67a0b8bffb01" + }, + { + "hash": "92a55da0a5a40ce354fcea2e1e19e423c4bc1918b27cb1da9a82bbd19739c656", + "secret": "54e3763bffdc23be470911b4a00cf1823402d60e6b3f05cb412eb743c710e6f0", + "signature": "ea15a3a4f3b394ae794f9011232b6475af40f70f4f0e56beaad1866080ddd064710d38451c93e451aa5e45e7bbba53a60d101a9ccecc8f81e1a450e8fca63e5c00" + }, + { + "hash": "dc789ac9ee186b72ed75874ddfec75404a325ce715f4acf0e7f15716873d27a5", + "secret": "eed38ac3a51d0b3b30ca0ba931a2eccc581978287af8dfe83c8a13f18063e4b1", + "signature": "b723f6c7ca1ed1cb2ac085cf084596c507471de79300071f54e00d857e9b3e502ecb9449f8a9e2d5d9983197b28dbf80249357ee5335b5c2c973de474ac2966501" + }, + { + "hash": "256bf871b80790aeeb3129684e6577094e892de32277d56335b6efa548c7760c", + "secret": "1d92a4d3e7702fc569e34b8c03a6002454d2a7b7ce1fb9e9e68fc1c22079b2a2", + "signature": "0cefc1979b394dc0ee569bdeb61e93abeeca083cddd1679a0216faaa5c2ee75136121fc441fa56eae40283ff8d9961deb8f12c2053f7e0d2a5e0e2b07398581800" + }, + { + "hash": "135dac79c2c15df5df784efc77d01f37fae8391f744e0754b3c2ced4b1aa4f35", + "secret": "cc4a32d268af6bd954854cc7a566fbbad87014735355bf37ff9815a32ad711bb", + "signature": "76be7b98542d4201fb6841fb729e6d0c864826632b6f88ff3865a26c6a134e743508e6d2cf5fc314600e0af433d9feef330bc470d9facd032fed13c47ab84e7701" + }, + { + "hash": "32dc1a4cacd866796365578fc9f34933d045181850fc595f308fce0b098179be", + "secret": "5d6353c5e80c2c8484a7fba5c8747acea369b3867e38202847d7412b9fffa3ec", + "signature": "3d4e87f439c79a4a41d2e3ae85012bb2be536e576f0d9b443e05dde31f6b764150d3890ba16d0831a161e0b46d953aa428da51ee32a0b4fdcee4a3474dbbdb5300" + }, + { + "hash": "edb61c559d972631edfc038ed12ec70c4f7364edb160622e30af1e983b26f44c", + "secret": "eae7552961000ec6c486803e006c5e9c8a426df8d712844616dfbf27845297c3", + "signature": "354b81c1993efae290a52bfbe4bf0fe8b1bbb311e75315a450ae5459fc2559a304af37c0c47e96238f487444ec022c23245814d0af97691d1578caa043fc575800" + }, + { + "hash": "3dc69be3da8b0c46ec7abb0b50d1232136ce3d2e5906d8714165ffc679145af0", + "secret": "1e10080c45b5b583072e1685f6afe98c357ed0d7dafb6c3aa63f06fbe47adcb2", + "signature": "9539625576b54478ac7bf94d107d1106a3ac9a0664080fb29fbaf3d0a8ce3882204faccb5b8b798b611e73d089326ff40e4fb497d50e563e649d5712c752c19600" + }, + { + "hash": "d60d71d332ef3ea77188f8845182b78bbae330790cdef1f08689d5466405c675", + "secret": "a6971000bf7ce5b2ab4529f60836de2a818ec2f643294d438d3973443a014051", + "signature": "4dcbedef2023d1812b3fcbc87b692be63363382a470073e4ab4116920b8e93da6f8b5671f51b16d7437df976e080a4eea99c5d07452b74f64ff0f5d7c5de648701" + }, + { + "hash": "2cc264741c237b61b59376c554424ad49ab4c92fa91fd0ed44dc27755525779e", + "secret": "b1aae3bf6f3a423291dc61dfc1a7d463a8e0d3823c243e99dd53228acf4d37f6", + "signature": "7f58d5e3289bb3e057789ed49841dc45ff5b94b31e1572d144c9544fe1bc30e21cb31301f2754e4ad6f471fd5610287245bdda11766038706b0e70bb1f46c38f00" + }, + { + "hash": "bf50b828447a139678010329af94d718f026c8275646cb6aa3b88d0536edb278", + "secret": "9d92aa2dd3d49f846e399d820642eafde6e47341ed863643225ffb68d982f8cd", + "signature": "7d1f5708d69f77b0df5996604c2eca23c9c24e4c29929d3a2d012aec01e42f8a00fd2f5f9b4dee732c93bba18a6680c771487a42055498e217ad72f2b71c824a00" + }, + { + "hash": "60eb03e7c48e7ce8cd7b9801957e97f15a4685441ab62e152294faed5f70447a", + "secret": "22cf83b25deb53cfe31c555fd0255b3272d7ab61f2aca1f19f36cc4bd82cea49", + "signature": "0aabd16e289762f9c363329e883b505f1c85c8fb0df7f383db2c7f651794ae2d6095285a7588187c78527d3b45438085bb69610c959dae58a08b1b43334f1d7b00" + } +] \ No newline at end of file diff --git a/rust/tw_keypair/tests/secp256k1_tests.rs b/rust/tw_keypair/tests/secp256k1_tests.rs new file mode 100644 index 00000000000..9774a47a2c1 --- /dev/null +++ b/rust/tw_keypair/tests/secp256k1_tests.rs @@ -0,0 +1,33 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Deserialize; +use tw_hash::{H256, H520}; +use tw_keypair::ecdsa::secp256k1::{KeyPair, VerifySignature}; +use tw_keypair::traits::{SigningKeyTrait, VerifyingKeyTrait}; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const SECP256K1_SIGN: &str = include_str!("secp256k1_sign.json"); + +#[derive(Deserialize)] +struct Secp256k1SignTest { + secret: H256, + hash: H256, + signature: H520, +} + +#[test] +fn test_secp256k1_sign_verify() { + let tests: Vec = serde_json::from_str(SECP256K1_SIGN).unwrap(); + for test in tests { + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + let actual = keypair.sign(test.hash).unwrap(); + assert_eq!(actual.to_bytes(), test.signature); + + let verify_sign = VerifySignature::from(actual); + assert!(keypair.verify(verify_sign, test.hash)); + } +} diff --git a/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs b/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs new file mode 100644 index 00000000000..40d28c2417d --- /dev/null +++ b/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs @@ -0,0 +1,38 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_encoding::hex; +use tw_hash::H512; +use tw_keypair::tw::{Curve, PrivateKey, PublicKeyType}; + +#[test] +fn test_starkex_tw_private_key() { + let privkey_bytes = + hex::decode("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe").unwrap(); + let pubkey_bytes = + hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd").unwrap(); + + let privkey = PrivateKey::new(privkey_bytes.clone()).unwrap(); + assert_eq!(privkey.key().into_vec(), privkey_bytes); + + let public = privkey + .get_public_key_by_type(PublicKeyType::Starkex) + .unwrap(); + assert_eq!(public.to_bytes(), pubkey_bytes); +} + +#[test] +fn test_starkex_tw_private_key_sign() { + let privkey_bytes = + hex::decode("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79").unwrap(); + let hash_to_sign = + hex::decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76").unwrap(); + + let privkey = PrivateKey::new(privkey_bytes).unwrap(); + let actual = privkey.sign(&hash_to_sign, Curve::Starkex).unwrap(); + let expected = H512::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); + assert_eq!(actual, expected.into_vec()); +} diff --git a/rust/tw_memory/Cargo.toml b/rust/tw_memory/Cargo.toml index faa09cfbde6..2c7b443f83f 100644 --- a/rust/tw_memory/Cargo.toml +++ b/rust/tw_memory/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] + +[features] +test-utils = [] diff --git a/rust/tw_memory/src/ffi/c_byte_array.rs b/rust/tw_memory/src/ffi/c_byte_array.rs index 65f04e2f13d..337cff99677 100644 --- a/rust/tw_memory/src/ffi/c_byte_array.rs +++ b/rust/tw_memory/src/ffi/c_byte_array.rs @@ -28,8 +28,22 @@ impl Drop for CByteArray { } impl From> for CByteArray { - fn from(data: Vec) -> Self { - CByteArray::new(data) + fn from(mut mut_vec: Vec) -> Self { + let data = mut_vec.as_mut_ptr(); + let size = mut_vec.len(); + let capacity = mut_vec.capacity(); + std::mem::forget(mut_vec); + CByteArray { + data, + size, + capacity, + } + } +} + +impl Default for CByteArray { + fn default() -> Self { + CByteArray::new() } } @@ -43,17 +57,9 @@ impl CByteArray { } } - /// Returns a `CByteArray` instance from the given `mut_vec` bytes. - pub fn new(mut mut_vec: Vec) -> CByteArray { - let data = mut_vec.as_mut_ptr(); - let size = mut_vec.len(); - let capacity = mut_vec.capacity(); - std::mem::forget(mut_vec); - CByteArray { - data, - size, - capacity, - } + /// Returns an empty `CByteArray` instance. + pub fn new() -> CByteArray { + CByteArray::from(Vec::new()) } /// Converts `CByteArray` into `Vec` without additional allocation. @@ -95,6 +101,11 @@ impl CByteArray { pub fn size(&self) -> usize { self.size } + + /// Returns the data slice. + pub unsafe fn as_slice(&self) -> &[u8] { + std::slice::from_raw_parts(self.data, self.size) + } } /// Releases the memory previously allocated for the pointer to `CByteArray`. diff --git a/rust/tw_memory/src/ffi/mod.rs b/rust/tw_memory/src/ffi/mod.rs index 058f841646a..9afbd7509f1 100644 --- a/rust/tw_memory/src/ffi/mod.rs +++ b/rust/tw_memory/src/ffi/mod.rs @@ -11,6 +11,9 @@ use std::ffi::{c_char, CString}; pub mod c_byte_array; pub mod c_byte_array_ref; pub mod c_result; +pub mod tw_data; +pub mod tw_data_vector; +pub mod tw_string; /// Releases the memory previously allocated for the `ptr` string. /// \param ptr *non-null* C-compatible, nul-terminated string. @@ -32,13 +35,20 @@ pub trait RawPtrTrait: Sized { Some(*Box::from_raw(raw)) } - unsafe fn from_ptr_as_ref(raw: *mut Self) -> Option<&'static Self> { + unsafe fn from_ptr_as_ref(raw: *const Self) -> Option<&'static Self> { if raw.is_null() { return None; } Some(&*raw) } + unsafe fn from_ptr_as_mut(raw: *mut Self) -> Option<&'static mut Self> { + if raw.is_null() { + return None; + } + Some(&mut *raw) + } + unsafe fn from_ptr_as_box(raw: *mut Self) -> Option> { if raw.is_null() { return None; diff --git a/rust/tw_memory/src/ffi/tw_data.rs b/rust/tw_memory/src/ffi/tw_data.rs new file mode 100644 index 00000000000..ad4c6ad4400 --- /dev/null +++ b/rust/tw_memory/src/ffi/tw_data.rs @@ -0,0 +1,104 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::c_byte_array_ref::CByteArrayRef; +use crate::ffi::RawPtrTrait; +use crate::Data; + +/// Defines a resizable block of data. +/// +/// The implementation of these methods should be language-specific to minimize translation overhead. +/// For instance it should be a `jbyteArray` for Java and an `NSData` for Swift. +#[derive(Clone, Debug, Default)] +pub struct TWData(Data); + +impl TWData { + /// Returns an empty `TWData` instance. + pub fn new() -> TWData { + TWData(Vec::new()) + } + + /// Creates a `TWData` from a raw byte array. + pub unsafe fn from_raw_data(bytes: *const u8, size: usize) -> Option { + CByteArrayRef::new(bytes, size).to_vec().map(TWData) + } + + /// Converts `TWData` into `Data` without additional allocation. + pub fn into_vec(self) -> Data { + self.0 + } + + /// Copies underlying data. + pub fn to_vec(&self) -> Data { + self.0.clone() + } + + /// Returns the data slice. + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + /// Returns a pointer to the data. + pub fn data(&self) -> *const u8 { + self.0.as_ptr() + } + + /// Returns a length of the data. + pub fn size(&self) -> usize { + self.0.len() + } +} + +impl From for TWData { + fn from(data: Data) -> Self { + TWData(data) + } +} + +impl RawPtrTrait for TWData {} + +/// Creates a block of data from a byte array. +/// +/// \param bytes Non-null raw bytes buffer +/// \param size size of the buffer +/// \return Non-null filled block of data. +#[no_mangle] +pub unsafe extern "C" fn tw_data_create_with_bytes(bytes: *const u8, size: usize) -> *mut TWData { + TWData::from_raw_data(bytes, size) + .map(|data| data.into_ptr()) + .unwrap_or_else(std::ptr::null_mut) +} + +/// Deletes a block of data created with a `TWDataCreate*` method. +/// +/// \param data A non-null valid block of data +#[no_mangle] +pub unsafe extern "C" fn tw_data_delete(data: *mut TWData) { + // Take the ownership back to rust and drop the owner. + let _ = TWData::from_ptr(data); +} + +/// Returns the raw pointer to the contents of data. +/// +/// \param data A non-null valid block of data +/// \return the raw pointer to the contents of data +#[no_mangle] +pub unsafe extern "C" fn tw_data_bytes(data: *const TWData) -> *const u8 { + TWData::from_ptr_as_ref(data) + .map(TWData::data) + .unwrap_or_else(std::ptr::null) +} + +/// Returns the size in bytes. +/// +/// \param data A non-null valid block of data +/// \return the size of the given block of data +#[no_mangle] +pub unsafe extern "C" fn tw_data_size(data: *const TWData) -> usize { + TWData::from_ptr_as_ref(data) + .map(|data| data.size()) + .unwrap_or_default() +} diff --git a/rust/tw_memory/src/ffi/tw_data_vector.rs b/rust/tw_memory/src/ffi/tw_data_vector.rs new file mode 100644 index 00000000000..5c596ede7fa --- /dev/null +++ b/rust/tw_memory/src/ffi/tw_data_vector.rs @@ -0,0 +1,74 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_data::TWData; +use crate::ffi::RawPtrTrait; + +type Data = Vec; + +/// A vector of `TWData` byte arrays. +#[derive(Default)] +pub struct TWDataVector(Vec); + +impl TWDataVector { + /// Returns an empty `TWDataVector` instance. + pub fn new() -> TWDataVector { + TWDataVector(Vec::new()) + } + + /// Adds an element to a vector of `TWData`. + pub fn push(&mut self, data: TWData) { + self.0.push(data); + } + + /// Retrieves the number of elements. + pub fn size(&self) -> usize { + self.0.len() + } + + /// Converts `Vec` to `Vec` by cloning each element. + pub fn to_data_vec(&self) -> Vec { + self.0.iter().map(TWData::to_vec).collect() + } +} + +impl RawPtrTrait for TWDataVector {} + +/// Creates a Vector of Data. +/// +/// \note Must be deleted with \TWDataVectorDelete +/// \return a non-null Vector of Data. +#[no_mangle] +pub unsafe extern "C" fn tw_data_vector_create() -> *mut TWDataVector { + TWDataVector::new().into_ptr() +} + +/// Delete/Deallocate a Vector of Data +/// +/// \param data_vector A non-null Vector of data +#[no_mangle] +pub unsafe extern "C" fn tw_data_vector_delete(data_vector: *mut TWDataVector) { + // Take the ownership back to rust and drop the owner. + let _ = TWDataVector::from_ptr(data_vector); +} + +/// Add an element to a Vector of Data. Element is cloned +/// +/// \param data_vector A non-null Vector of data +/// \param data A non-null valid block of data +/// \note data input parameter must be deleted on its own +#[no_mangle] +pub unsafe extern "C" fn tw_data_vector_add(data_vector: *mut TWDataVector, data: *const TWData) { + let Some(data_vector) = TWDataVector::from_ptr_as_mut(data_vector) else { + return; + }; + + let Some(data_ref) = TWData::from_ptr_as_ref(data) else { + return; + }; + + data_vector.push(data_ref.clone()); +} diff --git a/rust/tw_memory/src/ffi/tw_string.rs b/rust/tw_memory/src/ffi/tw_string.rs new file mode 100644 index 00000000000..41d122bd64e --- /dev/null +++ b/rust/tw_memory/src/ffi/tw_string.rs @@ -0,0 +1,80 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::RawPtrTrait; +use std::ffi::{c_char, CStr, CString}; + +/// Defines a resizable string. +/// +/// The implementation of these methods should be language-specific to minimize translation overhead. +/// For instance it should be a `jstring` for Java and an `NSString` for Swift. +/// Create allocates memory, the delete call should be called at the end to release memory. +#[derive(Debug, Default)] +pub struct TWString(CString); + +impl TWString { + /// Returns an empty `TWString` instance. + pub fn new() -> TWString { + TWString(CString::default()) + } + + /// Creates a `TWString` from a null-terminated UTF8 byte array. + pub unsafe fn from_c_str(ptr: *const c_char) -> Option { + if ptr.is_null() { + return None; + } + let str = CStr::from_ptr(ptr); + Some(TWString(CString::from(str))) + } + + /// Converts `TWString` into `String` without additional allocation. + pub fn into_string(self) -> Option { + self.0.into_string().ok() + } + + /// Returns a string slice. + pub fn as_str(&self) -> Option<&str> { + self.0.to_str().ok() + } + + /// Returns the const pointer to the string. + pub fn as_c_char(&self) -> *const c_char { + self.0.as_ptr() + } +} + +impl From for TWString { + fn from(s: String) -> Self { + TWString(CString::new(s).expect("CString::new(String) should never fail")) + } +} + +impl RawPtrTrait for TWString {} + +/// Creates a `TWString` from a null-terminated UTF8 byte array. It must be deleted at the end. +/// \param bytes a null-terminated UTF8 byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_string_create_with_utf8_bytes(bytes: *const c_char) -> *mut TWString { + TWString::from_c_str(bytes) + .map(TWString::into_ptr) + .unwrap_or_else(std::ptr::null_mut) +} + +/// Returns the raw pointer to the string's UTF8 bytes (null-terminated). +/// \param str a TWString pointer. +#[no_mangle] +pub unsafe extern "C" fn tw_string_utf8_bytes(str: *const TWString) -> *const c_char { + TWString::from_ptr_as_ref(str) + .map(|str| str.as_c_char()) + .unwrap_or_else(std::ptr::null) +} + +/// Deletes a string created with a `TWStringCreate*` method and frees the memory. +/// \param str a `TWString` pointer. +#[no_mangle] +pub unsafe extern "C" fn tw_string_delete(str: *mut TWString) { + let _ = TWString::from_ptr(str); +} diff --git a/rust/tw_memory/src/lib.rs b/rust/tw_memory/src/lib.rs index 2a164e90020..ff757aa6b04 100644 --- a/rust/tw_memory/src/lib.rs +++ b/rust/tw_memory/src/lib.rs @@ -6,8 +6,13 @@ use std::ffi::{c_char, CString}; +pub type Data = Vec; + pub mod ffi; +#[cfg(feature = "test-utils")] +pub mod test_utils; + pub fn c_string_standalone>(input: S) -> *const c_char { let res = CString::new(input.into()).unwrap(); let p = res.as_ptr(); diff --git a/rust/tw_memory/src/test_utils/mod.rs b/rust/tw_memory/src/test_utils/mod.rs new file mode 100644 index 00000000000..d2520378719 --- /dev/null +++ b/rust/tw_memory/src/test_utils/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod tw_data_helper; +pub mod tw_data_vector_helper; +pub mod tw_string_helper; +pub mod tw_wrapper; diff --git a/rust/tw_memory/src/test_utils/tw_data_helper.rs b/rust/tw_memory/src/test_utils/tw_data_helper.rs new file mode 100644 index 00000000000..4a317968e29 --- /dev/null +++ b/rust/tw_memory/src/test_utils/tw_data_helper.rs @@ -0,0 +1,54 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_data::{ + tw_data_bytes, tw_data_create_with_bytes, tw_data_delete, tw_data_size, TWData, +}; +use crate::Data; + +pub struct TWDataHelper { + ptr: *mut TWData, +} + +impl TWDataHelper { + pub fn create(bytes: Data) -> Self { + let ptr = unsafe { tw_data_create_with_bytes(bytes.as_ptr(), bytes.len()) }; + assert!(!ptr.is_null()); + TWDataHelper { ptr } + } + + pub fn wrap(ptr: *mut TWData) -> Self { + TWDataHelper { ptr } + } + + pub fn ptr(&self) -> *mut TWData { + self.ptr + } + + pub fn is_null(&self) -> bool { + self.ptr.is_null() + } + + pub fn to_vec(&self) -> Option { + if self.ptr.is_null() { + return None; + } + let bytes_ptr = unsafe { tw_data_bytes(self.ptr) }; + let len = unsafe { tw_data_size(self.ptr) }; + + let bytes = unsafe { std::slice::from_raw_parts(bytes_ptr, len) }.to_vec(); + Some(bytes) + } +} + +impl Drop for TWDataHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_data_delete(self.ptr) } + } +} diff --git a/rust/tw_memory/src/test_utils/tw_data_vector_helper.rs b/rust/tw_memory/src/test_utils/tw_data_vector_helper.rs new file mode 100644 index 00000000000..340461e5ec8 --- /dev/null +++ b/rust/tw_memory/src/test_utils/tw_data_vector_helper.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_data_vector::{ + tw_data_vector_add, tw_data_vector_create, tw_data_vector_delete, TWDataVector, +}; +use crate::test_utils::tw_data_helper::TWDataHelper; + +type Data = Vec; + +pub struct TWDataVectorHelper { + ptr: *mut TWDataVector, +} + +impl TWDataVectorHelper { + pub fn create(vec: I) -> TWDataVectorHelper + where + I: IntoIterator, + { + let ptr = unsafe { tw_data_vector_create() }; + for data in vec { + let data = TWDataHelper::create(data); + unsafe { tw_data_vector_add(ptr, data.ptr()) }; + } + + TWDataVectorHelper { ptr } + } + + pub fn ptr(&self) -> *mut TWDataVector { + self.ptr + } +} + +impl Drop for TWDataVectorHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_data_vector_delete(self.ptr) } + } +} diff --git a/rust/tw_memory/src/test_utils/tw_string_helper.rs b/rust/tw_memory/src/test_utils/tw_string_helper.rs new file mode 100644 index 00000000000..dd1fb22194a --- /dev/null +++ b/rust/tw_memory/src/test_utils/tw_string_helper.rs @@ -0,0 +1,52 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_string::{ + tw_string_create_with_utf8_bytes, tw_string_delete, tw_string_utf8_bytes, TWString, +}; +use std::ffi::{CStr, CString}; + +pub struct TWStringHelper { + ptr: *mut TWString, +} + +impl TWStringHelper { + pub fn create(s: &str) -> TWStringHelper { + let cstring = CString::new(s).unwrap(); + let ptr = unsafe { tw_string_create_with_utf8_bytes(cstring.as_ptr()) }; + assert!(!ptr.is_null()); + TWStringHelper { ptr } + } + + pub fn wrap(ptr: *mut TWString) -> Self { + TWStringHelper { ptr } + } + + pub fn ptr(&self) -> *mut TWString { + self.ptr + } + + pub fn to_string(&self) -> Option { + if self.ptr.is_null() { + return None; + } + let c_str = unsafe { tw_string_utf8_bytes(self.ptr) }; + let str = unsafe { CStr::from_ptr(c_str) } + .to_str() + .expect("Received an invalid c_str") + .to_string(); + Some(str) + } +} + +impl Drop for TWStringHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_string_delete(self.ptr) } + } +} diff --git a/rust/tw_memory/src/test_utils/tw_wrapper.rs b/rust/tw_memory/src/test_utils/tw_wrapper.rs new file mode 100644 index 00000000000..8cb5d26ada5 --- /dev/null +++ b/rust/tw_memory/src/test_utils/tw_wrapper.rs @@ -0,0 +1,32 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub trait WithDestructor: Sized { + fn destructor() -> unsafe extern "C" fn(*mut Self); +} + +pub struct TWWrapper { + ptr: *mut T, +} + +impl TWWrapper { + pub fn wrap(ptr: *mut T) -> Self { + TWWrapper { ptr } + } + + pub fn ptr(&self) -> *mut T { + self.ptr + } +} + +impl Drop for TWWrapper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { (T::destructor())(self.ptr) } + } +} diff --git a/rust/tw_memory/tests/c_byte_array_ffi_tests.rs b/rust/tw_memory/tests/c_byte_array_ffi_tests.rs index 5cbd8078f11..e3fe8e41ba3 100644 --- a/rust/tw_memory/tests/c_byte_array_ffi_tests.rs +++ b/rust/tw_memory/tests/c_byte_array_ffi_tests.rs @@ -11,7 +11,7 @@ fn test_free_c_byte_array() { unsafe { free_c_byte_array(std::ptr::null_mut()); - let mut raw_array = CByteArray::new(vec![1, 2, 3]); + let mut raw_array = CByteArray::from(vec![1, 2, 3]); free_c_byte_array(&mut raw_array as *mut CByteArray); // The following leads to an undefined behaviour. @@ -22,12 +22,12 @@ fn test_free_c_byte_array() { #[test] fn test_drop_c_byte_array() { // The memory must be released on `Drop::drop`. - let _ = CByteArray::new(vec![1, 2, 3]); + let _ = CByteArray::from(vec![1, 2, 3]); } #[test] fn test_c_byte_array_into_vec() { // The memory must be valid after `CByteArray::into_vec` and `CByteArray::drop`. - let data = unsafe { CByteArray::new(vec![1, 2, 3]).into_vec() }; + let data = unsafe { CByteArray::from(vec![1, 2, 3]).into_vec() }; assert_eq!(data, [1, 2, 3]); } diff --git a/rust/tw_memory/tests/c_result_ffi_tests.rs b/rust/tw_memory/tests/c_result_ffi_tests.rs index 58e11216516..dca5cee3f9e 100644 --- a/rust/tw_memory/tests/c_result_ffi_tests.rs +++ b/rust/tw_memory/tests/c_result_ffi_tests.rs @@ -9,7 +9,7 @@ use tw_memory::ffi::c_result::{OK_CODE, UNKNOWN_ERROR}; #[test] fn test_c_result_unwrap() { - let c_res = CByteArrayResult::ok(CByteArray::new(vec![1, 2, 3])); + let c_res = CByteArrayResult::ok(CByteArray::from(vec![1, 2, 3])); assert!(c_res.is_ok()); assert!(!c_res.is_err()); @@ -35,7 +35,7 @@ fn test_c_result_error_with_ok_code() { #[test] fn test_c_result_into_result() { - let c_res = CByteArrayResult::ok(CByteArray::new(vec![1, 2, 3])); + let c_res = CByteArrayResult::ok(CByteArray::from(vec![1, 2, 3])); c_res.into_result().unwrap(); let c_res = CByteArrayResult::error(10); diff --git a/rust/tw_misc/Cargo.toml b/rust/tw_misc/Cargo.toml index ac5867c20dc..60fe9a1033d 100644 --- a/rust/tw_misc/Cargo.toml +++ b/rust/tw_misc/Cargo.toml @@ -3,5 +3,10 @@ name = "tw_misc" version = "0.1.0" edition = "2021" +[features] +test-utils = ["serde", "serde_json"] + [dependencies] +serde = { version = "1.0.163", features = ["derive"], optional = true } +serde_json = { version = "1.0.96", optional = true } zeroize = "1.6.0" diff --git a/rust/tw_misc/src/lib.rs b/rust/tw_misc/src/lib.rs index 1f73752d7a1..a6017087f76 100644 --- a/rust/tw_misc/src/lib.rs +++ b/rust/tw_misc/src/lib.rs @@ -1,2 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + pub mod macros; +#[cfg(feature = "test-utils")] +pub mod test_utils; pub mod traits; diff --git a/rust/tw_misc/src/test_utils/json.rs b/rust/tw_misc/src/test_utils/json.rs new file mode 100644 index 00000000000..bcab8c3f6d3 --- /dev/null +++ b/rust/tw_misc/src/test_utils/json.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::Value as Json; +use std::borrow::Cow; + +pub trait ToJson { + fn to_json(&self) -> Json; +} + +impl ToJson for Json { + #[track_caller] + fn to_json(&self) -> Json { + self.clone() + } +} + +impl<'a> ToJson for Cow<'a, str> { + #[track_caller] + fn to_json(&self) -> Json { + self.as_ref().to_json() + } +} + +impl<'a> ToJson for &'a str { + #[track_caller] + fn to_json(&self) -> Json { + serde_json::from_str(self).expect("Error on deserializing JSON from string") + } +} + +#[macro_export] +macro_rules! assert_eq_json { + ($left:expr, $right:expr) => {{ + use $crate::test_utils::json::ToJson; + + let left = $left.to_json(); + let right = $right.to_json(); + assert_eq!(left, right); + }}; +} diff --git a/rust/tw_misc/src/test_utils/mod.rs b/rust/tw_misc/src/test_utils/mod.rs new file mode 100644 index 00000000000..019606a0fca --- /dev/null +++ b/rust/tw_misc/src/test_utils/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod json; diff --git a/rust/tw_misc/src/traits.rs b/rust/tw_misc/src/traits.rs index 1b8900eb019..36fb534853a 100644 --- a/rust/tw_misc/src/traits.rs +++ b/rust/tw_misc/src/traits.rs @@ -38,3 +38,7 @@ impl IntoOption for Option { self } } + +pub trait FromSlice: for<'a> TryFrom<&'a [u8]> {} + +impl FromSlice for T where for<'a> T: TryFrom<&'a [u8]> {} diff --git a/rust/tw_move_parser/Cargo.toml b/rust/tw_move_parser/Cargo.toml deleted file mode 100644 index b674b9a8908..00000000000 --- a/rust/tw_move_parser/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "tw_move_parser" -version = "0.1.0" -edition = "2021" - -[dependencies] -bcs = "0.1.4" -hex = "0.4.3" -move-core-types = { git = "https://github.com/move-language/move", rev = "f7137eabc2046f76fdad3ded2c51e03a3b1fbd01", features = ["address32"] } -tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_move_parser/src/ffi.rs b/rust/tw_move_parser/src/ffi.rs deleted file mode 100644 index 7552e37f457..00000000000 --- a/rust/tw_move_parser/src/ffi.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#![allow(clippy::missing_safety_doc)] - -use move_core_types::language_storage::TypeTag; -use move_core_types::transaction_argument::TransactionArgument; -use move_core_types::*; -use std::{ffi::c_char, ffi::CStr}; -use tw_memory::ffi::c_result::{CStrResult, ErrorCode}; - -#[repr(C)] -pub enum CMoveParserCode { - Ok = 0, - InvalidInput = 1, - ErrorParsing = 2, -} - -impl From for ErrorCode { - fn from(code: CMoveParserCode) -> Self { - code as ErrorCode - } -} - -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum ETypeTag { - Bool = 1, - U8 = 2, - U64 = 3, - U128 = 4, - Address = 5, - Signer = 6, - Vector = 7, - Struct = 8, - Error = 9, -} - -/// Parses a Move type tag. -/// \param input *non-null* C-compatible, nul-terminated string. -/// \return `ETypeTag` enumeration. -#[no_mangle] -pub unsafe extern "C" fn parse_type_tag(input: *const c_char) -> ETypeTag { - let s = CStr::from_ptr(input).to_str().unwrap(); - let transaction_argument = match parser::parse_type_tag(s) { - Ok(v) => v, - Err(_) => return ETypeTag::Error, - }; - match transaction_argument { - TypeTag::Bool => ETypeTag::Bool, - TypeTag::U8 => ETypeTag::U8, - TypeTag::U64 => ETypeTag::U64, - TypeTag::U128 => ETypeTag::U128, - TypeTag::Address => ETypeTag::Address, - TypeTag::Signer => ETypeTag::Signer, - TypeTag::Vector(_) => ETypeTag::Vector, - TypeTag::Struct(_) => ETypeTag::Struct, - } -} - -/// Parses `input` as a Move function argument. -/// \param input *non-null* C-compatible, nul-terminated string. -/// \return *non-null* C-compatible, nul-terminated string, Binary Canonical Serialization (BCS). -#[no_mangle] -pub unsafe extern "C" fn parse_function_argument_to_bcs(input: *const c_char) -> CStrResult { - let s = match CStr::from_ptr(input).to_str() { - Ok(input) => input, - Err(_) => return CStrResult::error(CMoveParserCode::InvalidInput), - }; - let transaction_argument = match parser::parse_transaction_argument(s) { - Ok(v) => v, - Err(_) => return CStrResult::error(CMoveParserCode::ErrorParsing), - }; - let v = match transaction_argument { - TransactionArgument::U8(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::U64(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::U128(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::Address(v) => { - hex::encode(bcs::to_bytes(&bcs::to_bytes(&v).unwrap()).unwrap()) - }, - TransactionArgument::U8Vector(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::Bool(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - }; - CStrResult::ok(tw_memory::c_string_standalone(v)) -} diff --git a/rust/tw_move_parser/tests/move_parser_ffi_tests.rs b/rust/tw_move_parser/tests/move_parser_ffi_tests.rs deleted file mode 100644 index 3cb0d528f5e..00000000000 --- a/rust/tw_move_parser/tests/move_parser_ffi_tests.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -use std::ffi::{c_char, CString}; -use tw_move_parser::ffi::{parse_function_argument_to_bcs, parse_type_tag, ETypeTag}; - -#[test] -fn tests_type_tag() { - let tag = unsafe { parse_type_tag("0x1::aptos_coin::AptosCoin\0".as_ptr() as *const c_char) }; - assert_eq!(tag, ETypeTag::Struct); -} - -#[test] -fn tests_function_argument_to_bcs() { - let str = unsafe { - let input = "10000000\0".as_ptr() as *const c_char; - CString::from_raw(parse_function_argument_to_bcs(input).unwrap() as *mut c_char) - .into_string() - .unwrap() - }; - assert_eq!(str, "8096980000000000"); - - let str = unsafe { - let input = "5047445908\0".as_ptr() as *const c_char; - CString::from_raw(parse_function_argument_to_bcs(input).unwrap() as *mut c_char) - .into_string() - .unwrap() - }; - assert_eq!(str, "94e9d92c01000000"); -} - -#[test] -fn tests_function_argument_to_bcs_another() { - let str = unsafe { - let input = "0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6\0".as_ptr() - as *const c_char; - CString::from_raw(parse_function_argument_to_bcs(input).unwrap() as *mut c_char) - .into_string() - .unwrap() - }; - let decoded = hex::decode(str).unwrap(); - let v = vec![decoded]; - let actual = hex::encode(bcs::to_bytes(&v).unwrap()); - let expected = "012120c95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6"; - assert_eq!(actual, expected); -} diff --git a/rust/tw_number/Cargo.toml b/rust/tw_number/Cargo.toml new file mode 100644 index 00000000000..3a180315669 --- /dev/null +++ b/rust/tw_number/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tw_number" +version = "0.1.0" +edition = "2021" + +[features] +arbitrary = ["dep:arbitrary", "primitive-types/arbitrary"] +default = ["helpers", "serde"] +helpers = [] + +[dependencies] +arbitrary = { version = "1", features = ["derive"], optional = true } +lazy_static = "1.4.0" +primitive-types = "0.12.1" +serde = { version = "1.0.159", features = ["derive"], optional = true } +tw_hash = { path = "../tw_hash" } +tw_memory = { path = "../tw_memory" } + +[dev-dependencies] +tw_encoding = { path = "../tw_encoding" } diff --git a/rust/tw_number/src/i256.rs b/rust/tw_number/src/i256.rs new file mode 100644 index 00000000000..aaae24a8257 --- /dev/null +++ b/rust/tw_number/src/i256.rs @@ -0,0 +1,458 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{NumberError, NumberResult, Sign, U256}; +use lazy_static::lazy_static; +use primitive_types::U256 as BaseU256; +use std::borrow::Cow; +use std::fmt; +use std::str::FromStr; +use tw_hash::H256; +use tw_memory::Data; + +lazy_static! { + static ref NEGATIVE_BIT_MASK: BaseU256 = BaseU256::from(1u8) << 255u8; + static ref MAX_POSITIVE_ABS: BaseU256 = BaseU256::MAX / 2u64; + static ref MAX_NEGATIVE_ABS: BaseU256 = *MAX_POSITIVE_ABS + 1u64; +} + +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct I256(BaseU256); + +// cbindgen:ignore +impl I256 { + pub const BITS: usize = 256; + + pub fn max() -> I256 { + I256(*MAX_POSITIVE_ABS) + } + + pub fn min() -> I256 { + I256::try_from_sign_and_abs(Sign::Negative, *MAX_NEGATIVE_ABS) + .expect("Expected a valid negative abs value") + } + + /// Converts the signed 256-bit number to an unsigned 256-bit representation. + /// Thus, + /// - `1` is [`U256::from(1)`] + /// - `0` is [`U256::zero()`] + /// - `-1` is [`U256::MAX`] + /// - `-2` is [`U256::MAX - 1`] + /// ... + #[inline] + pub fn to_u256_repr(&self) -> U256 { + U256::from(self.0) + } + + /// Constructs a signed 256-bit number from an unsigned 256-bit representation. + /// Thus, + /// - [`U256::from(1)`] is `1` + /// - [`U256::zero()`] is `0` + /// - [`U256::MAX`] is `-1` + /// - [`U256::MAX - 1`] is `-2` + /// ... + #[inline] + pub fn from_u256_repr(unsigned: U256) -> I256 { + I256(unsigned.0) + } + + /// Constructs a signed 256-bit number from a 256-bit big-endian representation. + #[inline] + pub fn from_big_endian(data: H256) -> I256 { + let inner = primitive_types::U256::from_big_endian(data.as_slice()); + I256(inner) + } + + #[inline] + pub fn from_big_endian_slice(data: &[u8]) -> NumberResult { + let u = U256::from_big_endian_slice(data)?; + Ok(I256::from_u256_repr(u)) + } + + #[inline] + pub fn to_big_endian(&self) -> H256 { + self.to_u256_repr().to_big_endian() + } + + #[inline] + pub fn to_big_endian_compact(&self) -> Data { + self.to_u256_repr().to_big_endian_compact() + } + + /// Returns the sign of the integer. + pub fn sign(&self) -> Sign { + let most_significant_bit = self.0 & *NEGATIVE_BIT_MASK; + if most_significant_bit.is_zero() { + Sign::Positive + } else { + Sign::Negative + } + } + + fn try_from_sign_and_abs(sign: Sign, abs_value: BaseU256) -> NumberResult { + if abs_value.is_zero() { + return Ok(I256(abs_value)); + } + + match sign { + Sign::Positive if abs_value > *MAX_POSITIVE_ABS => Err(NumberError::Overflow), + Sign::Positive => Ok(I256(abs_value)), + Sign::Negative if abs_value > *MAX_NEGATIVE_ABS => Err(NumberError::Overflow), + Sign::Negative => { + let int = twos_complement(abs_value); + Ok(I256(int)) + }, + } + } + + fn to_sign_and_abs(&self) -> (Sign, BaseU256) { + let sign = self.sign(); + let abs = match sign { + Sign::Positive => self.0, + Sign::Negative => twos_complement(self.0), + }; + (sign, abs) + } +} + +#[cfg(feature = "helpers")] +impl I256 { + #[inline] + pub fn encode_be_compact(num: i64) -> Cow<'static, [u8]> { + I256::from(num).to_big_endian_compact().into() + } +} + +impl TryFrom for U256 { + type Error = NumberError; + + fn try_from(i: I256) -> Result { + let (sign, abs) = i.to_sign_and_abs(); + if sign.is_negative() { + return Err(NumberError::Overflow); + } + Ok(U256(abs)) + } +} + +impl TryFrom for I256 { + type Error = NumberError; + + fn try_from(u: U256) -> Result { + I256::try_from_sign_and_abs(Sign::Positive, u.0) + } +} + +impl FromStr for I256 { + type Err = NumberError; + + fn from_str(s: &str) -> Result { + let (sign, value_str) = match s.strip_prefix('-') { + Some(value_str) => (Sign::Negative, value_str), + None => (Sign::Positive, s), + }; + + let abs_value = BaseU256::from_dec_str(value_str) + .map_err(|_| NumberError::InvalidStringRepresentation)?; + I256::try_from_sign_and_abs(sign, abs_value) + } +} + +impl fmt::Debug for I256 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl fmt::Display for I256 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (sign, abs) = self.to_sign_and_abs(); + write!(f, "{sign}{abs}") + } +} + +#[cfg(feature = "serde")] +mod impl_serde { + use super::I256; + use serde::de::Error as DeError; + use serde::{Deserialize, Deserializer, Serializer}; + use std::str::FromStr; + + impl I256 { + pub fn as_decimal_str(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } + + pub fn from_decimal_str<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: &str = Deserialize::deserialize(deserializer)?; + I256::from_str(s).map_err(|e| DeError::custom(format!("{e:?}"))) + } + + pub fn from_i64_or_decimal_str<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + crate::serde_common::from_num_or_decimal_str::<'de, I256, i64, D>(deserializer) + } + } +} + +macro_rules! impl_map_from_signed { + ($int:ty) => { + impl From<$int> for I256 { + fn from(int: $int) -> Self { + let sign = if int >= 0 { + Sign::Positive + } else { + Sign::Negative + }; + let abs_value = match int.overflowing_abs() { + (_, true) => BaseU256::from(<$int>::MAX) + 1u64, + (abs_i, false) => BaseU256::from(abs_i), + }; + I256::try_from_sign_and_abs(sign, abs_value).expect("Unexpected overflow") + } + } + }; +} + +macro_rules! impl_map_from_unsigned { + ($uint:ty) => { + impl From<$uint> for I256 { + fn from(uint: $uint) -> Self { + I256(BaseU256::from(uint)) + } + } + }; +} + +impl_map_from_signed!(i8); +impl_map_from_signed!(i16); +impl_map_from_signed!(i32); +impl_map_from_signed!(i64); + +impl_map_from_unsigned!(u8); +impl_map_from_unsigned!(u16); +impl_map_from_unsigned!(u32); +impl_map_from_unsigned!(u64); + +/// Compute the [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) of this number. +fn twos_complement(abs_value: BaseU256) -> BaseU256 { + let (value, _overflowed) = (!abs_value).overflowing_add(BaseU256::from(1u64)); + value +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_i256_from_str_impl(dec_str: &str, expected: BaseU256) { + let i256 = I256::from_str(dec_str).unwrap(); + assert_eq!(i256.0, expected, "{}", dec_str); + } + + fn test_i256_display_impl(base_u: BaseU256, expected: &str) { + assert_eq!(I256(base_u).to_string(), expected); + } + + fn test_i256_from_impl(int: I, expected_base: BaseU256) + where + I256: From, + { + assert_eq!(I256::from(int), I256(expected_base)); + } + + fn test_i256_to_u256(int: I, expected: U256) + where + I256: From, + { + assert_eq!(U256::try_from(I256::from(int)).unwrap(), expected); + } + + #[track_caller] + fn test_i256_to_u256_error(int: I) + where + I256: From, + { + let int = I256::from(int); + U256::try_from(int).unwrap_err(); + } + + #[track_caller] + fn test_i256_from_u256(uint: BaseU256) { + let int = I256::try_from(U256::from(uint)).unwrap(); + assert_eq!(int.0, uint); + } + + #[track_caller] + fn test_i256_from_u256_error(uint: BaseU256) { + I256::try_from(U256::from(uint)).unwrap_err(); + } + + #[test] + fn test_i256_from_str() { + test_i256_from_str_impl( + "57896044618658097711785492504343953926634992332820282019728792003956564819967", + *MAX_POSITIVE_ABS, + ); + test_i256_from_str_impl("2", BaseU256::from(2u8)); + test_i256_from_str_impl("1", BaseU256::from(1u8)); + test_i256_from_str_impl("0", BaseU256::zero()); + test_i256_from_str_impl("-0", BaseU256::zero()); + test_i256_from_str_impl("-1", BaseU256::MAX); + test_i256_from_str_impl("-2", BaseU256::MAX - 1); + test_i256_from_str_impl("-3", BaseU256::MAX - 2); + test_i256_from_str_impl( + "-57896044618658097711785492504343953926634992332820282019728792003956564819967", + *MAX_NEGATIVE_ABS + 1, + ); + test_i256_from_str_impl( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + *MAX_NEGATIVE_ABS, + ); + } + + #[test] + fn test_i256_from_str_negative() { + test_i256_from_str_impl("-0", BaseU256::zero()); + test_i256_from_str_impl("-1", BaseU256::MAX); + test_i256_from_str_impl("-2", BaseU256::MAX - 1); + test_i256_from_str_impl("-3", BaseU256::MAX - 2); + test_i256_from_str_impl( + "-57896044618658097711785492504343953926634992332820282019728792003956564819967", + *MAX_NEGATIVE_ABS + 1, + ); + test_i256_from_str_impl( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + *MAX_NEGATIVE_ABS, + ); + } + + #[test] + fn test_i256_from_str_overflow() { + let invalid = [ + "57896044618658097711785492504343953926634992332820282019728792003956564819968", + "57896044618658097711785492504343953926634992332820282019728792003956564819969", + "115792089237316195423570985008687907853269984665640564039457584007913129639935", + ]; + for num_str in invalid { + I256::from_str(num_str).expect_err(num_str); + } + } + + #[test] + fn test_i256_from_str_negative_overflow() { + let invalid = [ + "-57896044618658097711785492504343953926634992332820282019728792003956564819969", + "-57896044618658097711785492504343953926634992332820282019728792003956564819970", + "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + ]; + for num_str in invalid { + I256::from_str(num_str).expect_err(num_str); + } + } + + #[test] + fn test_i256_display() { + test_i256_display_impl(BaseU256::zero(), "0"); + test_i256_display_impl(BaseU256::from(1u64), "1"); + test_i256_display_impl(BaseU256::from(2u64), "2"); + test_i256_display_impl(BaseU256::from(10u64), "10"); + test_i256_display_impl( + *MAX_POSITIVE_ABS, + "57896044618658097711785492504343953926634992332820282019728792003956564819967", + ); + } + + #[test] + fn test_i256_display_negative() { + test_i256_display_impl(BaseU256::MAX, "-1"); + test_i256_display_impl(BaseU256::MAX - 1, "-2"); + test_i256_display_impl(BaseU256::MAX - 9, "-10"); + test_i256_display_impl( + *MAX_NEGATIVE_ABS + 1, + "-57896044618658097711785492504343953926634992332820282019728792003956564819967", + ); + test_i256_display_impl( + *MAX_NEGATIVE_ABS, + "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + ); + } + + #[test] + fn test_i256_from_unsigned() { + test_i256_from_impl(0u16, BaseU256::zero()); + test_i256_from_impl(1u16, BaseU256::from(1u64)); + test_i256_from_impl(10u16, BaseU256::from(10u64)); + } + + #[test] + fn test_i256_from_positive() { + test_i256_from_impl(0i16, BaseU256::zero()); + test_i256_from_impl(1i16, BaseU256::from(1u64)); + test_i256_from_impl(10i16, BaseU256::from(10u64)); + test_i256_from_impl(i16::MAX, BaseU256::from(i16::MAX as u64)); + } + + #[test] + fn test_i256_from_negative() { + test_i256_from_impl(-1i8, BaseU256::MAX); + test_i256_from_impl(-2i8, BaseU256::MAX - 1u64); + test_i256_from_impl(-10i8, BaseU256::MAX - 9u64); + test_i256_from_impl(i8::MIN + 1, BaseU256::MAX - i8::MAX + 1); + test_i256_from_impl(i8::MIN, BaseU256::MAX - i8::MAX); + test_i256_from_impl(i64::MIN + 1, BaseU256::MAX - i64::MAX + 1); + test_i256_from_impl(i64::MIN, BaseU256::MAX - i64::MAX); + } + + #[test] + fn test_i256_try_to_u256() { + test_i256_to_u256(0i64, U256::from(0u64)); + test_i256_to_u256(1i64, U256::from(1u64)); + test_i256_to_u256(I256::max(), U256::from(BaseU256::MAX / 2)); + } + + #[test] + fn test_i256_try_to_u256_error() { + test_i256_to_u256_error(-1i64); + test_i256_to_u256_error(-10i64); + test_i256_to_u256_error(i64::MIN); + } + + #[test] + fn test_i256_try_from_u256() { + test_i256_from_u256(BaseU256::zero()); + test_i256_from_u256(BaseU256::from(1)); + test_i256_from_u256(BaseU256::from(10)); + test_i256_from_u256(BaseU256::MAX / 2); + } + + #[test] + fn test_i256_try_from_u256_error() { + test_i256_from_u256_error(BaseU256::MAX - 1); + test_i256_from_u256_error(BaseU256::MAX / 2 + 1); + test_i256_from_u256_error(BaseU256::MAX); + } + + #[test] + fn test_i256_max_min() { + assert_eq!( + I256::max().to_string(), + "57896044618658097711785492504343953926634992332820282019728792003956564819967" + ); + assert_eq!( + I256::min().to_string(), + "-57896044618658097711785492504343953926634992332820282019728792003956564819968" + ); + } +} diff --git a/rust/tw_number/src/lib.rs b/rust/tw_number/src/lib.rs new file mode 100644 index 00000000000..13e6bb25ee6 --- /dev/null +++ b/rust/tw_number/src/lib.rs @@ -0,0 +1,51 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod i256; +mod sign; +mod u256; + +pub use i256::I256; +pub use sign::Sign; +pub use u256::U256; + +pub type NumberResult = Result; + +#[derive(Debug, PartialEq)] +pub enum NumberError { + IntegerOverflow, + InvalidBinaryRepresentation, + InvalidStringRepresentation, + Overflow, +} + +#[cfg(feature = "serde")] +pub(crate) mod serde_common { + use crate::NumberError; + use serde::de::Error as DeError; + use serde::{Deserialize, Deserializer}; + use std::str::FromStr; + + #[derive(Deserialize)] + #[serde(untagged)] + enum NumOrStr { + Num(Num), + Str(String), + } + + pub(crate) fn from_num_or_decimal_str<'de, T, Num, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: From + FromStr, + Num: Deserialize<'de>, + { + let num_or_str: NumOrStr = NumOrStr::deserialize(deserializer)?; + match num_or_str { + NumOrStr::Num(num) => Ok(T::from(num)), + NumOrStr::Str(s) => T::from_str(&s).map_err(|e| DeError::custom(format!("{e:?}"))), + } + } +} diff --git a/rust/tw_number/src/sign.rs b/rust/tw_number/src/sign.rs new file mode 100644 index 00000000000..438742ee06c --- /dev/null +++ b/rust/tw_number/src/sign.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::fmt; + +/// Represents a sign of an integer. +pub enum Sign { + Positive, + Negative, +} + +impl Sign { + /// Returns whether the sign is positive. + #[inline] + pub fn is_positive(&self) -> bool { + matches!(self, Sign::Positive) + } + + /// Returns whether the sign is negative. + #[inline] + pub fn is_negative(&self) -> bool { + !self.is_positive() + } + + /// Returns the sign character. + #[inline] + pub fn as_char(&self) -> char { + match self { + Sign::Positive => '+', + Sign::Negative => '-', + } + } +} + +impl fmt::Display for Sign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match (self, f.sign_plus()) { + (Self::Positive, false) => Ok(()), + _ => write!(f, "{}", self.as_char()), + } + } +} diff --git a/rust/tw_number/src/u256.rs b/rust/tw_number/src/u256.rs new file mode 100644 index 00000000000..049fbb0b95b --- /dev/null +++ b/rust/tw_number/src/u256.rs @@ -0,0 +1,264 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{NumberError, NumberResult}; +use std::borrow::Cow; +use std::fmt; +use std::fmt::Formatter; +use std::ops::Add; +use std::str::FromStr; +use tw_hash::H256; +use tw_memory::Data; + +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct U256(pub(crate) primitive_types::U256); + +impl From for U256 { + #[inline] + fn from(num: primitive_types::U256) -> Self { + U256(num) + } +} + +impl From for primitive_types::U256 { + #[inline] + fn from(num: U256) -> Self { + num.0 + } +} + +// cbindgen:ignore +impl U256 { + pub const WORDS_COUNT: usize = 4; + pub const BYTES: usize = U256::WORDS_COUNT * 8; + pub const BITS: usize = 256; + pub const MAX: U256 = U256(primitive_types::U256::MAX); + + #[inline] + pub fn zero() -> U256 { + U256::default() + } + + #[inline] + pub fn from_little_endian(data: H256) -> U256 { + let inner = primitive_types::U256::from_little_endian(data.as_slice()); + U256::from(inner) + } + + #[inline] + pub fn from_big_endian(data: H256) -> U256 { + let inner = primitive_types::U256::from_big_endian(data.as_slice()); + U256::from(inner) + } + + #[inline] + pub fn from_big_endian_slice(data: &[u8]) -> NumberResult { + if data.len() > Self::BYTES { + return Err(NumberError::InvalidBinaryRepresentation); + } + let inner = primitive_types::U256::from_big_endian(data); + Ok(U256::from(inner)) + } + + #[inline] + pub fn from_little_endian_slice(data: &[u8]) -> NumberResult { + if data.len() > Self::BYTES { + return Err(NumberError::InvalidBinaryRepresentation); + } + let inner = primitive_types::U256::from_little_endian(data); + Ok(U256::from(inner)) + } + + #[inline] + pub fn to_little_endian(&self) -> H256 { + let mut res = H256::default(); + self.0.to_little_endian(res.as_mut_slice()); + res + } + + pub fn to_little_endian_compact(&self) -> Data { + let leading_zero_bytes = self.leading_zero_bytes(); + let zero_bytes_start_at = U256::BYTES - leading_zero_bytes; + let bytes = self.to_little_endian(); + bytes[..zero_bytes_start_at].to_vec() + } + + #[inline] + pub fn to_big_endian(&self) -> H256 { + let mut res = H256::default(); + self.0.to_big_endian(res.as_mut_slice()); + res + } + + pub fn to_big_endian_compact(&self) -> Data { + let leading_zero_bytes = self.leading_zero_bytes(); + let bytes = self.to_big_endian(); + bytes[leading_zero_bytes..].to_vec() + } + + pub fn to_big_endian_compact_min_len(&self, min_len: usize) -> Data { + let bytes = self.to_big_endian_compact(); + + if min_len > bytes.len() { + let mut output = vec![0; min_len]; + let starts_at = min_len - bytes.len(); + output[starts_at..min_len].copy_from_slice(bytes.as_slice()); + return output; + } + + bytes + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + #[inline] + pub fn bits(&self) -> usize { + self.0.bits() + } + + #[inline] + pub fn low_u8(&self) -> u8 { + let lowest_byte_idx = 0; + self.0.byte(lowest_byte_idx) + } + + /// Checked addition. Returns `NumberError::IntegerOverflow` if overflow occurred. + #[inline] + pub fn checked_add(&self, rhs: T) -> NumberResult + where + T: Into, + { + let rhs = rhs.into(); + self.0 + .checked_add(rhs) + .map(U256) + .ok_or(NumberError::IntegerOverflow) + } + + #[inline] + fn leading_zero_bytes(&self) -> usize { + U256::BYTES - (self.0.bits() + 7) / 8 + } +} + +#[cfg(feature = "helpers")] +impl U256 { + #[inline] + pub fn encode_be_compact(num: u64) -> Cow<'static, [u8]> { + U256::from(num).to_big_endian_compact().into() + } +} + +impl FromStr for U256 { + type Err = NumberError; + + #[inline] + fn from_str(s: &str) -> Result { + let inner = if s.starts_with("0x") { + primitive_types::U256::from_str(s) + .map_err(|_| NumberError::InvalidStringRepresentation)? + } else { + primitive_types::U256::from_dec_str(s) + .map_err(|_| NumberError::InvalidStringRepresentation)? + }; + Ok(U256(inner)) + } +} + +impl fmt::Display for U256 { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Implements `Add`, `Add` etc for [U256]. +impl Add for U256 +where + T: Into, +{ + type Output = U256; + + #[inline] + fn add(self, rhs: T) -> Self::Output { + U256(self.0 + rhs.into()) + } +} + +#[cfg(feature = "serde")] +mod impl_serde { + use super::U256; + use serde::de::Error as DeError; + use serde::{Deserialize, Deserializer, Serializer}; + use std::str::FromStr; + + impl U256 { + pub fn as_decimal_str(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } + + pub fn from_decimal_str<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: &str = Deserialize::deserialize(deserializer)?; + U256::from_str(s).map_err(|e| DeError::custom(format!("{e:?}"))) + } + + pub fn from_u64_or_decimal_str<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + crate::serde_common::from_num_or_decimal_str::<'de, U256, u64, D>(deserializer) + } + } +} + +macro_rules! impl_map_from { + ($u:ty, $int:ty) => { + impl From<$int> for $u { + fn from(int: $int) -> $u { + <$u>::from(primitive_types::U256::from(int)) + } + } + + impl TryFrom<$u> for $int { + type Error = NumberError; + + fn try_from(u: $u) -> Result { + <$int>::try_from(u.0).map_err(|_| NumberError::IntegerOverflow) + } + } + }; +} + +impl_map_from!(U256, u8); +impl_map_from!(U256, u16); +impl_map_from!(U256, u32); +impl_map_from!(U256, u64); +impl_map_from!(U256, usize); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_u256_from_str() { + assert_eq!(U256::from_str("0x0"), Ok(U256::zero())); + assert_eq!(U256::from_str("0x00"), Ok(U256::zero())); + assert_eq!(U256::from_str("0x01"), Ok(U256::from(1_u64))); + assert_eq!(U256::from_str("0x2"), Ok(U256::from(2_u64))); + assert_eq!(U256::from_str("0x0000a"), Ok(U256::from(10_u64))); + assert_eq!(U256::from_str("4"), Ok(U256::from(4_u64))); + } +} diff --git a/rust/tw_number/tests/u256.rs b/rust/tw_number/tests/u256.rs new file mode 100644 index 00000000000..dd8c2dbaafc --- /dev/null +++ b/rust/tw_number/tests/u256.rs @@ -0,0 +1,208 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; +use tw_encoding::hex; +use tw_encoding::hex::ToHex; +use tw_hash::H256; +use tw_number::U256; + +struct TestCase { + num_str: &'static str, + little_endian_compact: &'static str, + little_endian: &'static str, + big_endian_compact: &'static str, + big_endian: &'static str, +} + +const TEST_CASES: &[TestCase] = &[ + TestCase { + num_str: "0", + little_endian_compact: "", + little_endian: "0000000000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "", + big_endian: "0000000000000000000000000000000000000000000000000000000000000000", + }, + TestCase { + num_str: "1", + little_endian_compact: "01", + little_endian: "0100000000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "01", + big_endian: "0000000000000000000000000000000000000000000000000000000000000001", + }, + TestCase { + num_str: "7", + little_endian_compact: "07", + little_endian: "0700000000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "07", + big_endian: "0000000000000000000000000000000000000000000000000000000000000007", + }, + TestCase { + num_str: "100", + little_endian_compact: "64", + little_endian: "6400000000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "64", + big_endian: "0000000000000000000000000000000000000000000000000000000000000064", + }, + TestCase { + num_str: "255", + little_endian_compact: "ff", + little_endian: "ff00000000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "ff", + big_endian: "00000000000000000000000000000000000000000000000000000000000000ff", + }, + TestCase { + num_str: "256", + little_endian_compact: "0001", + little_endian: "0001000000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "0100", + big_endian: "0000000000000000000000000000000000000000000000000000000000000100", + }, + TestCase { + num_str: "1000000", + little_endian_compact: "40420f", + little_endian: "40420f0000000000000000000000000000000000000000000000000000000000", + big_endian_compact: "0f4240", + big_endian: "00000000000000000000000000000000000000000000000000000000000f4240", + }, + TestCase { + num_str: "20000000000", + little_endian_compact: "00c817a804", + little_endian: "00c817a804000000000000000000000000000000000000000000000000000000", + big_endian_compact: "04a817c800", + big_endian: "00000000000000000000000000000000000000000000000000000004a817c800", + }, + TestCase { + num_str: "1311768467463790320", + little_endian_compact: "f0debc9a78563412", + little_endian: "f0debc9a78563412000000000000000000000000000000000000000000000000", + big_endian_compact: "123456789abcdef0", + big_endian: "000000000000000000000000000000000000000000000000123456789abcdef0", + }, + TestCase { + num_str: "94522879700260683142460330790866415", + little_endian_compact: "efcdab89674523f1debc9a78563412", + little_endian: "efcdab89674523f1debc9a785634120000000000000000000000000000000000", + big_endian_compact: "123456789abcdef123456789abcdef", + big_endian: "0000000000000000000000000000000000123456789abcdef123456789abcdef", + }, + TestCase { + num_str: "91343852333181432387730302044767688728495783936", + little_endian_compact: "0000000000000000000000000000000000000010", + little_endian: "0000000000000000000000000000000000000010000000000000000000000000", + big_endian_compact: "1000000000000000000000000000000000000000", + big_endian: "0000000000000000000000001000000000000000000000000000000000000000", + }, + TestCase { + num_str: "18515461264373351373200002665853028612451056578545711640558177340181847433846", + little_endian_compact: "766263aa200659e163ff713c5da1e1036086677553fe9521bc39d90b3461ef28", + little_endian: "766263aa200659e163ff713c5da1e1036086677553fe9521bc39d90b3461ef28", + big_endian_compact: "28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276", + big_endian: "28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276", + }, +]; + +#[test] +fn test_u256_big_endian_compact() { + for test in TEST_CASES { + let bytes = hex::decode(test.big_endian_compact).unwrap(); + let num = U256::from_big_endian_slice(bytes.as_slice()).unwrap(); + + assert_eq!(num.to_string(), test.num_str); + + let actual_hex_encoded = hex::encode(num.to_big_endian_compact(), false); + assert_eq!(actual_hex_encoded, test.big_endian_compact); + } +} + +#[test] +fn test_u256_big_endian() { + for test in TEST_CASES { + let bytes = H256::from_str(test.big_endian).unwrap(); + let num = U256::from_big_endian(bytes); + + assert_eq!(num.to_string(), test.num_str); + + let actual_bytes = num.to_big_endian(); + assert_eq!(actual_bytes, bytes); + } +} + +#[test] +fn test_u256_little_endian_compact() { + for test in TEST_CASES { + let bytes = hex::decode(test.little_endian_compact).unwrap(); + let num = U256::from_little_endian_slice(bytes.as_slice()).unwrap(); + + assert_eq!(num.to_string(), test.num_str); + + let actual_hex_encoded = hex::encode(num.to_little_endian_compact(), false); + assert_eq!(actual_hex_encoded, test.little_endian_compact); + } +} + +#[test] +fn test_u256_little_endian() { + for test in TEST_CASES { + let bytes = H256::from_str(test.little_endian).unwrap(); + let num = U256::from_little_endian(bytes); + + assert_eq!(num.to_string(), test.num_str); + + let actual_bytes = num.to_little_endian(); + assert_eq!(actual_bytes, bytes); + } +} + +#[test] +fn test_u256_from_str() { + for test in TEST_CASES { + let bytes = H256::from_str(test.little_endian).unwrap(); + let num = U256::from_little_endian(bytes); + + let actual = U256::from_str(test.num_str).unwrap(); + assert_eq!(num, actual); + } +} + +#[test] +fn test_u256_big_endian_min_len() { + // 0x0100 + let num = U256::from(256_u64); + + let num_0 = num.to_big_endian_compact_min_len(0); + assert_eq!(hex::encode(num_0, false), "0100"); + + let num_1 = num.to_big_endian_compact_min_len(1); + assert_eq!(hex::encode(num_1, false), "0100"); + + let num_2 = num.to_big_endian_compact_min_len(2); + assert_eq!(hex::encode(num_2, false), "0100"); + + let num_3 = num.to_big_endian_compact_min_len(3); + assert_eq!(num_3.to_hex(), "000100"); + + let num_20 = num.to_big_endian_compact_min_len(20); + assert_eq!(num_20.to_hex(), "0000000000000000000000000000000000000100"); + + let num_32 = num.to_big_endian_compact_min_len(32); + assert_eq!( + num_32.to_hex(), + "0000000000000000000000000000000000000000000000000000000000000100" + ); + + let num_33 = num.to_big_endian_compact_min_len(33); + assert_eq!( + num_33.to_hex(), + "000000000000000000000000000000000000000000000000000000000000000100" + ); + + let num_64 = num.to_big_endian_compact_min_len(64); + assert_eq!( + num_64.to_hex(), + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100" + ); +} diff --git a/rust/tw_proto/Cargo.toml b/rust/tw_proto/Cargo.toml index 22179b761bc..73be2eecd76 100644 --- a/rust/tw_proto/Cargo.toml +++ b/rust/tw_proto/Cargo.toml @@ -3,7 +3,12 @@ name = "tw_proto" version = "0.1.0" edition = "2021" +[features] +fuzz = ["arbitrary"] + [dependencies] +# Enable in fuzz tests only! +arbitrary = { version = "1", features = ["derive"], optional = true } quick-protobuf = "0.8.1" tw_encoding = { path = "../tw_encoding" } tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_proto/build.rs b/rust/tw_proto/build.rs index 1ba71bd5a11..b3e60df9a68 100644 --- a/rust/tw_proto/build.rs +++ b/rust/tw_proto/build.rs @@ -6,6 +6,8 @@ use pb_rs::types::FileDescriptor; use pb_rs::ConfigBuilder; +#[cfg(feature = "fuzz")] +use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use std::{env, fs}; @@ -50,4 +52,54 @@ fn main() { .expect("Error configuring pb-rs builder") .build(); FileDescriptor::run(&out_protos).expect("Error generating proto files"); + + #[cfg(feature = "fuzz")] + add_custom_derives(&out_dir, &["arbitrary::Arbitrary"]) + .expect("Error on adding 'arbitrary::Arbitrary' derive"); +} + +/// Unfortunately, `pb-rs` does not provide a proper support of custom derives. +/// [`ConfigBuilder::custom_struct_derive`] adds the derive macroses for structs only, +/// however they should be added for enums too. +/// Issues: https://github.com/tafia/quick-protobuf/issues/195, https://github.com/tafia/quick-protobuf/issues/212 +#[cfg(feature = "fuzz")] +fn add_custom_derives(out_dir: &Path, custom_derives: &[&str]) -> io::Result<()> { + // Debug is derived for all generated types. + let pattern = "#[derive(Debug"; + let replace_with = format!("#[derive(Debug, {}", custom_derives.join(", ")); + + let tw_dir = out_dir.join("TW"); + for blockchain_dir in tw_dir.read_dir()? { + let blockchain_dir = blockchain_dir?.path(); + + // There can be `mod.rs` files. Skip them. + if !blockchain_dir.is_dir() { + continue; + } + + let blockchain_proto = blockchain_dir.join("Proto.rs"); + replace_proto_content(&blockchain_proto, pattern, &replace_with)?; + } + + Ok(()) +} + +#[cfg(feature = "fuzz")] +fn replace_proto_content(path_to_file: &Path, pattern: &str, replace_with: &str) -> io::Result<()> { + let proto_content = { + let mut proto_file = fs::File::open(path_to_file)?; + + let mut proto_content = String::new(); + proto_file.read_to_string(&mut proto_content)?; + + proto_content + }; + + let upgraded_proto_content = proto_content.replace(pattern, &replace_with); + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(&path_to_file)?; + file.write_all(upgraded_proto_content.as_bytes()) } diff --git a/rust/tw_proto/src/common/google/mod.rs b/rust/tw_proto/src/common/google/mod.rs new file mode 100644 index 00000000000..a11a0995e85 --- /dev/null +++ b/rust/tw_proto/src/common/google/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod protobuf; diff --git a/rust/tw_proto/src/common/google/protobuf/any.proto b/rust/tw_proto/src/common/google/protobuf/any.proto new file mode 100644 index 00000000000..c7aa5a0baa1 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/any.proto @@ -0,0 +1,169 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Source (any): https://github.com/protocolbuffers/protobuf/blob/8bf4fe924a924e0ee290bf883977c108fc12f28d/src/google/protobuf/any.proto +// To recompile the file use the following command inside `wallet-core` directory: +// ``` +// cargo install pb-rs +// pb-rs --dont_use_cow --single-mod --output_directory rust/tw_proto/common_proto/google/protobuf/ rust/tw_proto/common_proto/google/protobuf/any.proto +// ``` + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/rust/tw_proto/src/common/google/protobuf/any.rs b/rust/tw_proto/src/common/google/protobuf/any.rs new file mode 100644 index 00000000000..743b9b93b94 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/any.rs @@ -0,0 +1,51 @@ +// Automatically generated rust module for 'any.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Any { + pub type_url: String, + pub value: Vec, +} + +impl<'a> MessageRead<'a> for Any { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.type_url = r.read_string(bytes)?.to_owned(), + Ok(18) => msg.value = r.read_bytes(bytes)?.to_owned(), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Any { + fn get_size(&self) -> usize { + 0 + + if self.type_url == String::default() { 0 } else { 1 + sizeof_len((&self.type_url).len()) } + + if self.value.is_empty() { 0 } else { 1 + sizeof_len((&self.value).len()) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.type_url != String::default() { w.write_with_tag(10, |w| w.write_string(&**&self.type_url))?; } + if !self.value.is_empty() { w.write_with_tag(18, |w| w.write_bytes(&**&self.value))?; } + Ok(()) + } +} + diff --git a/rust/tw_proto/src/common/google/protobuf/mod.rs b/rust/tw_proto/src/common/google/protobuf/mod.rs new file mode 100644 index 00000000000..3f4d90a7f08 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod any; +mod timestamp; + +pub use any::*; +pub use timestamp::*; diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.proto b/rust/tw_proto/src/common/google/protobuf/timestamp.proto new file mode 100644 index 00000000000..353b1632497 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.proto @@ -0,0 +1,151 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Source: https://github.com/protocolbuffers/protobuf/blob/538a8e9a0d90b0bd8aea7b10f8e17ba76585b2e8/src/google/protobuf/timestamp.proto +// To recompile the file use the following command inside `wallet-core` directory: +// ``` +// cargo install pb-rs +// pb-rs --dont_use_cow --single-mod --output_directory rust/tw_proto/common_proto/google/protobuf/ rust/tw_proto/common_proto/google/protobuf/any.proto +// ``` + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.rs b/rust/tw_proto/src/common/google/protobuf/timestamp.rs new file mode 100644 index 00000000000..2e073dbe982 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.rs @@ -0,0 +1,51 @@ +// Automatically generated rust module for 'timestamp.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Timestamp { + pub seconds: i64, + pub nanos: i32, +} + +impl<'a> MessageRead<'a> for Timestamp { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.seconds = r.read_int64(bytes)?, + Ok(16) => msg.nanos = r.read_int32(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Timestamp { + fn get_size(&self) -> usize { + 0 + + if self.seconds == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.seconds) as u64) } + + if self.nanos == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.nanos) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.seconds != 0i64 { w.write_with_tag(8, |w| w.write_int64(*&self.seconds))?; } + if self.nanos != 0i32 { w.write_with_tag(16, |w| w.write_int32(*&self.nanos))?; } + Ok(()) + } +} + diff --git a/rust/tw_proto/src/common/mod.rs b/rust/tw_proto/src/common/mod.rs new file mode 100644 index 00000000000..972a22f676f --- /dev/null +++ b/rust/tw_proto/src/common/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod google; diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index 1b52d670b61..45eb41fac60 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -4,7 +4,11 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; +use quick_protobuf::{BytesReader, MessageInfo, Writer}; + +#[allow(non_snake_case)] +#[rustfmt::skip] +mod common; #[allow(non_snake_case)] #[rustfmt::skip] @@ -12,10 +16,11 @@ mod generated { include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); } +pub use common::google; pub use generated::TW::*; pub use quick_protobuf::{ deserialize_from_slice as deserialize_prefixed, serialize_into_vec as serialize_prefixed, - Error as ProtoError, Result as ProtoResult, + Error as ProtoError, MessageRead, MessageWrite, Result as ProtoResult, }; pub mod ffi; @@ -37,3 +42,28 @@ pub fn deserialize<'a, T: MessageRead<'a>>(data: &'a [u8]) -> ProtoResult { let mut reader = BytesReader::from_bytes(data); T::from_reader(&mut reader, data) } + +pub fn to_any(message: &T) -> google::protobuf::Any +where + T: MessageInfo + MessageWrite, +{ + let value = serialize(message).expect("Protobuf serialization should never fail"); + let type_url = format!("/{}", T::PATH); + google::protobuf::Any { type_url, value } +} + +/// There is no way to create an instance of the `NoMessage` enum as it doesn't has variants. +pub enum NoMessage {} + +impl MessageWrite for NoMessage {} + +/// `DummyMessage` has no effect on `MessageWrite` and `MessageRead`. +pub struct DummyMessage; + +impl MessageWrite for DummyMessage {} + +impl<'a> MessageRead<'a> for DummyMessage { + fn from_reader(_r: &mut BytesReader, _bytes: &'a [u8]) -> ProtoResult { + Ok(DummyMessage) + } +} diff --git a/rust/tw_proto/tests/proto_ffi_tests.rs b/rust/tw_proto/tests/proto_ffi_tests.rs index 654141268da..ce3df7c6a03 100644 --- a/rust/tw_proto/tests/proto_ffi_tests.rs +++ b/rust/tw_proto/tests/proto_ffi_tests.rs @@ -11,7 +11,7 @@ use tw_memory::ffi::c_byte_array::CByteArray; fn test_pass_eth_signing_msg_through() { let serialized = tw_encoding::hex::decode("0a0101120100220509c76524002a030130b9422a3078366231373534373465383930393463343464613938623935346565646561633439353237316430664a20608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151523812360a2a30783533323262333463383865643036393139373162663532613730343734343866306634656663383412081bc16d674ec80000").unwrap(); let actual = unsafe { - let array = CByteArray::new(serialized.clone()); + let array = CByteArray::from(serialized.clone()); tw_proto::ffi::pass_eth_signing_msg_through(array.data(), array.size()) .unwrap() .into_vec() diff --git a/rust/tw_ronin/Cargo.toml b/rust/tw_ronin/Cargo.toml new file mode 100644 index 00000000000..98a13847890 --- /dev/null +++ b/rust/tw_ronin/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_ronin" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../tw_coin_entry" } +tw_evm = { path = "../tw_evm" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_proto = { path = "../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../tw_encoding" } +tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/rust/tw_ronin/src/address.rs b/rust/tw_ronin/src/address.rs new file mode 100644 index 00000000000..e14be4225e0 --- /dev/null +++ b/rust/tw_ronin/src/address.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::AddressError; +use tw_evm::address::{Address as EthAddress, EvmAddress}; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; + +/// cbindgen:ignore +const RONIN_PREFIX: &str = "ronin:"; + +#[derive(Debug)] +pub struct Address(EthAddress); + +impl Address { + /// Initializes an address with a `secp256k1` public key. + #[inline] + pub fn with_secp256k1_pubkey(pubkey: &secp256k1::PublicKey) -> Address { + Address(EthAddress::with_secp256k1_pubkey(pubkey)) + } +} + +impl EvmAddress for Address {} + +impl From
for EthAddress { + #[inline] + fn from(addr: Address) -> Self { + addr.0 + } +} + +impl FromStr for Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + // Accept both Ronin and standard addresses. + let standard = match s.strip_prefix(RONIN_PREFIX) { + Some(ronin_no_prefix) => format!("0x{ronin_no_prefix}"), + None => s.to_string(), + }; + EthAddress::from_str(&standard).map(Address) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let standard_prefixed = self.0.to_string(); + // Strip the `0x` prefix. + let standard_no_prefix = standard_prefixed + .strip_prefix("0x") + .unwrap_or(&standard_prefixed); + write!(f, "{RONIN_PREFIX}{standard_no_prefix}") + } +} + +impl CoinAddress for Address { + #[inline] + fn data(&self) -> Data { + self.0.data() + } +} diff --git a/rust/tw_ronin/src/entry.rs b/rust/tw_ronin/src/entry.rs new file mode 100644 index 00000000000..36bb8fb96b2 --- /dev/null +++ b/rust/tw_ronin/src/entry.rs @@ -0,0 +1,109 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::ronin_context::RoninContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_evm::evm_entry::EvmEntry; +use tw_evm::modules::compiler::Compiler; +use tw_evm::modules::json_signer::EthJsonSigner; +use tw_evm::modules::message_signer::EthMessageSigner; +use tw_evm::modules::signer::Signer; +use tw_keypair::tw::PublicKey; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct RoninEntry; + +impl CoinEntry for RoninEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = EthJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = EthMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Address::with_secp256k1_pubkey(public_key)) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::::compile(input, signatures, public_keys) + } + + #[inline] + fn json_signer(&self) -> Option { + Some(EthJsonSigner::default()) + } + + #[inline] + fn message_signer(&self) -> Option { + Some(EthMessageSigner) + } +} + +impl EvmEntry for RoninEntry { + type Context = RoninContext; +} diff --git a/rust/tw_ronin/src/lib.rs b/rust/tw_ronin/src/lib.rs new file mode 100644 index 00000000000..132d9b9b730 --- /dev/null +++ b/rust/tw_ronin/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod entry; +pub mod ronin_context; diff --git a/rust/tw_ronin/src/ronin_context.rs b/rust/tw_ronin/src/ronin_context.rs new file mode 100644 index 00000000000..0799f42e3f5 --- /dev/null +++ b/rust/tw_ronin/src/ronin_context.rs @@ -0,0 +1,15 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use tw_evm::evm_context::EvmContext; + +#[derive(Default)] +pub struct RoninContext; + +impl EvmContext for RoninContext { + type Address = Address; +} diff --git a/rust/tw_ronin/tests/address.rs b/rust/tw_ronin/tests/address.rs new file mode 100644 index 00000000000..0e62310ddb0 --- /dev/null +++ b/rust/tw_ronin/tests/address.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; +use tw_ronin::address::Address; + +#[test] +fn test_ronin_address_valid() { + let normalized = "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab"; + let valid = [ + normalized, + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835ab", + "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", + "ronin:0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", + ]; + + for test in valid { + let addr = Address::from_str(test).unwrap(); + assert_eq!(addr.to_string(), normalized); + } +} + +#[test] +fn test_ronin_address_invalid() { + let invalid = [ + "EC49280228b0D05Aa8e8b756503254e1eE7835ab", // no prefix + "ec49280228b0d05aa8e8b756503254e1ee7835ab", // no prefix + "roni:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "ronin=EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "0xronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835", // too short + "", // empty + ]; + + for test in invalid { + Address::from_str(test).unwrap_err(); + } +} diff --git a/rust/tw_ronin/tests/compiler.rs b/rust/tw_ronin/tests/compiler.rs new file mode 100644 index 00000000000..5c99e67b419 --- /dev/null +++ b/rust/tw_ronin/tests/compiler.rs @@ -0,0 +1,79 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::tw; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; + +#[test] +fn test_ronin_preimage_hashes_and_compile() { + let coin = TestCoinContext::default(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + let input = Proto::SigningInput { + nonce: U256::encode_be_compact(11), + chain_id: U256::encode_be_compact(1), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "ronin:3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + ..Proto::SigningInput::default() + }; + + let input_data = serialize(&input).unwrap(); + + let res = RoninEntry + .preimage_hashes(&coin, &input_data) + .expect("!preimage_hashes"); + let preimage: CompilerProto::PreSigningOutput = + deserialize(res.as_slice()).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + preimage.data_hash.to_hex(), + "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217" + ); + + // Simulate signature, normally obtained from signature server + let public_key = secp256k1::PublicKey::try_from("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a").unwrap(); + let public_key = tw::PublicKey::Secp256k1Extended(public_key); + let signature = "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900".decode_hex().unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public_key.verify(&signature, &preimage.data_hash)); + + // Step 3: Compile transaction info + let output_data = RoninEntry + .compile( + &coin, + &input_data, + vec![signature], + vec![public_key.to_bytes()], + ) + .expect("!compile"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + assert_eq!(output.encoded.to_hex(), expected_encoded); +} diff --git a/rust/tw_ronin/tests/rlp.rs b/rust/tw_ronin/tests/rlp.rs new file mode 100644 index 00000000000..d4d88b6dbb0 --- /dev/null +++ b/rust/tw_ronin/tests/rlp.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::ToHex; +use tw_evm::evm_entry::EvmEntryExt; +use tw_proto::EthereumRlp::Proto as RlpProto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +#[test] +fn test_rlp_encode_ronin_address() { + let ronin_addr = "ronin:6b175474e89094c44da98b954eedeac495271d0f"; + let input = RlpProto::EncodingInput { + item: Some(RlpProto::RlpItem { + item: Item::address(Cow::from(ronin_addr)), + }), + }; + let input_data = serialize(&input).unwrap(); + let output_data = RoninEntry.encode_rlp(&input_data).unwrap(); + let output: RlpProto::EncodingOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!( + output.encoded.to_hex(), + "946b175474e89094c44da98b954eedeac495271d0f" + ); +} diff --git a/rust/tw_ronin/tests/signer.rs b/rust/tw_ronin/tests/signer.rs new file mode 100644 index 00000000000..d68396a34b4 --- /dev/null +++ b/rust/tw_ronin/tests/signer.rs @@ -0,0 +1,70 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; + +/// https://explorer.roninchain.com/tx/0xf13a2c4421700f8782ca73eaf16bb8baf82bcf093e23570a1ff062cdd8dbf6c3 +#[test] +fn test_ronin_signing() { + let coin = TestCoinContext::default(); + + let private = "0x4646464646464646464646464646464646464646464646464646464646464646" + .decode_hex() + .unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(276_447), + data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(2020), + nonce: U256::encode_be_compact(0), + gas_price: U256::encode_be_compact(1_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "ronin:c36edf48e21cf395b206352a1819de658fd7f988".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + let input_data = serialize(&input).unwrap(); + + let output_data = RoninEntry.sign(&coin, &input_data).expect("!sign"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7"; + assert_eq!(output.encoded.to_hex(), expected); +} + +#[test] +fn test_sign_json() { + let coin = TestCoinContext::default(); + + let input_json = r#"{"chainId":"B+Q=","nonce":"AA==","gasPrice":"O5rKAA==","gasLimit":"Ugg=","toAddress":"ronin:c36edf48e21cf395b206352a1819de658fd7f988","privateKey":"RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkY=","transaction":{"transfer":{"amount":"BDff"}}}"#; + let private_key = "0x4646464646464646464646464646464646464646464646464646464646464646" + .decode_hex() + .unwrap(); + + RoninEntry + .sign_json(&coin, input_json, private_key) + .expect_err("'EthEntry::sign_json' is not supported yet"); + + // Expected result - "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7" +} diff --git a/rust/tw_starknet/Cargo.toml b/rust/tw_starknet/Cargo.toml deleted file mode 100644 index 8aa39b7880c..00000000000 --- a/rust/tw_starknet/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tw_starknet" -version = "0.1.0" -edition = "2021" - -[dependencies] -hex = "0.4.3" -starknet-crypto = "0.5.1" -starknet-ff = "0.3.3" -tw_encoding = { path = "../tw_encoding" } -tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_starknet/src/ffi.rs b/rust/tw_starknet/src/ffi.rs deleted file mode 100644 index 79f243b52fe..00000000000 --- a/rust/tw_starknet/src/ffi.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#![allow(clippy::missing_safety_doc)] - -use crate::key_pair; -use std::ffi::{c_char, CStr}; -use tw_memory::ffi::c_result::{CBoolResult, CStrResult, ErrorCode}; - -#[repr(C)] -pub enum CStarknetCode { - Ok = 0, - InvalidInput = 1, - PrivKeyError = 2, -} - -impl From for ErrorCode { - fn from(code: CStarknetCode) -> Self { - code as ErrorCode - } -} - -impl From for CStarknetCode { - fn from(_: key_pair::StarknetKeyPairError) -> Self { - CStarknetCode::PrivKeyError - } -} - -/// Returns a StarkNet pubkey corresponding to the given `priv_key`. -/// \param priv_key - *non-null* C-compatible, nul-terminated string. -/// \return *non-null* C-compatible, nul-terminated string. -#[no_mangle] -pub unsafe extern "C" fn starknet_pubkey_from_private(priv_key: *const c_char) -> CStrResult { - let priv_key = match CStr::from_ptr(priv_key).to_str() { - Ok(priv_key) => priv_key, - Err(_) => return CStrResult::error(CStarknetCode::InvalidInput), - }; - key_pair::starknet_pubkey_from_private(priv_key) - .map(tw_memory::c_string_standalone) - .map_err(CStarknetCode::from) - .into() -} - -/// Signs the input `hash` with the given `priv_key` and returns a signature compatible with StarkNet. -/// \param priv_key *non-null* C-compatible, nul-terminated string. -/// \param hash *non-null* C-compatible, nul-terminated string. -/// \return *non-null* C-compatible, nul-terminated string. -#[no_mangle] -pub unsafe extern "C" fn starknet_sign(priv_key: *const c_char, hash: *const c_char) -> CStrResult { - let priv_key = match CStr::from_ptr(priv_key).to_str() { - Ok(priv_key) => priv_key, - Err(_) => return CStrResult::error(CStarknetCode::InvalidInput), - }; - let hash = match CStr::from_ptr(hash).to_str() { - Ok(hash) => hash, - Err(_) => return CStrResult::error(CStarknetCode::InvalidInput), - }; - key_pair::starknet_sign(priv_key, hash) - .map(tw_memory::c_string_standalone) - .map_err(CStarknetCode::from) - .into() -} - -/// Verifies if the given signature (`r` and `s`) is valid over a message `hash` given a StarkNet `pub_key`. -/// \param pub_key *non-null* C-compatible, nul-terminated string. -/// \param hash *non-null* C-compatible, nul-terminated string. -/// \param r *non-null* C-compatible, nul-terminated string. -/// \param s *non-null* C-compatible, nul-terminated string. -/// \return true if the signature is valid. -#[no_mangle] -pub unsafe extern "C" fn starknet_verify( - pub_key: *const c_char, - hash: *const c_char, - r: *const c_char, - s: *const c_char, -) -> CBoolResult { - macro_rules! parse_c_str { - ($s:expr) => { - match CStr::from_ptr($s).to_str() { - Ok(s) => s, - Err(_) => return CBoolResult::error(CStarknetCode::InvalidInput), - } - }; - } - - let pub_key = parse_c_str!(pub_key); - let hash = parse_c_str!(hash); - let r = parse_c_str!(r); - let s = parse_c_str!(s); - - key_pair::starknet_verify(pub_key, hash, r, s) - .map_err(CStarknetCode::from) - .into() -} diff --git a/rust/tw_starknet/src/key_pair.rs b/rust/tw_starknet/src/key_pair.rs deleted file mode 100644 index 1d63b6f6f56..00000000000 --- a/rust/tw_starknet/src/key_pair.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -use starknet_crypto::{get_public_key, rfc6979_generate_k, sign, verify, SignError, Signature}; -use starknet_ff::{FieldElement, FromByteArrayError}; -use tw_encoding::hex::{self as tw_hex, FromHexError}; - -/// The maximum number of attempts to sign a message. -/// As the number is coming from `rfc6979_generate_k` so the probability is lower. -const SIGN_RETRIES: usize = 5; - -pub type Result = std::result::Result; - -#[derive(Debug)] -pub enum StarknetKeyPairError { - HexError(FromHexError), - ByteArrayError(FromByteArrayError), - InvalidLength, - ErrorSigning, -} - -impl From for StarknetKeyPairError { - fn from(err: FromHexError) -> Self { - StarknetKeyPairError::HexError(err) - } -} - -impl From for StarknetKeyPairError { - fn from(err: FromByteArrayError) -> Self { - StarknetKeyPairError::ByteArrayError(err) - } -} - -pub fn starknet_pubkey_from_private(priv_key: &str) -> Result { - let private_key = field_element_from_be_hex(priv_key)?; - Ok(format!("{:#02x}", get_public_key(&private_key))) -} - -pub fn starknet_sign(priv_key: &str, hash: &str) -> Result { - let private_key = field_element_from_be_hex(priv_key)?; - let hash_field = field_element_from_be_hex(hash)?; - let signature = ecdsa_sign(&private_key, &hash_field)?; - Ok(signature.to_string()) -} - -pub fn starknet_verify(pub_key: &str, hash: &str, r: &str, s: &str) -> Result { - let pub_key = field_element_from_be_hex(pub_key)?; - let hash = field_element_from_be_hex(hash)?; - let r = field_element_from_be_hex(r)?; - let s = field_element_from_be_hex(s)?; - - Ok(verify(&pub_key, &hash, &r, &s).unwrap_or_default()) -} - -fn field_element_from_be_hex(hex: &str) -> Result { - let decoded = tw_hex::decode(hex)?; - if decoded.len() > 32 { - return Err(StarknetKeyPairError::InvalidLength); - } - - let mut buffer = [0u8; 32]; - buffer[(32 - decoded.len())..].copy_from_slice(&decoded[..]); - - FieldElement::from_bytes_be(&buffer).map_err(StarknetKeyPairError::from) -} - -/// `starknet-core` depends on an out-dated `starknet-crypto` crate. -/// We need to reimplement the same but using the latest `starknet-crypto` version. -/// https://github.com/xJonathanLEI/starknet-rs/blob/0c78b365c2a7a7d4138553cba42fa69d695aa73d/starknet-core/src/crypto.rs#L34-L59 -pub fn ecdsa_sign(private_key: &FieldElement, message_hash: &FieldElement) -> Result { - // Seed-retry logic ported from `cairo-lang` - let mut seed = None; - for _ in 0..SIGN_RETRIES { - let k = rfc6979_generate_k(message_hash, private_key, seed.as_ref()); - - match sign(private_key, message_hash, &k) { - Ok(sig) => { - return Ok(Signature { r: sig.r, s: sig.s }); - }, - Err(SignError::InvalidMessageHash) => return Err(StarknetKeyPairError::ErrorSigning), - Err(SignError::InvalidK) => { - // Bump seed and retry - seed = match seed { - Some(prev_seed) => Some(prev_seed + FieldElement::ONE), - None => Some(FieldElement::ONE), - }; - }, - }; - } - Err(StarknetKeyPairError::ErrorSigning) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_starknet_sign_invalid_k() { - let private = "0000000000000000000000000000000000000000000000000000000000000000"; - let hash = "0000000000000000000000000000000000000000000000000000000000000000"; - let err = starknet_sign(private, hash).expect_err("Retry limit expected"); - assert!(matches!(err, StarknetKeyPairError::ErrorSigning)); - } -} diff --git a/rust/tw_starknet/tests/starknet_ffi_tests.rs b/rust/tw_starknet/tests/starknet_ffi_tests.rs deleted file mode 100644 index 958a08847fe..00000000000 --- a/rust/tw_starknet/tests/starknet_ffi_tests.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::ffi::{c_char, CString}; -use tw_memory::c_string_standalone; -use tw_memory::ffi::c_result::OK_CODE; -use tw_memory::ffi::free_string; -use tw_starknet::ffi::{ - starknet_pubkey_from_private, starknet_sign, starknet_verify, CStarknetCode, -}; - -#[test] -fn test_starknet_pubkey_from_private() { - let priv_key_hex = - c_string_standalone("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe"); - let pub_key_raw = unsafe { starknet_pubkey_from_private(priv_key_hex) }; - assert_eq!(pub_key_raw.code, OK_CODE); - let actual = unsafe { - CString::from_raw(pub_key_raw.result as *mut c_char) - .into_string() - .unwrap() - }; - assert_eq!( - actual, - "0x2a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd" - ); - - unsafe { free_string(priv_key_hex) }; -} - -#[test] -fn test_starknet_pubkey_from_private_invalid() { - let priv_key_raw = - c_string_standalone("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afx"); - let pub_key_raw = unsafe { starknet_pubkey_from_private(priv_key_raw) }; - assert_eq!(pub_key_raw.code, CStarknetCode::PrivKeyError as i32); - assert!(pub_key_raw.result.is_null()); - - // Non-null terminated string. - let invalid_data = vec![159, 146, 150]; - let invalid_priv_key_ptr = invalid_data.as_ptr() as *const c_char; - - let pub_key_raw = unsafe { starknet_pubkey_from_private(invalid_priv_key_ptr) }; - assert_eq!(pub_key_raw.code, CStarknetCode::InvalidInput as i32); - assert!(pub_key_raw.result.is_null()); - - unsafe { free_string(priv_key_raw) }; -} - -#[test] -fn test_starknet_sign() { - let priv_key_raw = - c_string_standalone("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); - let digest_raw = - c_string_standalone("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - - let signature = unsafe { starknet_sign(priv_key_raw, digest_raw) }; - assert_eq!(signature.code, OK_CODE); - let actual = unsafe { CString::from_raw(signature.result.cast_mut()) } - .into_string() - .unwrap(); - let expected = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; - assert_eq!(actual, expected); - - unsafe { free_string(priv_key_raw) }; - unsafe { free_string(digest_raw) }; -} - -#[test] -fn test_starknet_verify() { - let pubkey_raw = - c_string_standalone("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); - let hash_raw = - c_string_standalone("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - let signature_r_raw = - c_string_standalone("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); - let signature_s_raw = - c_string_standalone("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); - - let res = unsafe { starknet_verify(pubkey_raw, hash_raw, signature_r_raw, signature_s_raw) }; - assert_eq!(res.code, OK_CODE); - assert!(res.result); - - unsafe { free_string(pubkey_raw) }; - unsafe { free_string(hash_raw) }; - unsafe { free_string(signature_r_raw) }; - unsafe { free_string(signature_s_raw) }; -} - -#[test] -fn test_starknet_verify_fail() { - let pubkey_raw = - c_string_standalone("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); - let hash_raw = - c_string_standalone("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - let signature_r_raw = - c_string_standalone("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); - let signature_s_raw = - c_string_standalone("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b"); - - let res = unsafe { starknet_verify(pubkey_raw, hash_raw, signature_r_raw, signature_s_raw) }; - assert_eq!(res.code, OK_CODE); - assert!(!res.result); - - unsafe { free_string(pubkey_raw) }; - unsafe { free_string(hash_raw) }; - unsafe { free_string(signature_r_raw) }; - unsafe { free_string(signature_s_raw) }; -} - -#[test] -fn test_starknet_private_key_invalid() { - let private_raw = - c_string_standalone("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f831590123"); - - let res = unsafe { starknet_pubkey_from_private(private_raw) }; - assert_eq!(res.code, CStarknetCode::PrivKeyError as i32); - - unsafe { free_string(private_raw) }; -} - -#[test] -fn test_starknet_private_key_zero() { - let private_raw = - c_string_standalone("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - - let res = unsafe { starknet_pubkey_from_private(private_raw) }; - assert_eq!(res.code, CStarknetCode::PrivKeyError as i32); - - unsafe { free_string(private_raw) }; -} diff --git a/rust/tw_utxo/Cargo.toml b/rust/tw_utxo/Cargo.toml new file mode 100644 index 00000000000..738f2932771 --- /dev/null +++ b/rust/tw_utxo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_utxo" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../tw_coin_entry" } +tw_keypair = { path = "../tw_keypair" } +tw_proto = { path = "../tw_proto" } +tw_memory = { path = "../tw_memory" } +tw_encoding = { path = "../tw_encoding" } +bitcoin = "0.30.1" +secp256k1 = { version = "0.27.0", features = [ "rand-std" ] } + +[dev-dependencies] +tw_encoding = { path = "../tw_encoding" } diff --git a/rust/tw_utxo/src/compiler.rs b/rust/tw_utxo/src/compiler.rs new file mode 100644 index 00000000000..776d5f24a32 --- /dev/null +++ b/rust/tw_utxo/src/compiler.rs @@ -0,0 +1,453 @@ +use crate::{Error, Result}; +use bitcoin::blockdata::locktime::absolute::{Height, LockTime, Time}; +use bitcoin::consensus::Encodable; +use bitcoin::hashes::Hash; +use bitcoin::sighash::{EcdsaSighashType, Prevouts, SighashCache, TapSighashType}; +use bitcoin::taproot::TapLeafHash; +use bitcoin::{OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness}; +use std::marker::PhantomData; +use tw_proto::Utxo::Proto::{self, SighashType}; + +type ProtoLockTimeVariant = Proto::mod_LockTime::OneOfvariant; +type ProtoSigningMethod = Proto::SigningMethod; + +pub trait UtxoContext { + type SigningInput<'a>; + type SigningOutput; + type PreSigningOutput; +} + +pub struct StandardBitcoinContext; + +impl UtxoContext for StandardBitcoinContext { + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningInput<'static>; + type PreSigningOutput = Proto::SigningInput<'static>; +} + +pub struct Compiler { + _phantom: PhantomData, +} + +impl Compiler { + #[inline] + pub fn preimage_hashes(proto: Proto::SigningInput<'_>) -> Proto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(proto) + .or_else(|err| { + std::result::Result::<_, ()>::Ok(Proto::PreSigningOutput { + error: err.into(), + ..Default::default() + }) + }) + .expect("did not convert error value") + } + + #[inline] + pub fn compile(proto: Proto::PreSerialization<'_>) -> Proto::SerializedTransaction<'static> { + Self::compile_impl(proto) + .or_else(|err| { + std::result::Result::<_, ()>::Ok(Proto::SerializedTransaction { + error: err.into(), + ..Default::default() + }) + }) + .expect("did not convert error value") + } + + fn preimage_hashes_impl( + mut proto: Proto::SigningInput<'_>, + ) -> Result> { + // TODO: Check for duplicate Txid (user error). + + // Calculate total outputs amount, based on it we can determine how many inputs to select. + let total_input: u64 = proto.inputs.iter().map(|input| input.value).sum(); + let total_output: u64 = proto.outputs.iter().map(|output| output.value).sum(); + + // Do some easy checks first. + + // Insufficient input amount. + if total_output > total_input { + return Err(Error::from(Proto::Error::Error_insufficient_inputs)); + } + + // Change scriptPubkey must be set if change output is enabled. + if !proto.disable_change_output && proto.change_script_pubkey.is_empty() { + return Err(Error::from( + Proto::Error::Error_missing_change_script_pubkey, + )); + } + + // If the input selector is InputSelector::SelectAscending, we sort the + // input first. + if let Proto::InputSelector::SelectAscending = proto.input_selector { + proto.inputs.sort_by(|a, b| a.value.cmp(&b.value)); + } + + // Unless InputSelector::UseAll is provided, we only use the necessariy + // amount of inputs to cover `total_output`. Any other input gets + // dropped. + let selected = if let Proto::InputSelector::SelectInOrder + | Proto::InputSelector::SelectAscending = proto.input_selector + { + let mut total_input = total_input; + let mut remaining = total_output; + + let selected: Vec = proto + .inputs + .into_iter() + .take_while(|input| { + if remaining == 0 { + return false; + } + + total_input += input.value; + remaining = remaining.saturating_sub(input.value); + + true + }) + .map(|input| Proto::TxIn { + txid: input.txid.to_vec().into(), + script_pubkey: input.script_pubkey.to_vec().into(), + leaf_hash: input.leaf_hash.to_vec().into(), + ..input + }) + .collect(); + + selected + } else { + // TODO: Write a function for this + proto + .inputs + .into_iter() + .map(|input| Proto::TxIn { + txid: input.txid.to_vec().into(), + script_pubkey: input.script_pubkey.to_vec().into(), + leaf_hash: input.leaf_hash.to_vec().into(), + ..input + }) + .collect() + }; + + // Update protobuf structure with selected inputs. + proto.inputs = selected.clone(); + + // Update the `total_input` amount based on the selected inputs. + let total_input: u64 = proto.inputs.iter().map(|input| input.value).sum(); + + // Calculate the total input weight projection. + let input_weight: u64 = proto.inputs.iter().map(|input| input.weight_estimate).sum(); + + // Convert Protobuf structure to `bitcoin` crate native transaction, + // used for weight/fee calculation. + let tx = convert_proto_to_tx(&proto)?; + + // Estimate of the change output weight. + let output_weight = if proto.disable_change_output { + 0 + } else { + // VarInt + script_pubkey size, rough estimate. + 1 + proto.change_script_pubkey.len() as u64 + }; + + // Calculate the full weight projection (base weight + input & output weight). + let weight_estimate = tx.weight().to_wu() + input_weight + output_weight; + let fee_estimate = (weight_estimate + 3) / 4 * proto.weight_base; + + // Check if the fee projection would make the change amount negative + // (implying insufficient input amount). + let change_amount_before_fee = total_input - total_output; + if change_amount_before_fee < fee_estimate { + return Err(Error::from(Proto::Error::Error_insufficient_inputs)); + } + + if !proto.disable_change_output { + // The amount to be returned (if enabled). + let change_amount = change_amount_before_fee - fee_estimate; + + // Update the passed on protobuf structure by adding a change output + // (return to sender) + if change_amount != 0 { + proto.outputs.push(Proto::TxOut { + value: change_amount, + script_pubkey: proto.change_script_pubkey.clone(), + }); + } + } + + // Convert *updated* Protobuf structure to `bitcoin` crate native + // transaction. + let tx = convert_proto_to_tx(&proto)?; + + let mut cache = SighashCache::new(&tx); + + let mut sighashes: Vec<(Vec, ProtoSigningMethod, Proto::SighashType)> = vec![]; + + for (index, input) in proto.inputs.iter().enumerate() { + match input.signing_method { + // Use the legacy hashing mechanism (e.g. P2SH, P2PK, P2PKH). + ProtoSigningMethod::Legacy => { + let script_pubkey = Script::from_bytes(input.script_pubkey.as_ref()); + let sighash_type = if let SighashType::UseDefault = input.sighash_type { + EcdsaSighashType::All + } else { + EcdsaSighashType::from_consensus(input.sighash_type as u32) + }; + let sighash = + cache.legacy_signature_hash(index, script_pubkey, sighash_type.to_u32())?; + + sighashes.push(( + sighash.as_byte_array().to_vec(), + ProtoSigningMethod::Legacy, + input.sighash_type, + )); + }, + // Use the Segwit hashing mechanism (e.g. P2WSH, P2WPKH). + ProtoSigningMethod::Segwit => { + let script_pubkey = ScriptBuf::from_bytes(input.script_pubkey.to_vec()); + let sighash_type = if let SighashType::UseDefault = input.sighash_type { + EcdsaSighashType::All + } else { + EcdsaSighashType::from_consensus(input.sighash_type as u32) + }; + + let sighash = cache.segwit_signature_hash( + index, + script_pubkey.as_script(), + input.value, + sighash_type, + )?; + + sighashes.push(( + sighash.as_byte_array().to_vec(), + ProtoSigningMethod::Segwit, + input.sighash_type, + )); + }, + // Use the Taproot hashing mechanism (e.g. P2TR key-path/script-path) + ProtoSigningMethod::TaprootAll => { + let leaf_hash = if input.leaf_hash.is_empty() { + None + } else { + Some(( + TapLeafHash::from_slice(input.leaf_hash.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_leaf_hash))?, + // TODO: We might want to make this configurable?. + 0xFFFFFFFF, + )) + }; + + // Note that `input.sighash_type = 0` is handled by the underlying library. + let sighash_type = TapSighashType::from_consensus_u8(input.sighash_type as u8) + .map_err(|_| Error::from(Proto::Error::Error_invalid_sighash_type))?; + + let prevouts = proto + .inputs + .iter() + .map(|i| TxOut { + value: i.value, + script_pubkey: ScriptBuf::from_bytes(i.script_pubkey.to_vec()), + }) + .collect::>(); + + let sighash = cache.taproot_signature_hash( + index, + &Prevouts::All(&prevouts), + None, + leaf_hash, + sighash_type, + )?; + + sighashes.push(( + sighash.as_byte_array().to_vec(), + ProtoSigningMethod::TaprootAll, + input.sighash_type, + )); + }, + ProtoSigningMethod::TaprootOnePrevout => { + let leaf_hash = if input.leaf_hash.is_empty() { + None + } else { + Some(( + TapLeafHash::from_slice(input.leaf_hash.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_leaf_hash))?, + // TODO: We might want to make this configurable?. + 0xFFFFFFFF, + )) + }; + + // Note that `input.sighash_type = 0` is handled by the underlying library. + let sighash_type = TapSighashType::from_consensus_u8(input.sighash_type as u8) + .map_err(|_| Error::from(Proto::Error::Error_invalid_sighash_type))?; + + let prevouts = Prevouts::One( + index, + TxOut { + value: input.value, + script_pubkey: ScriptBuf::from_bytes(input.script_pubkey.to_vec()), + }, + ); + + let sighash = cache.taproot_signature_hash( + index, + &prevouts, + None, + leaf_hash, + sighash_type, + )?; + + sighashes.push(( + sighash.as_byte_array().to_vec(), + ProtoSigningMethod::TaprootOnePrevout, + input.sighash_type, + )); + }, + } + } + + let tx = cache.into_transaction(); + // The transaction identifier, which we represent in + // non-reversed/non-network order. + let txid: Vec = tx.txid().as_byte_array().iter().copied().rev().collect(); + + Ok(Proto::PreSigningOutput { + error: Proto::Error::OK, + txid: txid.into(), + sighashes: sighashes + .into_iter() + .map(|(sighash, method, sighash_type)| Proto::Sighash { + sighash: sighash.into(), + signing_method: method, + sighash_type, + }) + .collect(), + inputs: selected, + outputs: proto + .outputs + .into_iter() + .map(|output| Proto::TxOut { + value: output.value, + script_pubkey: output.script_pubkey.to_vec().into(), + }) + .collect(), + weight_estimate, + fee_estimate, + }) + } + + fn compile_impl( + proto: Proto::PreSerialization<'_>, + ) -> Result> { + let mut tx = Transaction { + version: proto.version, + lock_time: lock_time_from_proto(&proto.lock_time)?, + input: vec![], + output: vec![], + }; + + for txin in &proto.inputs { + let txid = Txid::from_slice(txin.txid.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_txid))?; + let vout = txin.vout; + let sequence = Sequence::from_consensus(txin.sequence); + let script_sig = ScriptBuf::from_bytes(txin.script_sig.to_vec()); + let witness = Witness::from_slice( + &txin + .witness_items + .iter() + .map(|s| s.as_ref()) + .collect::>(), + ); + + tx.input.push(TxIn { + previous_output: OutPoint { txid, vout }, + script_sig, + sequence, + witness, + }); + } + + for txout in &proto.outputs { + tx.output.push(TxOut { + value: txout.value, + script_pubkey: ScriptBuf::from_bytes(txout.script_pubkey.to_vec()), + }); + } + + // Encode the transaction. + let mut buffer = vec![]; + tx.consensus_encode(&mut buffer) + .map_err(|_| Error::from(Proto::Error::Error_failed_encoding))?; + + // The transaction identifier, which we represent in + // non-reversed/non-network order. + let txid: Vec = tx.txid().as_byte_array().iter().copied().rev().collect(); + + Ok(Proto::SerializedTransaction { + error: Proto::Error::OK, + encoded: buffer.into(), + txid: txid.into(), + weight: tx.weight().to_wu(), + fee: tx.weight().to_vbytes_ceil() * proto.weight_base, + }) + } +} + +fn convert_proto_to_tx<'a>(proto: &'a Proto::SigningInput<'a>) -> Result { + let mut tx = Transaction { + version: proto.version, + lock_time: lock_time_from_proto(&proto.lock_time)?, + input: vec![], + output: vec![], + }; + + for txin in &proto.inputs { + let txid = Txid::from_slice(txin.txid.as_ref()) + .map_err(|_| Error::from(Proto::Error::Error_invalid_txid))?; + + let vout = txin.vout; + + tx.input.push(TxIn { + previous_output: OutPoint { txid, vout }, + script_sig: ScriptBuf::new(), + sequence: Sequence(txin.sequence), + witness: Witness::new(), + }); + } + + for txout in &proto.outputs { + tx.output.push(TxOut { + value: txout.value, + script_pubkey: ScriptBuf::from_bytes(txout.script_pubkey.to_vec()), + }); + } + + Ok(tx) +} + +// Convenience function to retreive the lock time. If none is provided, the +// default lock time is used (immediately spendable). +fn lock_time_from_proto(proto: &Option) -> Result { + let lock_time = if let Some(lock_time) = proto { + match lock_time.variant { + ProtoLockTimeVariant::blocks(block) => LockTime::Blocks( + Height::from_consensus(block) + .map_err(|_| Error::from(Proto::Error::Error_invalid_lock_time))?, + ), + ProtoLockTimeVariant::seconds(secs) => LockTime::Seconds( + Time::from_consensus(secs) + .map_err(|_| Error::from(Proto::Error::Error_invalid_lock_time))?, + ), + ProtoLockTimeVariant::None => LockTime::Blocks( + Height::from_consensus(0) + .map_err(|_| Error::from(Proto::Error::Error_invalid_lock_time))?, + ), + } + } else { + LockTime::Blocks( + Height::from_consensus(0) + .map_err(|_| Error::from(Proto::Error::Error_invalid_lock_time))?, + ) + }; + + Ok(lock_time) +} diff --git a/rust/tw_utxo/src/lib.rs b/rust/tw_utxo/src/lib.rs new file mode 100644 index 00000000000..3724b9ef75c --- /dev/null +++ b/rust/tw_utxo/src/lib.rs @@ -0,0 +1,26 @@ +use tw_proto::Utxo::Proto; + +pub mod compiler; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub struct Error(Proto::Error); + +impl From for Error { + fn from(value: Proto::Error) -> Self { + Error(value) + } +} + +impl From for Error { + fn from(_value: bitcoin::sighash::Error) -> Self { + Error(Proto::Error::Error_sighash_failed) + } +} + +impl From for Proto::Error { + fn from(value: Error) -> Self { + value.0 + } +} diff --git a/rust/tw_utxo/tests/common.rs b/rust/tw_utxo/tests/common.rs new file mode 100644 index 00000000000..c8b65535cab --- /dev/null +++ b/rust/tw_utxo/tests/common.rs @@ -0,0 +1,23 @@ +#![allow(dead_code)] + +use bitcoin::key::UntweakedPublicKey; +use bitcoin::{PubkeyHash, PublicKey, WPubkeyHash}; +use secp256k1::{hashes::Hash, XOnlyPublicKey}; +use tw_encoding::hex; + +pub fn pubkey_hash_from_hex(hex: &str) -> PubkeyHash { + PubkeyHash::from_byte_array(hex::decode(hex).unwrap().try_into().unwrap()) +} + +pub fn witness_pubkey_hash(hex: &str) -> WPubkeyHash { + WPubkeyHash::from_byte_array(hex::decode(hex).unwrap().try_into().unwrap()) +} + +pub fn untweaked_pubkey(hex: &str) -> UntweakedPublicKey { + let pubkey = PublicKey::from_slice(&hex::decode(hex).unwrap()).unwrap(); + XOnlyPublicKey::from(pubkey.inner) +} + +pub fn txid_rev(hex: &str) -> Vec { + hex::decode(hex).unwrap().into_iter().rev().collect() +} diff --git a/rust/tw_utxo/tests/input_selection.rs b/rust/tw_utxo/tests/input_selection.rs new file mode 100644 index 00000000000..a8b00d87285 --- /dev/null +++ b/rust/tw_utxo/tests/input_selection.rs @@ -0,0 +1,597 @@ +mod common; +use common::{pubkey_hash_from_hex, txid_rev}; + +use bitcoin::ScriptBuf; +use tw_proto::Utxo::Proto; +use tw_utxo::compiler::{Compiler, StandardBitcoinContext}; + +const WEIGHT_BASE: u64 = 2; + +// Convenience function, creates the change output script. +fn change_output() -> ScriptBuf { + let pubkey_hash = pubkey_hash_from_hex("aabbccddeeff00112233445566778899aabbccdd"); + ScriptBuf::new_p2pkh(&pubkey_hash) +} + +#[test] +fn input_selector_all() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 1_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx2 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 2_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx3 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 3_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 500, + script_pubkey: Default::default(), + }; + + // Generate prehashes without change output. + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone()], + // Explicitly select all inputs. + input_selector: Proto::InputSelector::UseAll, + weight_base: WEIGHT_BASE, + change_script_pubkey: Default::default(), + // DISABLE change output. + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + assert_eq!(output.sighashes.len(), 3); + + // All inputs are used as mandated by the `input_selector`. Technically only + // one input is needed. + assert_eq!(output.inputs.len(), 3); + assert_eq!(output.inputs[0], tx1); + assert_eq!(output.inputs[1], tx2); + assert_eq!(output.inputs[2], tx3); + + assert_eq!(output.outputs.len(), 1); + assert_eq!(output.outputs[0], out1); + + // Generate prehashes WITH change output. + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone()], + // Explicitly select all inputs. + input_selector: Proto::InputSelector::UseAll, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + assert_eq!(output.sighashes.len(), 3); + assert_eq!(output.weight_estimate, 594); + assert_eq!(output.fee_estimate, (594 + 3) / 4 * WEIGHT_BASE); + + assert_eq!(output.inputs.len(), 3); + assert_eq!(output.inputs[0], tx1); + assert_eq!(output.inputs[1], tx2); + assert_eq!(output.inputs[2], tx3); + + // All inputs: 6_000, all outputs: 500 + let change_out = Proto::TxOut { + value: 6_000 - 500 - output.fee_estimate, + script_pubkey: change_script.as_bytes().into(), + }; + + assert_eq!(output.outputs.len(), 2); + assert_eq!(output.outputs[0], out1); + assert_eq!(output.outputs[1], change_out); +} + +#[test] +fn input_selector_all_insufficient_inputs() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 1_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx2 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 2_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx3 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 3_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 6_000, + script_pubkey: Default::default(), + }; + + // Generate prehashes without change output. + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone()], + // Explicitly select all inputs. + input_selector: Proto::InputSelector::UseAll, + weight_base: WEIGHT_BASE, + change_script_pubkey: Default::default(), + // DISABLE change output. + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + // While the input covers all outputs, it does not + // cover the projected fee. + assert_eq!(output.error, Proto::Error::Error_insufficient_inputs); + assert_eq!(output.sighashes.len(), 0); + assert_eq!(output.inputs.len(), 0); + assert_eq!(output.outputs.len(), 0); + + // Generate prehashes WITH change output (same outcome). + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone()], + // Explicitly select all inputs. + input_selector: Proto::InputSelector::UseAll, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::Error_insufficient_inputs); + assert_eq!(output.sighashes.len(), 0); + assert_eq!(output.inputs.len(), 0); + assert_eq!(output.outputs.len(), 0); +} + +#[test] +fn input_selector_one_input_required() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 4_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx2 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 4_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx3 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 4_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 500, + script_pubkey: Default::default(), + }; + let out2 = Proto::TxOut { + value: 500, + script_pubkey: Default::default(), + }; + + // Generate sighashes without change output. + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone(), out2.clone()], + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: Default::default(), + // DISABLE change output. + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + assert_eq!(output.sighashes.len(), 1); + + // One inputs covers the full output. + assert_eq!(output.inputs.len(), 1); + assert_eq!(output.inputs[0], tx1); + + assert_eq!(output.outputs.len(), 2); + assert_eq!(output.outputs[0], out1); + assert_eq!(output.outputs[1], out2); + + // Generate sighashes WITH change output. + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone(), out2.clone()], + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + assert_eq!(output.sighashes.len(), 1); + assert_eq!(output.weight_estimate, 302); + assert_eq!(output.fee_estimate, (302 + 3) / 4 * WEIGHT_BASE); + + // One inputs covers the full output. + assert_eq!(output.inputs.len(), 1); + assert_eq!(output.inputs[0], tx1); + + // All inputs: 4_000, all outputs: 2_000 + let change_out = Proto::TxOut { + value: 4_000 - 1_000 - output.fee_estimate, + script_pubkey: change_script.as_bytes().into(), + }; + + assert_eq!(output.outputs.len(), 3); + assert_eq!(output.outputs[0], out1); + assert_eq!(output.outputs[1], out2); + assert_eq!(output.outputs[2], change_out); +} + +#[test] +fn input_selector_two_inputs_required() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 1_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx2 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 3_000, + sequence: u32::MAX, + ..Default::default() + }; + let tx3 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 4_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + let out2 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + + // Generate sighashes without change output. + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone(), out2.clone()], + // We only select the necessary value of inputs to cover the output + // value. + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: Default::default(), + // DISABLE change output. + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + //assert_eq!(output.sighashes.len(), 2); + + // Only two inputs are needed to cover outputs. + assert_eq!(output.inputs.len(), 2); + assert_eq!(output.inputs[0], tx1); + assert_eq!(output.inputs[1], tx2); + + assert_eq!(output.outputs.len(), 2); + assert_eq!(output.outputs[0], out1); + assert_eq!(output.outputs[1], out2); + + // Generate sighashes WITH change output. + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone(), out2.clone()], + // We only select the necessary value of inputs to cover the output + // value. + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + assert_eq!(output.sighashes.len(), 2); + assert_eq!(output.weight_estimate, 466); + assert_eq!(output.fee_estimate, (466 + 3) / 4 * WEIGHT_BASE); + + // Only two inputs are needed to cover outputs. + assert_eq!(output.inputs.len(), 2); + assert_eq!(output.inputs[0], tx1); + assert_eq!(output.inputs[1], tx2); + + // All inputs: 4_000, all outputs: 2_000 + let change_out = Proto::TxOut { + value: 4_000 - 2_000 - output.fee_estimate, + script_pubkey: change_script.as_bytes().into(), + }; + + assert_eq!(output.outputs.len(), 3); + assert_eq!(output.outputs[0], out1); + assert_eq!(output.outputs[1], out2); + assert_eq!(output.outputs[2], change_out); +} + +#[test] +fn input_selector_one_input_cannot_cover_fees() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 2_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + let out2 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + + // Generate sighashes without change output. + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone()], + outputs: vec![out1.clone(), out2.clone()], + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: Default::default(), + // DISABLE change output. + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + // While the input covers all outputs, it does not + // cover the projected fee. + assert_eq!(output.error, Proto::Error::Error_insufficient_inputs); + assert_eq!(output.weight_estimate, 0); + assert_eq!(output.fee_estimate, 0); + assert_eq!(output.sighashes.len(), 0); + assert_eq!(output.inputs.len(), 0); + assert_eq!(output.outputs.len(), 0); + + // Generate sighashes WITH change output (same outcome). + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone()], + outputs: vec![out1.clone(), out2.clone()], + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::Error_insufficient_inputs); + assert_eq!(output.sighashes.len(), 0); + assert_eq!(output.weight_estimate, 0); + assert_eq!(output.fee_estimate, 0); + assert_eq!(output.inputs.len(), 0); + assert_eq!(output.outputs.len(), 0); +} + +#[test] +fn input_selector_exact_balance_no_change() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + // Covers the exact output value + projected fee. + value: 2_000 + (302 + 3) / 4 * WEIGHT_BASE, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + let out2 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone()], + outputs: vec![out1.clone(), out2.clone()], + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + assert_eq!(output.sighashes.len(), 1); + assert_eq!(output.weight_estimate, 302); + assert_eq!(output.fee_estimate, (302 + 3) / 4 * WEIGHT_BASE); + + // One inputs covers the full output. + assert_eq!(output.inputs.len(), 1); + assert_eq!(output.inputs[0], tx1); + + // NO change output + assert_eq!(output.outputs.len(), 2); + assert_eq!(output.outputs[0], out1); + assert_eq!(output.outputs[1], out2); +} + +#[test] +fn input_selector_empty_script_bufs() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 4_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + let out2 = Proto::TxOut { + value: 1_000, + script_pubkey: Default::default(), + }; + + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone()], + outputs: vec![out1.clone(), out2.clone()], + input_selector: Proto::InputSelector::SelectInOrder, + weight_base: WEIGHT_BASE, + // NO change script_pubkey specified, results in an error. + change_script_pubkey: Default::default(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!( + output.error, + Proto::Error::Error_missing_change_script_pubkey + ); + assert_eq!(output.sighashes.len(), 0); + assert_eq!(output.weight_estimate, 0); + assert_eq!(output.fee_estimate, 0); + assert_eq!(output.inputs.len(), 0); + assert_eq!(output.outputs.len(), 0); +} + +#[test] +fn input_selector_select_ascending() { + // Reusing the txid is fine here, although in production this would mark the transaction invalid. + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let tx1 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 8_000, + sequence: u32::MAX, + ..Default::default() + }; + + let tx2 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 4_000, + sequence: u32::MAX, + ..Default::default() + }; + + let tx3 = Proto::TxIn { + txid: txid.as_slice().into(), + value: 2_000, + sequence: u32::MAX, + ..Default::default() + }; + + let out1 = Proto::TxOut { + value: 3_000, + script_pubkey: Default::default(), + }; + + let change_script = change_output(); + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![tx1.clone(), tx2.clone(), tx3.clone()], + outputs: vec![out1.clone()], + // Select in ASCENDING order: + input_selector: Proto::InputSelector::SelectAscending, + weight_base: WEIGHT_BASE, + change_script_pubkey: change_script.as_bytes().into(), + // ENABLE change output. + disable_change_output: false, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + + // Two inputs where selected (in ASCENDING order). + assert_eq!(output.inputs.len(), 2); + assert_eq!(output.inputs[0], tx3); + assert_eq!(output.inputs[1], tx2); + + // Two outputs; target and change. + assert_eq!(output.outputs.len(), 2); + assert_eq!(output.outputs[0], out1); +} diff --git a/rust/tw_utxo/tests/p2pkh.rs b/rust/tw_utxo/tests/p2pkh.rs new file mode 100644 index 00000000000..99fe20e1cc0 --- /dev/null +++ b/rust/tw_utxo/tests/p2pkh.rs @@ -0,0 +1,53 @@ +mod common; +use common::{pubkey_hash_from_hex, txid_rev}; + +use bitcoin::ScriptBuf; +use tw_encoding::hex; +use tw_proto::Utxo::Proto; +use tw_utxo::compiler::{Compiler, StandardBitcoinContext}; + +#[test] +fn sighash_input_p2pkh_output_p2pkh() { + let pubkey_hash = pubkey_hash_from_hex("e4c1ea86373d554b8f4efff2cfb0001ea19124d2"); + let input_script_pubkey = ScriptBuf::new_p2pkh(&pubkey_hash); + + let pubkey_hash = pubkey_hash_from_hex("5eaaa4f458f9158f86afcba08dd7448d27045e3d"); + let output_script_pubkey = ScriptBuf::new_p2pkh(&pubkey_hash); + + let txid = txid_rev("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![Proto::TxIn { + txid: txid.into(), + vout: 0, + // Amount is not part of sighash for `Legacy`. + value: u64::MAX, + sequence: u32::MAX, + script_pubkey: input_script_pubkey.as_bytes().into(), + sighash_type: Proto::SighashType::All, + signing_method: Proto::SigningMethod::Legacy, + weight_estimate: 1, + leaf_hash: Default::default(), + }], + outputs: vec![Proto::TxOut { + value: 50 * 100_000_000 - 1_000_000, + script_pubkey: output_script_pubkey.as_bytes().into(), + }], + input_selector: Proto::InputSelector::UseAll, + weight_base: 1, + change_script_pubkey: Default::default(), + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + + let hashes = output.sighashes; + assert_eq!(hashes.len(), 1); + assert_eq!( + hex::encode(hashes[0].sighash.as_ref(), false), + "6a0e072da66b141fdb448323d54765cafcaf084a06d2fa13c8aed0c694e50d18" + ); +} diff --git a/rust/tw_utxo/tests/p2tr.rs b/rust/tw_utxo/tests/p2tr.rs new file mode 100644 index 00000000000..f8521872f33 --- /dev/null +++ b/rust/tw_utxo/tests/p2tr.rs @@ -0,0 +1,56 @@ +mod common; +use common::{pubkey_hash_from_hex, txid_rev, untweaked_pubkey}; + +use bitcoin::ScriptBuf; +use tw_encoding::hex; +use tw_proto::Utxo::Proto; +use tw_utxo::compiler::{Compiler, StandardBitcoinContext}; + +#[test] +fn sighash_input_p2pkh_output_p2tr_key_spend() { + let pubkey_hash = pubkey_hash_from_hex("a0cd6d6e2f9804351ba4b722b708bc2fd3229a5a"); + let input_script_pubkey = ScriptBuf::new_p2pkh(&pubkey_hash); + + let untweaked_pubkey = + untweaked_pubkey("02c0938cf377023dfde55e9c96b3cff4ca8894fb6b5d2009006bd43c0bff69cac9"); + let output_script_pubkey = + // Merkle root of `None` is interpreted as P2TR key-spend. + ScriptBuf::new_v1_p2tr(&secp256k1::Secp256k1::new(), untweaked_pubkey, None); + + let txid = txid_rev("c50563913e5a838f937c94232f5a8fc74e58b629fae41dfdffcc9a70f833b53a"); + + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![Proto::TxIn { + txid: txid.into(), + vout: 0, + // Amount is not part of sighash for `Legacy`. + value: u64::MAX, + sequence: u32::MAX, + script_pubkey: input_script_pubkey.as_bytes().into(), + sighash_type: Proto::SighashType::All, + signing_method: Proto::SigningMethod::Legacy, + weight_estimate: 1, + leaf_hash: Default::default(), + }], + outputs: vec![Proto::TxOut { + value: 50 * 100_000_000 - 1_000_000, + script_pubkey: output_script_pubkey.as_bytes().into(), + }], + input_selector: Proto::InputSelector::UseAll, + weight_base: 1, + change_script_pubkey: Default::default(), + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + + let hashes = output.sighashes; + assert_eq!(hashes.len(), 1); + assert_eq!( + hex::encode(hashes[0].sighash.as_ref(), false), + "c914fd08efdcc7f8007c75c39ab47e1ee736a6ce1e6363250fe88cda8fca04d1" + ); +} diff --git a/rust/tw_utxo/tests/p2wpkh.rs b/rust/tw_utxo/tests/p2wpkh.rs new file mode 100644 index 00000000000..3ad47370695 --- /dev/null +++ b/rust/tw_utxo/tests/p2wpkh.rs @@ -0,0 +1,100 @@ +mod common; +use common::{pubkey_hash_from_hex, txid_rev, witness_pubkey_hash}; + +use bitcoin::ScriptBuf; +use tw_encoding::hex; +use tw_proto::Utxo::Proto; +use tw_utxo::compiler::{Compiler, StandardBitcoinContext}; + +#[test] +fn sighash_input_p2pkh_output_p2wpkh() { + let pubkey_hash = pubkey_hash_from_hex("60cda7b50f14c152d7401c28ae773c698db92373"); + let input_script_pubkey = ScriptBuf::new_p2pkh(&pubkey_hash); + + let wpubkey_hash = witness_pubkey_hash("0d0e1cec6c2babe8badde5e9b3dea667da90036d"); + let output_script_pubkey = ScriptBuf::new_v0_p2wpkh(&wpubkey_hash); + + let txid = txid_rev("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911"); + + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![Proto::TxIn { + txid: txid.into(), + vout: 0, + // Amount is not part of sighash for `Legacy`. + value: u64::MAX, + sequence: u32::MAX, + script_pubkey: input_script_pubkey.as_bytes().into(), + sighash_type: Proto::SighashType::All, + signing_method: Proto::SigningMethod::Legacy, + weight_estimate: 1, + leaf_hash: Default::default(), + }], + outputs: vec![Proto::TxOut { + value: 50 * 100_000_000 - 1_000_000, + script_pubkey: output_script_pubkey.as_bytes().into(), + }], + input_selector: Proto::InputSelector::UseAll, + weight_base: 1, + change_script_pubkey: Default::default(), + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + + let hashes = output.sighashes; + assert_eq!(hashes.len(), 1); + assert_eq!( + hex::encode(hashes[0].sighash.as_ref(), false), + "c4963ecd6c08be4c9dd66416349084a5b54318b3802370451d580210bc883463" + ); +} + +#[test] +fn sighash_input_p2wpkh_output_p2wpkh() { + let wpubkey_hash = witness_pubkey_hash("0d0e1cec6c2babe8badde5e9b3dea667da90036d"); + let input_script_pubkey = ScriptBuf::new_v0_p2wpkh(&wpubkey_hash) + .p2wpkh_script_code() + .unwrap(); + + let wpubkey_hash = witness_pubkey_hash("60cda7b50f14c152d7401c28ae773c698db92373"); + let output_script_pubkey = ScriptBuf::new_v0_p2wpkh(&wpubkey_hash); + + let txid = txid_rev("858e450a1da44397bde05ca2f8a78510d74c623cc2f69736a8b3fbfadc161f6e"); + + let signing = Proto::SigningInput { + version: 2, + lock_time: Default::default(), + inputs: vec![Proto::TxIn { + txid: txid.into(), + vout: 0, + value: 50 * 100_000_000 - 1_000_000, + sequence: u32::MAX, + script_pubkey: input_script_pubkey.as_bytes().into(), + sighash_type: Proto::SighashType::All, + signing_method: Proto::SigningMethod::Segwit, + weight_estimate: 1, + leaf_hash: Default::default(), + }], + outputs: vec![Proto::TxOut { + value: 50 * 100_000_000 - 1_000_000 * 2, + script_pubkey: output_script_pubkey.as_bytes().into(), + }], + input_selector: Proto::InputSelector::UseAll, + weight_base: 1, + change_script_pubkey: Default::default(), + disable_change_output: true, + }; + + let output = Compiler::::preimage_hashes(signing); + assert_eq!(output.error, Proto::Error::OK); + + let hashes = output.sighashes; + assert_eq!(hashes.len(), 1); + assert_eq!( + hex::encode(hashes[0].sighash.as_ref(), false), + "6900ebbef74c938ec2310df10cd520b5e7c82c0fe1bb68c62c8fae7bf54e2092" + ); +} diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 87386edb3d6..37b8c878d1d 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -5,14 +5,30 @@ edition = "2021" [lib] name = "wallet_core_rs" -crate-type = ["staticlib"] # Creates static lib +crate-type = ["staticlib", "rlib"] # Creates static lib + +[features] +default = ["bitcoin-legacy", "ethereum-abi", "ethereum-rlp"] +bitcoin-legacy = [] +ethereum-abi = [] +ethereum-rlp = [] [dependencies] +tw_any_coin = { path = "../tw_any_coin" } +tw_aptos = { path = "../tw_aptos" } +tw_bitcoin = { path = "../tw_bitcoin" } +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_coin_registry = { path = "../tw_coin_registry" } tw_encoding = { path = "../tw_encoding" } +tw_ethereum = { path = "../tw_ethereum" } tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } -tw_move_parser = { path = "../tw_move_parser" } +tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto" } -tw_bitcoin = { path = "../tw_bitcoin" } -tw_starknet = { path = "../tw_starknet" } + +[dev-dependencies] +serde_json = "1.0.96" +tw_any_coin = { path = "../tw_any_coin", features = ["test-utils"] } +tw_memory = { path = "../tw_memory", features = ["test-utils"] } +tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/rust/wallet_core_rs/cbindgen.toml b/rust/wallet_core_rs/cbindgen.toml index f2a1fc06b70..fcab0505411 100644 --- a/rust/wallet_core_rs/cbindgen.toml +++ b/rust/wallet_core_rs/cbindgen.toml @@ -6,5 +6,27 @@ namespaces = ["TW", "Rust"] [parse] parse_deps = true -extra_bindings = ["tw_bitcoin", "tw_memory", "tw_encoding", "tw_hash", "tw_keypair", "tw_move_parser", "tw_proto", "tw_starknet"] -include = ["tw_bitcoin", "tw_memory", "tw_encoding", "tw_hash", "tw_keypair", "tw_move_parser", "tw_proto", "tw_starknet"] +extra_bindings = [ + "tw_any_coin", + "tw_bitcoin", + "tw_coin_registry", + "tw_encoding", + "tw_ethereum", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_move_parser", + "tw_proto" +] +include = [ + "tw_any_coin", + "tw_bitcoin", + "tw_coin_registry", + "tw_encoding", + "tw_ethereum", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_move_parser", + "tw_proto" +] diff --git a/rust/wallet_core_rs/src/ffi/bitcoin/legacy.rs b/rust/wallet_core_rs/src/ffi/bitcoin/legacy.rs new file mode 100644 index 00000000000..5a172e2c648 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/bitcoin/legacy.rs @@ -0,0 +1,308 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use std::ffi::{c_char, CStr}; +use tw_bitcoin::aliases::*; +use tw_bitcoin::native::consensus::Decodable; +use tw_bitcoin::native::{PublicKey, Transaction}; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::c_result::CUInt64Result; +use tw_misc::try_or_else; +use tw_proto::Bitcoin::Proto as LegacyProto; +use tw_proto::BitcoinV2::Proto; +use tw_proto::Common::Proto as CommonProto; + +// NOTE: The tests for those APIs can be found in `tw_bitcoin`. + +#[no_mangle] +#[deprecated] +// Builds the P2PKH scriptPubkey. +pub unsafe extern "C" fn tw_bitcoin_legacy_build_p2pkh_script( + _satoshis: i64, + pubkey: *const u8, + pubkey_len: usize, +) -> CByteArray { + // Convert Recipient + let slice = try_or_else!( + CByteArrayRef::new(pubkey, pubkey_len).as_slice(), + CByteArray::null + ); + let recipient = try_or_else!(PublicKey::from_slice(slice), CByteArray::null); + + let output = Proto::Output { + value: _satoshis as u64, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2pkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(recipient.to_bytes().into()), + }), + }), + }; + + let res = try_or_else!( + tw_bitcoin::modules::transactions::OutputBuilder::utxo_from_proto(&output), + CByteArray::null + ); + + // Prepare and serialize protobuf structure. + let proto = LegacyProto::TransactionOutput { + value: res.value as i64, + script: res.script_pubkey, + spendingScript: Default::default(), + }; + + let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); + CByteArray::from(serialized) +} + +#[no_mangle] +#[deprecated] +// Builds the P2WPKH scriptPubkey. +pub unsafe extern "C" fn tw_bitcoin_legacy_build_p2wpkh_script( + _satoshis: i64, + pubkey: *const u8, + pubkey_len: usize, +) -> CByteArray { + // Convert Recipient + let slice = try_or_else!( + CByteArrayRef::new(pubkey, pubkey_len).as_slice(), + CByteArray::null + ); + + let recipient = try_or_else!(PublicKey::from_slice(slice), CByteArray::null); + + let output = Proto::Output { + value: _satoshis as u64, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2wpkh(Proto::ToPublicKeyOrHash { + to_address: ProtoPubkeyOrHash::pubkey(recipient.to_bytes().into()), + }), + }), + }; + + let res = try_or_else!( + tw_bitcoin::modules::transactions::OutputBuilder::utxo_from_proto(&output), + CByteArray::null + ); + + // Prepare and serialize protobuf structure. + let proto = LegacyProto::TransactionOutput { + value: res.value as i64, + script: res.script_pubkey, + spendingScript: Default::default(), + }; + + let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); + CByteArray::from(serialized) +} + +#[no_mangle] +#[deprecated] +// Builds the P2TR key-path scriptPubkey. +pub unsafe extern "C" fn tw_bitcoin_legacy_build_p2tr_key_path_script( + _satoshis: i64, + pubkey: *const u8, + pubkey_len: usize, +) -> CByteArray { + // Convert Recipient + let slice = try_or_else!( + CByteArrayRef::new(pubkey, pubkey_len).as_slice(), + CByteArray::null + ); + let recipient = try_or_else!(PublicKey::from_slice(slice), CByteArray::null); + + let output = Proto::Output { + value: _satoshis as u64, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::p2tr_key_path(recipient.to_bytes().into()), + }), + }; + + let res = try_or_else!( + tw_bitcoin::modules::transactions::OutputBuilder::utxo_from_proto(&output), + CByteArray::null + ); + + // Prepare and serialize protobuf structure. + let proto = LegacyProto::TransactionOutput { + value: res.value as i64, + script: res.script_pubkey, + spendingScript: Default::default(), + }; + + let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); + CByteArray::from(serialized) +} + +#[no_mangle] +#[deprecated] +// Builds the Ordinals inscripton for BRC20 transfer. +pub unsafe extern "C" fn tw_bitcoin_legacy_build_brc20_transfer_inscription( + // The 4-byte ticker. + ticker: *const c_char, + value: u64, + _satoshis: i64, + pubkey: *const u8, + pubkey_len: usize, +) -> CByteArray { + // Convert Recipient + let slice = try_or_else!( + CByteArrayRef::new(pubkey, pubkey_len).as_slice(), + CByteArray::null + ); + + let recipient = try_or_else!(PublicKey::from_slice(slice), CByteArray::null); + + // Convert ticket. + let ticker = match CStr::from_ptr(ticker).to_str() { + Ok(input) => input, + Err(_) => return CByteArray::null(), + }; + + let output = Proto::Output { + value: _satoshis as u64, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::brc20_inscribe( + Proto::mod_Output::OutputBrc20Inscription { + inscribe_to: recipient.to_bytes().into(), + ticker: ticker.into(), + transfer_amount: value, + }, + ), + }), + }; + + let res = try_or_else!( + tw_bitcoin::modules::transactions::OutputBuilder::utxo_from_proto(&output), + CByteArray::null + ); + + // Prepare and serialize protobuf structure. + let proto = LegacyProto::TransactionOutput { + value: res.value as i64, + script: res.script_pubkey, + spendingScript: res.taproot_payload, + }; + + let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); + CByteArray::from(serialized) +} + +#[no_mangle] +#[deprecated] +// Builds the Ordinals inscripton for BRC20 transfer. +pub unsafe extern "C" fn tw_bitcoin_legacy_build_nft_inscription( + mime_type: *const c_char, + payload: *const u8, + payload_len: usize, + _satoshis: i64, + pubkey: *const u8, + pubkey_len: usize, +) -> CByteArray { + // Convert mimeType. + let mime_type = match CStr::from_ptr(mime_type).to_str() { + Ok(input) => input, + Err(_) => return CByteArray::null(), + }; + + // Convert data to inscribe. + let payload = try_or_else!( + CByteArrayRef::new(payload, payload_len).as_slice(), + CByteArray::null + ); + + // Convert Recipient. + let slice = try_or_else!( + CByteArrayRef::new(pubkey, pubkey_len).as_slice(), + CByteArray::null + ); + + let recipient = try_or_else!(PublicKey::from_slice(slice), CByteArray::null); + + // Inscribe NFT data. + let output = Proto::Output { + value: _satoshis as u64, + to_recipient: ProtoOutputRecipient::builder(Proto::mod_Output::OutputBuilder { + variant: ProtoOutputBuilder::ordinal_inscribe( + Proto::mod_Output::OutputOrdinalInscription { + inscribe_to: recipient.to_bytes().into(), + mime_type: mime_type.into(), + payload: payload.into(), + }, + ), + }), + }; + + let res = try_or_else!( + tw_bitcoin::modules::transactions::OutputBuilder::utxo_from_proto(&output), + CByteArray::null + ); + + // Prepare and serialize protobuf structure. + let proto = LegacyProto::TransactionOutput { + value: res.value as i64, + script: res.script_pubkey, + spendingScript: res.taproot_payload, + }; + + let serialized = tw_proto::serialize(&proto).expect("failed to serialized transaction output"); + CByteArray::from(serialized) +} + +#[deprecated] +#[no_mangle] +pub unsafe extern "C" fn tw_bitcoin_legacy_calculate_transaction_fee( + input: *const u8, + input_len: usize, + sat_vb: u64, +) -> CUInt64Result { + let Some(mut encoded) = CByteArrayRef::new(input, input_len).as_slice() else { + return CUInt64Result::error(1); + }; + + // Decode transaction. + let Ok(tx) = Transaction::consensus_decode(&mut encoded) else { + return CUInt64Result::error(1); + }; + + // Calculate fee. + let weight = tx.weight(); + let fee = weight.to_vbytes_ceil() * sat_vb; + + CUInt64Result::ok(fee) +} + +#[no_mangle] +#[deprecated] +pub unsafe extern "C" fn tw_bitcoin_legacy_taproot_build_and_sign_transaction( + input: *const u8, + input_len: usize, +) -> CByteArray { + let data = CByteArrayRef::new(input, input_len) + .to_vec() + .unwrap_or_default(); + + let proto: LegacyProto::SigningInput = + try_or_else!(tw_proto::deserialize(&data), CByteArray::null); + + let Ok(signing) = tw_bitcoin::modules::legacy::taproot_build_and_sign_transaction(proto) else { + // Convert the `BitcoinV2.proto` error type inot the `Common.proto` + // errot type and return. + let error = LegacyProto::SigningOutput { + error: CommonProto::SigningError::Error_general, + ..Default::default() + }; + + let serialized = tw_proto::serialize(&error).expect("failed to serialize error message"); + return CByteArray::from(serialized) + }; + + // Serialize SigningOutput and return. + let serialized = tw_proto::serialize(&signing).expect("failed to serialize signed transaction"); + CByteArray::from(serialized) +} diff --git a/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs b/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs new file mode 100644 index 00000000000..5b0dcab0885 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#[cfg(feature = "bitcoin-legacy")] +pub mod legacy; diff --git a/rust/wallet_core_rs/src/ffi/ethereum/abi.rs b/rust/wallet_core_rs/src/ffi/ethereum/abi.rs new file mode 100644 index 00000000000..84f64877781 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/ethereum/abi.rs @@ -0,0 +1,115 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::evm_dispatcher; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; + +/// Decode function call data to human readable json format, according to input abi json. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ContractCallDecodingInput`. +/// \return serialized `EthereumAbi::Proto::ContractCallDecodingOutput`. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_abi_decode_contract_call( + coin: u32, + input: *const TWData, +) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); + + evm_dispatcher + .decode_abi_contract_call(input_data.as_slice()) + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Decode a function input or output data according to a given ABI. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ParamsDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ParamsDecodingOutput` proto object. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_abi_decode_params( + coin: u32, + input: *const TWData, +) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); + + evm_dispatcher + .decode_abi_params(input_data.as_slice()) + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the function type signature, of the form "baz(int32,uint256)". +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.FunctionGetTypeInput`. +/// \return function type signature as a Non-null string. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_abi_function_get_signature( + coin: u32, + input: *const TWData, +) -> *mut TWString { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), || TWString::new() + .into_ptr()); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), || TWString::new().into_ptr()); + + evm_dispatcher + .get_abi_function_signature(input_data.as_slice()) + .map(|str| TWString::from(str).into_ptr()) + .unwrap_or_else(|_| TWString::new().into_ptr()) +} + +/// Encode function inputs to Eth ABI binary. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.FunctionEncodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.FunctionEncodingOutput` proto object. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_abi_encode_function( + coin: u32, + input: *const TWData, +) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); + + evm_dispatcher + .encode_abi_function(input_data.as_slice()) + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// /// Decodes an Eth ABI value according to a given type. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ValueDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ValueDecodingOutput` proto object. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_abi_decode_value( + coin: u32, + input: *const TWData, +) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); + + evm_dispatcher + .decode_abi_value(input_data.as_slice()) + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/wallet_core_rs/src/ffi/ethereum/mod.rs b/rust/wallet_core_rs/src/ffi/ethereum/mod.rs new file mode 100644 index 00000000000..e3d6538646b --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/ethereum/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#[cfg(feature = "ethereum-abi")] +pub mod abi; +#[cfg(feature = "ethereum-rlp")] +pub mod rlp; diff --git a/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs b/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs new file mode 100644 index 00000000000..f13470891c6 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::evm_dispatcher; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; + +/// Encodes an item or a list of items as Eth RLP binary format. +/// +/// \param coin EVM-compatible coin type. +/// \param input Non-null serialized `EthereumRlp::Proto::EncodingInput`. +/// \return serialized `EthereumRlp::Proto::EncodingOutput`. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_rlp_encode(coin: u32, input: *const TWData) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); + evm_dispatcher + .encode_rlp(input_data.as_slice()) + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/wallet_core_rs/src/ffi/mod.rs b/rust/wallet_core_rs/src/ffi/mod.rs new file mode 100644 index 00000000000..54176826c15 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod bitcoin; +pub mod ethereum; diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index b3eb03421d4..eefcb93f8c8 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -4,11 +4,15 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub extern crate tw_any_coin; +pub extern crate tw_aptos; pub extern crate tw_bitcoin; +pub extern crate tw_coin_registry; pub extern crate tw_encoding; +pub extern crate tw_ethereum; pub extern crate tw_hash; pub extern crate tw_keypair; pub extern crate tw_memory; -pub extern crate tw_move_parser; pub extern crate tw_proto; -pub extern crate tw_starknet; + +pub mod ffi; diff --git a/rust/wallet_core_rs/tests/data/custom.json b/rust/wallet_core_rs/tests/data/custom.json new file mode 100644 index 00000000000..711a8c86217 --- /dev/null +++ b/rust/wallet_core_rs/tests/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/rust/wallet_core_rs/tests/data/custom_decoded.json b/rust/wallet_core_rs/tests/data/custom_decoded.json new file mode 100644 index 00000000000..c5f0e4b4cb8 --- /dev/null +++ b/rust/wallet_core_rs/tests/data/custom_decoded.json @@ -0,0 +1,20 @@ +{ + "function": "setName(string,uint256,int32)", + "inputs": [ + { + "name": "name", + "type": "string", + "value": "trusty" + }, + { + "name": "age", + "type": "uint256", + "value": "3" + }, + { + "name": "height", + "type": "int32", + "value": "100" + } + ] +} \ No newline at end of file diff --git a/rust/wallet_core_rs/tests/ethereum_abi.rs b/rust/wallet_core_rs/tests/ethereum_abi.rs new file mode 100644 index 00000000000..50f1dd09d7e --- /dev/null +++ b/rust/wallet_core_rs/tests/ethereum_abi.rs @@ -0,0 +1,195 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::{json, Value as Json}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_number::U256; +use tw_proto::EthereumAbi::{Proto as AbiProto, Proto}; +use tw_proto::{deserialize, serialize}; +use wallet_core_rs::ffi::ethereum::abi::{ + tw_ethereum_abi_decode_contract_call, tw_ethereum_abi_decode_params, + tw_ethereum_abi_decode_value, tw_ethereum_abi_encode_function, + tw_ethereum_abi_function_get_signature, +}; + +use tw_coin_registry::coin_type::CoinType; +use Proto::mod_ParamType::OneOfparam as ParamTypeEnum; +use Proto::mod_Token::OneOftoken as TokenEnum; +use Proto::AbiError as AbiErrorKind; + +fn param(name: &str, kind: ParamTypeEnum<'static>) -> Proto::Param<'static> { + Proto::Param { + name: name.to_string().into(), + param: Some(Proto::ParamType { param: kind }), + } +} + +fn named_token(name: &str, token: TokenEnum<'static>) -> Proto::Token<'static> { + Proto::Token { + name: name.to_string().into(), + token, + } +} + +fn number_n(value: u64) -> Proto::NumberNParam<'static> { + Proto::NumberNParam { + bits: BITS, + value: U256::encode_be_compact(value), + } +} + +#[test] +fn test_ethereum_abi_decode_contract_call() { + const CUSTOM_ABI_JSON: &str = include_str!("data/custom.json"); + const CUSTOM_DECODED_JSON: &str = include_str!("data/custom_decoded.json"); + + let encoded = "ec37a4a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000067472757374790000000000000000000000000000000000000000000000000000".decode_hex().unwrap(); + + let input = AbiProto::ContractCallDecodingInput { + encoded: encoded.into(), + smart_contract_abi_json: CUSTOM_ABI_JSON.into(), + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output_data = TWDataHelper::wrap(unsafe { + tw_ethereum_abi_decode_contract_call(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_ethereum_abi_decode_contract_call returned nullptr"); + let output: AbiProto::ContractCallDecodingOutput = deserialize(&output_data) + .expect("!tw_ethereum_abi_decode_contract_call returned an invalid output"); + + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + + let actual: Json = serde_json::from_str(&output.decoded_json).unwrap(); + let expected: Json = serde_json::from_str(CUSTOM_DECODED_JSON).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn test_ethereum_abi_decode_params() { + let abi_json = json!([ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ]); + let abi_json = serde_json::to_string(&abi_json).unwrap(); + // https://etherscan.io/tx/0xc2744000a107aee4761cf8a638657f91c3003a54e2f1818c37d781be7e48187a + let encoded = "00000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b0c0000000000000000000000000000000000000000000000000000000000000001".decode_hex().unwrap(); + + let input = AbiProto::ParamsDecodingInput { + encoded: encoded.into(), + abi: AbiProto::mod_ParamsDecodingInput::OneOfabi::abi_json(abi_json.into()), + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output_data = TWDataHelper::wrap(unsafe { + tw_ethereum_abi_decode_params(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_ethereum_abi_decode_params returned nullptr"); + + let output: AbiProto::ParamsDecodingOutput = deserialize(&output_data) + .expect("!tw_ethereum_abi_decode_params returned an invalid output"); + + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + + let expected_tokens = vec![ + named_token( + "to", + TokenEnum::address("0x88341d1a8F672D2780C8dC725902AAe72F143B0c".into()), + ), + named_token("approved", TokenEnum::boolean(true)), + ]; + assert_eq!(output.tokens, expected_tokens); +} + +#[test] +fn test_ethereum_abi_function_get_signature() { + let input = AbiProto::FunctionGetTypeInput { + function_name: "baz".into(), + inputs: vec![ + param( + "foo", + ParamTypeEnum::number_uint(Proto::NumberNType { bits: 64 }), + ), + param("bar", ParamTypeEnum::address(Proto::AddressType {})), + ], + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let actual = TWStringHelper::wrap(unsafe { + tw_ethereum_abi_function_get_signature(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_string() + .expect("!tw_ethereum_abi_function_get_signature returned nullptr"); + + assert_eq!(actual, "baz(uint64,address)"); +} + +#[test] +fn test_ethereum_abi_encode_function() { + let input = AbiProto::FunctionEncodingInput { + function_name: "baz".into(), + tokens: vec![ + named_token("", TokenEnum::number_uint(number_n::<256>(69))), + named_token("", TokenEnum::boolean(true)), + ], + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output_data = TWDataHelper::wrap(unsafe { + tw_ethereum_abi_encode_function(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_ethereum_abi_encode_function returned nullptr"); + + let output: AbiProto::FunctionEncodingOutput = deserialize(&output_data) + .expect("!tw_ethereum_abi_encode_function returned an invalid output"); + + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.encoded.to_hex(), "72ed38b600000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001"); +} + +#[test] +fn test_ethereum_abi_decode_value() { + let input = AbiProto::ValueDecodingInput { + encoded: "000000000000000000000000000000000000000000000000000000000000002a" + .decode_hex() + .unwrap() + .into(), + param_type: "int8".into(), + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output_data = TWDataHelper::wrap(unsafe { + tw_ethereum_abi_decode_value(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_ethereum_abi_decode_value returned nullptr"); + + let output: AbiProto::ValueDecodingOutput = deserialize(&output_data) + .expect("!tw_ethereum_abi_decode_value returned an invalid output"); + + assert_eq!(output.error, AbiErrorKind::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.param_str, "42"); +} diff --git a/rust/wallet_core_rs/tests/ethereum_rlp.rs b/rust/wallet_core_rs/tests/ethereum_rlp.rs new file mode 100644 index 00000000000..2326c482042 --- /dev/null +++ b/rust/wallet_core_rs/tests/ethereum_rlp.rs @@ -0,0 +1,37 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::ToHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::EthereumRlp::Proto as RlpProto; +use tw_proto::{deserialize, serialize}; +use wallet_core_rs::ffi::ethereum::rlp::tw_ethereum_rlp_encode; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +#[test] +fn test_ethereum_rlp() { + let item = RlpProto::RlpItem { + item: Item::number_u64(128), + }; + let input = RlpProto::EncodingInput { item: Some(item) }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output_data = TWDataHelper::wrap(unsafe { + tw_ethereum_rlp_encode(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_ethereum_rlp_encode returned nullptr"); + let output: RlpProto::EncodingOutput = + deserialize(&output_data).expect("!tw_ethereum_rlp_encode returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected_encoded = "8180"; + assert_eq!(output.encoded.to_hex(), expected_encoded); +} diff --git a/samples/cpp/README.md b/samples/cpp/README.md index 353c7163b93..5998b7c59d7 100644 --- a/samples/cpp/README.md +++ b/samples/cpp/README.md @@ -2,7 +2,7 @@ ## Overview -This repository contains a simple but complete **C++** sample application, for demostrating usage of the +This repository contains a simple but complete **C++** sample application, for demonstrating usage of the [Wallet Core](https://github.com/trustwallet/wallet-core) library (part of [Trust Wallet](https://trustwallet.com)). ## DISCLAIMER @@ -23,7 +23,7 @@ and [Build Instructions](https://developer.trustwallet.com/wallet-core/building) You need to download and build WalletCore yourself (there is no official binary distribution). -The dependencies TrezorCrypto and protobuf are also needed, these are also come with WalletCore. +The dependencies TrezorCrypto and protobuf are also needed, these also come with WalletCore. You need to [build](https://developer.trustwallet.com/wallet-core/building) the library. @@ -49,7 +49,7 @@ Run it: ./sample ``` -The relavant sample code is in the file `cpp/sample.cpp`. +The relevant sample code is in the file `cpp/sample.cpp`. # What it Does diff --git a/samples/go/README.md b/samples/go/README.md index 96716cabb90..e543e7f7c0b 100644 --- a/samples/go/README.md +++ b/samples/go/README.md @@ -50,7 +50,7 @@ cd wallet-core ### 🐳 Docker 1. Run `docker run -it trustwallet/wallet-core` -The librabry is already built in this image (Build instructions [here](building.md)) Note: may not be the most recent version. +The library 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.16.12](https://go.dev/dl/go1.16.12.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). @@ -62,7 +62,7 @@ The librabry is already built in this image (Build instructions [here](building cd wallet-core/samples/go ``` -2. Compile it by `go build -o main`. Relavant source file is `main.go`. +2. Compile it by `go build -o main`. Relevant source file is `main.go`. 3. Run `./main` and you will see the output below: @@ -73,4 +73,4 @@ cd wallet-core/samples/go ``` 4. *(optional)* 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. -5. *(optional)* If you want to make transaction on other networks you need to compile `src/proto` proto files and to do that, just run the `./compile.sh` . you can also modify it based on your project. \ No newline at end of file +5. *(optional)* If you want to make transaction on other networks you need to compile `src/proto` proto files and to do that, just run the `./compile.sh` . you can also modify it based on your project. diff --git a/samples/go/main.go b/samples/go/main.go index 8aae9f98dac..1b36647baf4 100644 --- a/samples/go/main.go +++ b/samples/go/main.go @@ -45,7 +45,7 @@ func main() { fmt.Println("Ethereum signed tx:") fmt.Println("\t", ethTxn) - // Bitcion transaction + // Bitcoin transaction btcTxn := createBtcTransaction(bw) fmt.Println("\nBitcoin signed tx:") fmt.Println("\t", btcTxn) diff --git a/samples/go/sample/external_signing.go b/samples/go/sample/external_signing.go index 87b5b96d6b3..161de4380db 100644 --- a/samples/go/sample/external_signing.go +++ b/samples/go/sample/external_signing.go @@ -68,29 +68,25 @@ func SignExternalEthereumDemo() { coin := core.CoinTypeEthereum fmt.Println("\n==> Step 1: Prepare transaction input (protobuf)") - txInputData := core.BuildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "", // chainId - ) - fmt.Println("txInputData len: ", len(txInputData)) - - // Set a few other values var input ethereum.SigningInput - proto.Unmarshal(txInputData, &input) + input.Transaction = ðereum.Transaction { + TransactionOneof: ðereum.Transaction_Transfer_ { + Transfer: ðereum.Transaction_Transfer{ + Amount: big.NewInt(1000000000000000000).Bytes(), + }, + }, + } + input.ChainId = big.NewInt(1).Bytes() + input.ToAddress = "0x3535353535353535353535353535353535353535" input.Nonce = big.NewInt(11).Bytes() input.GasPrice = big.NewInt(20000000000).Bytes() input.GasLimit = big.NewInt(21000).Bytes() input.TxMode = ethereum.TransactionMode_Legacy - txInputData2, _ := proto.Marshal(&input) - fmt.Println("txInputData len: ", len(txInputData2)) + txInputData, _ := proto.Marshal(&input) + fmt.Println("txInputData len: ", len(txInputData)) fmt.Println("\n==> Step 2: Obtain preimage hash") - hashes := core.PreImageHashes(coin, txInputData2) + hashes := core.PreImageHashes(coin, txInputData) fmt.Println("hash(es): ", len(hashes), hex.EncodeToString(hashes)) var preSigningOutput transactioncompiler.PreSigningOutput @@ -100,7 +96,7 @@ func SignExternalEthereumDemo() { // Simulate signature, normally obtained from signature server signature, _ := hex.DecodeString("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900") publicKey, _ := hex.DecodeString("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a") - txOutput := core.CompileWithSignatures(coin, txInputData2, [][]byte{signature}, [][]byte{publicKey}) + txOutput := core.CompileWithSignatures(coin, txInputData, [][]byte{signature}, [][]byte{publicKey}) fmt.Println("final txOutput proto: ", len(txOutput)) var output ethereum.SigningOutput diff --git a/samples/kmp/shared/build.gradle.kts b/samples/kmp/shared/build.gradle.kts index 3c96d189b58..d7ba445c535 100644 --- a/samples/kmp/shared/build.gradle.kts +++ b/samples/kmp/shared/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("com.trustwallet:wallet-core-kotlin:3.2.17") + implementation("com.trustwallet:wallet-core-kotlin:4.0.10") } } val commonTest by getting { diff --git a/samples/node/package-lock.json b/samples/node/package-lock.json index e0a6b4d8d20..2e8f7ed848c 100644 --- a/samples/node/package-lock.json +++ b/samples/node/package-lock.json @@ -140,11 +140,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, "node_modules/@types/node": { "version": "10.17.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", @@ -194,9 +189,9 @@ } }, "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/make-error": { "version": "1.3.6", @@ -205,9 +200,9 @@ "dev": true }, "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -220,13 +215,11 @@ "@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" + "long": "^5.0.0" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "engines": { + "node": ">=12.0.0" } }, "node_modules/protobufjs/node_modules/@types/node": { @@ -424,11 +417,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, "@types/node": { "version": "10.17.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", @@ -466,9 +454,9 @@ "dev": true }, "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "make-error": { "version": "1.3.6", @@ -477,9 +465,9 @@ "dev": true }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -491,9 +479,8 @@ "@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" + "long": "^5.0.0" }, "dependencies": { "@types/node": { diff --git a/samples/osx/README.md b/samples/osx/README.md index e3dd3a8f6df..a6a2323d3b2 100644 --- a/samples/osx/README.md +++ b/samples/osx/README.md @@ -2,7 +2,7 @@ ## Overview -This folder contains a simple but complete **iOS/macOS** sample application, for demostrating usage of the +This folder contains a simple but complete **iOS/macOS** sample application, for demonstrating usage of the [Wallet Core](https://github.com/trustwallet/wallet-core) library (part of [Trust Wallet](https://trustwallet.com)). ## DISCLAIMER diff --git a/samples/rust/Cargo.lock b/samples/rust/Cargo.lock index 038f6999084..b77fefe671f 100644 --- a/samples/rust/Cargo.lock +++ b/samples/rust/Cargo.lock @@ -52,23 +52,12 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -273,9 +262,9 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "rustix" -version = "0.36.11" +version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ "bitflags", "errno", @@ -359,49 +348,36 @@ dependencies = [ ] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-targets 0.42.2", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.0", ] [[package]] @@ -410,13 +386,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -425,38 +416,80 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/samples/rust/README.md b/samples/rust/README.md index 535990b95c1..237b65b34ae 100644 --- a/samples/rust/README.md +++ b/samples/rust/README.md @@ -20,11 +20,11 @@ cargo run - The app links with the wallet-core library (C/C++). - The `walletcore_iface.rs` file contains the interface definitions in Rust. -- Links with `TrustWalletCore`, `TrezorCrypto`, `protobuf`, and the platform libc (`c++` or `stdc++`). Build/link paramaters are in `build.rs`. +- Links with `TrustWalletCore`, `TrezorCrypto`, `protobuf`, and the platform libc (`c++` or `stdc++`). Build/link parameters are in `build.rs`. - Rust proto files are created during the build process, from the `.proto` files in wallet-core, into subfolder `src/wc_proto` (see `build.rs`). -- Notable dependecies: +- Notable dependencies: -- `protobuf` -- `lib` for C linking -- `hex` diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index ea05ed59875..20d31a6dd61 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -48,20 +48,23 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey& privateKey, Transaction& return createProtoOutput(signature, signedEncodedTx); } -Data Signer::buildRlpTxRaw(Data& txRaw, Data& sigRaw) { - auto rlpTxRaw = Data(); - auto signaturesList = Data(); - append(signaturesList, Ethereum::RLP::encode(sigRaw)); +Data Signer::buildRlpTxRaw(const Data& txRaw, const Data& sigRaw) { + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); - append(rlpTxRaw, Ethereum::RLP::encode(Identifiers::objectTagSignedTransaction)); - append(rlpTxRaw, Ethereum::RLP::encode(Identifiers::rlpMessageVersion)); - append(rlpTxRaw, Ethereum::RLP::encodeList(signaturesList)); - append(rlpTxRaw, Ethereum::RLP::encode(txRaw)); + rlpList->add_items()->set_number_u64(Identifiers::objectTagSignedTransaction); + rlpList->add_items()->set_number_u64(Identifiers::rlpMessageVersion); - return Ethereum::RLP::encodeList(rlpTxRaw); + // Append a list of signatures. + auto* signaturesList = rlpList->add_items()->mutable_list(); + signaturesList->add_items()->set_data(sigRaw.data(), sigRaw.size()); + + rlpList->add_items()->set_data(txRaw.data(), txRaw.size()); + + return Ethereum::RLP::encode(input); } -Data Signer::buildMessageToSign(Data& txRaw) { +Data Signer::buildMessageToSign(const Data& txRaw) { auto data = Data(); Data bytes(Identifiers::networkId.begin(), Identifiers::networkId.end()); append(data, bytes); diff --git a/src/Aeternity/Signer.h b/src/Aeternity/Signer.h index fd085b2fdb3..db49586d535 100644 --- a/src/Aeternity/Signer.h +++ b/src/Aeternity/Signer.h @@ -23,9 +23,9 @@ class Signer { private: static const uint8_t checkSumSize = 4; - static Data buildRlpTxRaw(Data& txRaw, Data& sigRaw); + static Data buildRlpTxRaw(const Data& txRaw, const Data& sigRaw); - static Data buildMessageToSign(Data& txRaw); + static Data buildMessageToSign(const Data& txRaw); static Proto::SigningOutput createProtoOutput(std::string& signature, const std::string& signedTx); diff --git a/src/Aeternity/Transaction.cpp b/src/Aeternity/Transaction.cpp index cb104daede2..90dd197b74b 100644 --- a/src/Aeternity/Transaction.cpp +++ b/src/Aeternity/Transaction.cpp @@ -12,21 +12,44 @@ namespace TW::Aeternity { +/// Aeternity network does not accept zero int values as rlp param, +/// instead empty byte array should be encoded +/// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera +EthereumRlp::Proto::RlpItem prepareSafeZero(const uint256_t& value) { + EthereumRlp::Proto::RlpItem item; + + if (value == 0) { + Data zeroValue{0}; + item.set_data(zeroValue.data(), zeroValue.size()); + } else { + auto valueData = store(value); + item.set_number_u256(valueData.data(), valueData.size()); + } + + return item; +} + /// RLP returns a byte serialized representation Data Transaction::encode() { - auto encoded = Data(); - append(encoded, Ethereum::RLP::encode(Identifiers::objectTagSpendTransaction)); - append(encoded, Ethereum::RLP::encode(Identifiers::rlpMessageVersion)); - append(encoded, Ethereum::RLP::encode(buildTag(sender_id))); - append(encoded, Ethereum::RLP::encode(buildTag(recipient_id))); - append(encoded, encodeSafeZero(amount)); - append(encoded, encodeSafeZero(fee)); - append(encoded, encodeSafeZero(ttl)); - append(encoded, encodeSafeZero(nonce)); - append(encoded, Ethereum::RLP::encode(payload)); - - const Data& raw = Ethereum::RLP::encodeList(encoded); - return raw; + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + auto senderIdTag = buildTag(sender_id); + auto recipientIdTag = buildTag(recipient_id); + + rlpList->add_items()->set_number_u64(Identifiers::objectTagSpendTransaction); + rlpList->add_items()->set_number_u64(Identifiers::rlpMessageVersion); + rlpList->add_items()->set_data(senderIdTag.data(), senderIdTag.size()); + rlpList->add_items()->set_data(recipientIdTag.data(), recipientIdTag.size()); + + *rlpList->add_items() = prepareSafeZero(amount); + *rlpList->add_items() = prepareSafeZero(fee); + *rlpList->add_items() = prepareSafeZero(ttl); + *rlpList->add_items() = prepareSafeZero(nonce); + + rlpList->add_items()->set_data(payload.data(), payload.size()); + + return Ethereum::RLP::encode(input); } TW::Data Transaction::buildTag(const std::string& address) { @@ -39,11 +62,4 @@ TW::Data Transaction::buildTag(const std::string& address) { return data; } -TW::Data Transaction::encodeSafeZero(uint256_t value) { - if (value == 0) { - return Ethereum::RLP::encode(Data{0}); - } - return Ethereum::RLP::encode(value); -} - } // namespace TW::Aeternity diff --git a/src/Aeternity/Transaction.h b/src/Aeternity/Transaction.h index 20efc9d5d79..f019395ecd6 100644 --- a/src/Aeternity/Transaction.h +++ b/src/Aeternity/Transaction.h @@ -52,13 +52,6 @@ class Transaction { //// buildIDTag assemble an id() object //// see https://github.com/aeternity/protocol/blob/epoch-v0.22.0/serializations.md#the-id-type static Data buildTag(const std::string& address); - - /// Aeternity network does not accept zero int values as rlp param, - /// instead empty byte array should be encoded - /// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera - static Data encodeSafeZero(uint256_t value); - - }; } // namespace TW::Aeternity diff --git a/src/Aion/RLP.h b/src/Aion/RLP.h index 5f0c8a1991c..a1995e11a60 100644 --- a/src/Aion/RLP.h +++ b/src/Aion/RLP.h @@ -17,20 +17,28 @@ namespace TW::Aion { +using boost::multiprecision::uint128_t; + /// Aion's RLP encoding for long numbers /// https://github.com/aionnetwork/aion/issues/680 struct RLP { - static Data encodeLong(boost::multiprecision::uint128_t l) noexcept { + static EthereumRlp::Proto::RlpItem prepareLong(uint128_t l) { + EthereumRlp::Proto::RlpItem item; + if ((l & 0x00000000FFFFFFFFL) == l) { - return Ethereum::RLP::encode(static_cast(l)); + auto u256 = store(l); + item.set_number_u256(u256.data(), u256.size()); + } else { + Data result(9); + result[0] = 0x80 + 8; + for (int i = 8; i > 0; i--) { + result[i] = (byte)(l & 0xFF); + l >>= 8; + } + item.set_raw_encoded(result.data(), result.size()); } - Data result(9); - result[0] = 0x80 + 8; - for (int i = 8; i > 0; i--) { - result[i] = (byte)(l & 0xFF); - l >>= 8; - } - return result; + + return item; } }; diff --git a/src/Aion/Transaction.cpp b/src/Aion/Transaction.cpp index 3cfad0b3d91..62f81e6b163 100644 --- a/src/Aion/Transaction.cpp +++ b/src/Aion/Transaction.cpp @@ -5,27 +5,42 @@ // file LICENSE at the root of the source code distribution tree. #include "Transaction.h" + +#include "Ethereum/RLP.h" #include "RLP.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" using namespace TW; using boost::multiprecision::uint128_t; namespace TW::Aion { +static const uint128_t gTransactionType = 1; + Data Transaction::encode() const noexcept { - auto encoded = Data(); - append(encoded, Ethereum::RLP::encode(nonce)); - append(encoded, Ethereum::RLP::encode(to.bytes)); - append(encoded, Ethereum::RLP::encode(amount)); - append(encoded, Ethereum::RLP::encode(payload)); - append(encoded, Ethereum::RLP::encode(timestamp)); - append(encoded, RLP::encodeLong(gasLimit)); - append(encoded, RLP::encodeLong(gasPrice)); - append(encoded, RLP::encodeLong(uint128_t(1))); // Aion transaction type + auto nonceData = store(nonce); + auto amountData = store(amount); + auto timestampData = store(timestamp); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u256(nonceData.data(), nonceData.size()); + rlpList->add_items()->set_data(to.bytes.data(), to.bytes.size()); + rlpList->add_items()->set_number_u256(amountData.data(), amountData.size()); + rlpList->add_items()->set_data(payload.data(), payload.size()); + rlpList->add_items()->set_number_u256(timestampData.data(), timestampData.size()); + + *rlpList->add_items() = RLP::prepareLong(gasLimit); + *rlpList->add_items() = RLP::prepareLong(gasPrice); + *rlpList->add_items() = RLP::prepareLong(gTransactionType); + if (!signature.empty()) { - append(encoded, Ethereum::RLP::encode(signature)); + rlpList->add_items()->set_data(signature.data(), signature.size()); } - return Ethereum::RLP::encodeList(encoded); + + return Ethereum::RLP::encode(input); } } // namespace TW::Aion diff --git a/src/Aptos/Address.cpp b/src/Aptos/Address.cpp deleted file mode 100644 index 18242423a9e..00000000000 --- a/src/Aptos/Address.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Author: Clement Doumergue -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" -#include "HexCoding.h" - -namespace TW::Aptos { - -Address::Address(const std::string& string) : Address::AptosAddress(string) { -} - -Address::Address(const PublicKey& publicKey): Address::AptosAddress(publicKey) { -} - -Data Address::getDigest(const PublicKey& publicKey) { - auto key_data = publicKey.bytes; - append(key_data, 0x00); - return key_data; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, Address addr) noexcept { - stream.add_bytes(addr.bytes.begin(), addr.bytes.end()); - return stream; -} - -} // namespace TW::Aptos diff --git a/src/Aptos/Address.h b/src/Aptos/Address.h deleted file mode 100644 index db735283ddf..00000000000 --- a/src/Aptos/Address.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Author: Clement Doumergue -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "BCS.h" -#include "Data.h" -#include "Move/Address.h" -#include "PublicKey.h" - -#include - -namespace TW::Aptos { - -class Address : public Move::Address { -public: - using AptosAddress = Move::Address; - using AptosAddress::size; - using AptosAddress::bytes; - - /// Initializes an Aptos address with a string representation. - explicit Address(const std::string& string); - - /// Initializes an Aptos address with a public key. - explicit Address(const PublicKey& publicKey); - - /// Constructor that allow factory programming; - Address() noexcept = default; - - Data getDigest(const PublicKey& publicKey); -}; - -constexpr inline bool operator==(const Address& lhs, const Address& rhs) noexcept { - return lhs.bytes == rhs.bytes; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, Address) noexcept; - -} // namespace TW::Aptos diff --git a/src/Aptos/Entry.cpp b/src/Aptos/Entry.cpp deleted file mode 100644 index 8c7fafced8e..00000000000 --- a/src/Aptos/Entry.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright 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" - -namespace TW::Aptos { - -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address::isValid(address); -} - -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address(publicKey).string(); -} - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - output = Signer::preImageHashes(input); - }); -} - -void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerSingleTemplate( - txInputData, signatures, publicKeys, - [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { - output = Signer::compile(input, signature, publicKey); - }); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/Entry.h b/src/Aptos/Entry.h index 2219499f5ac..750c5dba2a9 100644 --- a/src/Aptos/Entry.h +++ b/src/Aptos/Entry.h @@ -6,19 +6,13 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Aptos { /// Entry point for implementation of Aptos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public CoinEntry { -public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; - Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +class Entry final : public Rust::RustCoinEntry { }; } // namespace TW::Aptos diff --git a/src/Aptos/MoveTypes.cpp b/src/Aptos/MoveTypes.cpp deleted file mode 100644 index ccde47c9439..00000000000 --- a/src/Aptos/MoveTypes.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright 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 - -namespace TW::Aptos { - -Aptos::ModuleId::ModuleId(Address accountAddress, Identifier name) noexcept - : mAccountAddress(accountAddress), mName(std::move(name)) { -} - -Data ModuleId::accessVector() const noexcept { - BCS::Serializer serializer; - serializer << static_cast(gCodeTag) << mAccountAddress << mName; - return serializer.bytes; -} - -std::string ModuleId::string() const noexcept { - std::stringstream ss; - ss << mAccountAddress.string() << "::" << mName; - return ss.str(); -} - -std::string ModuleId::shortString() const noexcept { - std::stringstream ss; - ss << "0x" << mAccountAddress.shortString() << "::" << mName; - return ss.str(); -} - -Data StructTag::serialize(bool withResourceTag) const noexcept { - BCS::Serializer serializer; - if (withResourceTag) - { - serializer << gResourceTag; - } - serializer << mAccountAddress << mModule << mName << mTypeParams; - return serializer.bytes; -} - -StructTag::StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept - : mAccountAddress(accountAddress), mModule(std::move(module)), mName(std::move(name)), mTypeParams(std::move(typeParams)) { -} -std::string StructTag::string() const noexcept { - std::stringstream ss; - ss << "0x" << mAccountAddress.shortString() << "::" << mModule << "::" << mName; - if (!mTypeParams.empty()) { - ss << "<"; - ss << TypeTagToString(*mTypeParams.begin()); - std::for_each(begin(mTypeParams) + 1, end(mTypeParams), [&ss](auto&& cur) { - ss << ", " << TypeTagToString(cur); - }); - ss << ">"; - } - return ss.str(); -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept { - stream << Bool::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept { - stream << U8::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept { - stream << U64::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept { - stream << U128::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept { - stream << TAddress::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept { - stream << TSigner::value; - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept { - auto res = st.serialize(); - stream.add_bytes(begin(res), end(res)); - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& st) noexcept { - stream << TStructTag::value; - auto res = st.st.serialize(false); - stream.add_bytes(begin(res), end(res)); - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept { - stream << Vector::value; - for (auto&& cur: t.tags) { - stream << cur; - } - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept { - std::visit([&stream](auto&& arg) { stream << arg; }, t.tags); - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept { - stream << module.address() << module.name(); - return stream; -} -std::string TypeTagToString(const TypeTag& typeTag) noexcept { - auto visit_functor = [](const TypeTag::TypeTagVariant& value) -> std::string { - if (std::holds_alternative(value)) { - return "bool"; - } else if (std::holds_alternative(value)) { - return "u8"; - } else if (std::holds_alternative(value)) { - return "u64"; - } else if (std::holds_alternative(value)) { - return "u128"; - } else if (std::holds_alternative(value)) { - return "address"; - } else if (std::holds_alternative(value)) { - return "signer"; - } else if (auto* vectorData = std::get_if(&value); vectorData != nullptr && !vectorData->tags.empty()) { - std::stringstream ss; - ss << "vector<" << TypeTagToString(*vectorData->tags.begin()) << ">"; - return ss.str(); - } else if (auto* structData = std::get_if(&value); structData) { - return structData->string(); - } else if (auto* tStructData = std::get_if(&value); tStructData) { - return tStructData->st.string(); - } else { - return ""; - } - }; - - return std::visit(visit_functor, typeTag.tags); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/MoveTypes.h b/src/Aptos/MoveTypes.h deleted file mode 100644 index 26dfe1b8477..00000000000 --- a/src/Aptos/MoveTypes.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "Aptos/Address.h" -#include "BCS.h" -#include - -namespace TW::Aptos { - -constexpr std::uint8_t gCodeTag{0}; -constexpr std::uint8_t gResourceTag{1}; -using Identifier = std::string; - -class ModuleId { -public: - ///< Constructor - ModuleId(Address accountAddress, Identifier name) noexcept; - - ///< Getters - [[nodiscard]] const std::string& name() const noexcept { return mName; } - [[nodiscard]] const Address& address() const noexcept { return mAccountAddress; } - [[nodiscard]] Data accessVector() const noexcept; - [[nodiscard]] std::string string() const noexcept; - [[nodiscard]] std::string shortString() const noexcept; - -private: - Address mAccountAddress; - Identifier mName; -}; - -inline ModuleId gAptosAccountModule{Address::one(), "aptos_account"}; -inline ModuleId gAptosCoinModule{Address::one(), "coin"}; -inline ModuleId gAptosManagedCoinsModule{Address::one(), "managed_coin"}; -inline ModuleId gAptosTokenTransfersModule{Address::three(), "token_transfers"}; - -BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept; - -struct TypeTag; - -struct Bool { - static constexpr std::uint8_t value = 0; -}; -struct U8 { - static constexpr std::uint8_t value = 1; -}; -struct U64 { - static constexpr std::uint8_t value = 2; -}; -struct U128 { - static constexpr std::uint8_t value = 3; -}; -struct TAddress { - static constexpr std::uint8_t value = 4; -}; -struct TSigner { - static constexpr std::uint8_t value = 5; -}; -struct Vector { - static constexpr std::uint8_t value = 6; - std::vector tags; -}; - -class StructTag { -public: - explicit StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept; - [[nodiscard]] Data serialize(bool withResourceTag = true) const noexcept; - [[nodiscard]] ModuleId moduleID() const noexcept { return {mAccountAddress, mName}; }; - [[nodiscard]] std::string string() const noexcept; - -private: - Address mAccountAddress; - Identifier mModule; - Identifier mName; - std::vector mTypeParams; -}; - -// C++ limitation, the first StructTag will serialize with ResourceTag, the inner one will use the value 7 instead. Tweaking by wrapping the struct -struct TStructTag { - static constexpr std::uint8_t value = 7; - StructTag st; -}; - -struct TypeTag { - using TypeTagVariant = std::variant; - TypeTagVariant tags; -}; - -std::string TypeTagToString(const TypeTag& typeTag) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& t) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept; -static const TypeTag gTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address::one(), "aptos_coin", "AptosCoin", {})})}; -static const TypeTag gOfferNftTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address::three(), "token_transfers", "offer_script", {})})}; - -} // namespace TW::Aptos diff --git a/src/Aptos/Signer.cpp b/src/Aptos/Signer.cpp deleted file mode 100644 index 992d91dd6f6..00000000000 --- a/src/Aptos/Signer.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright 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 "Hash.h" -#include "MoveTypes.h" -#include "TransactionBuilder.h" -#include "TransactionPayload.h" - -namespace { -template -void serializeToArgs(std::vector& args, T&& toSerialize) { - TW::BCS::Serializer serializer; - serializer << std::forward(toSerialize); - args.emplace_back(serializer.bytes); -} -} // namespace - -namespace TW::Aptos { - -template -std::pair, nlohmann::json> commonTransferPayload(const TPayload& input) { - std::vector args; - serializeToArgs(args, Address(input.to())); - serializeToArgs(args, input.amount()); - nlohmann::json argsJson = nlohmann::json::array({input.to(), std::to_string(input.amount())}); - return std::make_pair(args, argsJson); -} - -TransactionPayload transferPayload(const Proto::SigningInput& input) { - auto&& [args, argsJson] = commonTransferPayload(input.transfer()); - TransactionPayload payload = EntryFunction(gAptosAccountModule, "transfer", {}, args, argsJson); - return payload; -} - -TransactionPayload createAccountPayload(const Proto::SigningInput& input) { - std::vector args; - serializeToArgs(args, Address(input.create_account().auth_key())); - nlohmann::json argsJson = nlohmann::json::array({input.create_account().auth_key()}); - TransactionPayload payload = EntryFunction(gAptosAccountModule, "create_account", {}, args, argsJson); - return payload; -} - -TransactionPayload claimNftPayload(const Proto::ClaimNftMessage& msg) { - std::vector args; - serializeToArgs(args, Address(msg.sender())); - serializeToArgs(args, Address(msg.creator())); - serializeToArgs(args, msg.collectionname()); - serializeToArgs(args, msg.name()); - serializeToArgs(args, msg.property_version()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - msg.sender(), - msg.creator(), - msg.collectionname(), - msg.name(), - std::to_string(msg.property_version()), - }); - // clang-format on - TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "claim_script", {}, args, argsJson); - return payload; -} - -TransactionPayload nftOfferPayload(const Proto::OfferNftMessage& msg) { - std::vector args; - serializeToArgs(args, Address(msg.receiver())); - serializeToArgs(args, Address(msg.creator())); - serializeToArgs(args, msg.collectionname()); - serializeToArgs(args, msg.name()); - serializeToArgs(args, msg.property_version()); - serializeToArgs(args, msg.amount()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - msg.receiver(), - msg.creator(), - msg.collectionname(), - msg.name(), - std::to_string(msg.property_version()), - std::to_string(msg.amount()) - }); - // clang-format on - TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "offer_script", {}, args, argsJson); - return payload; -} - -TransactionPayload tortugaClaimPayload(const std::string& smart_contract_address, const Proto::TortugaClaim& msg) { - std::vector args; - serializeToArgs(args, msg.idx()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - std::to_string(msg.idx()) - }); - // clang-format on - ModuleId tortugaStakeModule{Address(smart_contract_address), "stake_router"}; - TransactionPayload payload = EntryFunction(tortugaStakeModule, "claim", {}, args, argsJson); - return payload; -} - -TransactionPayload tortugaStakePayload(const std::string& smart_contract_address, const Proto::TortugaStake& msg) { - std::vector args; - serializeToArgs(args, msg.amount()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - std::to_string(msg.amount()) - }); - // clang-format on - ModuleId tortugaStakeModule{Address(smart_contract_address), "stake_router"}; - TransactionPayload payload = EntryFunction(tortugaStakeModule, "stake", {}, args, argsJson); - return payload; -} - -TransactionPayload tortugaUnStakePayload(const std::string& smart_contract_address, const Proto::TortugaUnstake& msg) { - std::vector args; - serializeToArgs(args, msg.amount()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - std::to_string(msg.amount()) - }); - // clang-format on - ModuleId tortugaStakeModule{Address(smart_contract_address), "stake_router"}; - TransactionPayload payload = EntryFunction(tortugaStakeModule, "unstake", {}, args, argsJson); - return payload; -} - -TransactionPayload cancelNftOfferPayload(const Proto::CancelOfferNftMessage& msg) { - std::vector args; - serializeToArgs(args, Address(msg.receiver())); - serializeToArgs(args, Address(msg.creator())); - serializeToArgs(args, msg.collectionname()); - serializeToArgs(args, msg.name()); - serializeToArgs(args, msg.property_version()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - msg.receiver(), - msg.creator(), - msg.collectionname(), - msg.name(), - std::to_string(msg.property_version()), - }); - // clang-format on - TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "cancel_offer_script", {}, args, argsJson); - return payload; -} - -TransactionPayload tokenTransferPayload(const Proto::SigningInput& input) { - - auto&& [args, argsJson] = commonTransferPayload(input.token_transfer()); - auto& function = input.token_transfer().function(); - TypeTag tokenTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), - function.module(), function.name(), {})})}; - TransactionPayload payload = EntryFunction(gAptosCoinModule, "transfer", {tokenTransferTag}, args, argsJson); - return payload; -} - -TransactionPayload tokenTransferCoinsPayload(const Proto::SigningInput& input) { - auto&& [args, argsJson] = commonTransferPayload(input.token_transfer_coins()); - auto& function = input.token_transfer_coins().function(); - TypeTag tokenTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), - function.module(), function.name(), {})})}; - TransactionPayload payload = EntryFunction(gAptosAccountModule, "transfer_coins", {tokenTransferTag}, args, argsJson); - return payload; -} - -TransactionPayload registerTokenPayload(const Proto::SigningInput& input) { - - auto& function = input.register_token().function(); - TypeTag tokenRegisterTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), - function.module(), function.name(), {})})}; - TransactionPayload payload = EntryFunction(gAptosManagedCoinsModule, "register", {tokenRegisterTag}, {}); - return payload; -} - -TransactionBasePtr buildBlindTx(const Proto::SigningInput& input) { - if (nlohmann::json j = nlohmann::json::parse(input.any_encoded(), nullptr, false); j.is_discarded()) { - auto blindBuilder = std::make_unique(); - blindBuilder->encodedCallHex(input.any_encoded()); - return blindBuilder; - } else { - auto txBuilder = std::make_unique(); - txBuilder->sender(Address(input.sender())) - .sequenceNumber(input.sequence_number()) - .payload(EntryFunction::from_json(j)) - .maxGasAmount(input.max_gas_amount()) - .gasUnitPrice(input.gas_unit_price()) - .expirationTimestampSecs(input.expiration_timestamp_secs()) - .chainId(static_cast(input.chain_id())); - return txBuilder; - } -} - -TransactionBasePtr buildTx(const Proto::SigningInput& input) { - if (!input.any_encoded().empty()) { - return buildBlindTx(input); - } - - auto nftPayloadFunctor = [](const Proto::NftMessage& nftMessage) { - switch (nftMessage.nft_transaction_payload_case()) { - case Proto::NftMessage::kOfferNft: - return nftOfferPayload(nftMessage.offer_nft()); - case Proto::NftMessage::kCancelOfferNft: - return cancelNftOfferPayload(nftMessage.cancel_offer_nft()); - case Proto::NftMessage::kClaimNft: - return claimNftPayload(nftMessage.claim_nft()); - case Proto::NftMessage::NFT_TRANSACTION_PAYLOAD_NOT_SET: - throw std::runtime_error("Nft message payload not set"); - } - }; - auto liquidStakingFunctor = [](const Proto::LiquidStaking& liquidStakingMessage) { - switch (liquidStakingMessage.liquid_stake_transaction_payload_case()) { - case Proto::LiquidStaking::kStake: - return tortugaStakePayload(liquidStakingMessage.smart_contract_address(), liquidStakingMessage.stake()); - case Proto::LiquidStaking::kUnstake: - return tortugaUnStakePayload(liquidStakingMessage.smart_contract_address(), liquidStakingMessage.unstake()); - case Proto::LiquidStaking::kClaim: - return tortugaClaimPayload(liquidStakingMessage.smart_contract_address(), liquidStakingMessage.claim()); - case Proto::LiquidStaking::LIQUID_STAKE_TRANSACTION_PAYLOAD_NOT_SET: - return TransactionPayload(); - } - }; - auto payloadFunctor = [&input, &nftPayloadFunctor, &liquidStakingFunctor]() { - switch (input.transaction_payload_case()) { - case Proto::SigningInput::kTransfer: { - return transferPayload(input); - } - case Proto::SigningInput::kTokenTransfer: { - return tokenTransferPayload(input); - } - case Proto::SigningInput::kNftMessage: { - return nftPayloadFunctor(input.nft_message()); - } - case Proto::SigningInput::kCreateAccount: { - return createAccountPayload(input); - } - case Proto::SigningInput::kRegisterToken: { - return registerTokenPayload(input); - } - case Proto::SigningInput::kLiquidStakingMessage: { - return liquidStakingFunctor(input.liquid_staking_message()); - } - case Proto::SigningInput::kTokenTransferCoins: { - return tokenTransferCoinsPayload(input); - } - case Proto::SigningInput::TRANSACTION_PAYLOAD_NOT_SET: - throw std::runtime_error("Transaction payload should be set"); - } - }; - auto txBuilder = std::make_unique(); - txBuilder->sender(Address(input.sender())) - .sequenceNumber(input.sequence_number()) - .payload(payloadFunctor()) - .maxGasAmount(input.max_gas_amount()) - .gasUnitPrice(input.gas_unit_price()) - .expirationTimestampSecs(input.expiration_timestamp_secs()) - .chainId(static_cast(input.chain_id())); - return txBuilder; -} - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { - return buildTx(input)->sign(input); -} - -TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) { - return buildTx(input)->preImage(); -} - -Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey) { - return buildTx(input)->compile(signature, publicKey); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/Signer.h b/src/Aptos/Signer.h deleted file mode 100644 index e0222515ca0..00000000000 --- a/src/Aptos/Signer.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust 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/Aptos.pb.h" -#include "../proto/TransactionCompiler.pb.h" - -namespace TW::Aptos { - -inline const Data gAptosSalt = data("APTOS::RawTransaction"); - -/// Helper class that performs Aptos transaction signing. -class Signer { -public: - /// Hide default constructor - Signer() = delete; - - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input); - - static TxCompiler::Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input); - - static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey); -}; - -} // namespace TW::Aptos diff --git a/src/Aptos/TransactionBuilder.h b/src/Aptos/TransactionBuilder.h deleted file mode 100644 index 46bf3fbaa12..00000000000 --- a/src/Aptos/TransactionBuilder.h +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "HexCoding.h" -#include "TransactionPayload.h" - -#include -#include - -namespace TW::Aptos { - -struct TransactionBase; - -using TransactionBasePtr = std::unique_ptr; - -struct TransactionBase { - virtual ~TransactionBase() = default; - - virtual TxCompiler::Proto::PreSigningOutput preImage() noexcept = 0; - - virtual Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) = 0; - - virtual Proto::SigningOutput sign(const Proto::SigningInput& input) = 0; -}; - -class BlindBuilder final : public TransactionBase { -public: - BlindBuilder() noexcept = default; - - BlindBuilder& encodedCallHex(const std::string& encodedCallHex) { - mEncodedCall = parse_hex(encodedCallHex); - return *this; - } - - TxCompiler::Proto::PreSigningOutput preImage() noexcept override { - TxCompiler::Proto::PreSigningOutput output; - // Aptos has no preImageHash. - output.set_data(mEncodedCall.data(), mEncodedCall.size()); - return output; - } - - Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) override { - Proto::SigningOutput output; - const auto& pubKeyData = publicKey.bytes; - - BCS::Serializer serializer; - serializer.add_bytes(begin(mEncodedCall), end(mEncodedCall)); - - output.set_raw_txn(mEncodedCall.data(), mEncodedCall.size()); - output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); - output.mutable_authenticator()->set_signature(signature.data(), signature.size()); - serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; - output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); - - // clang-format off - nlohmann::json json = { - {"type", "ed25519_signature"}, - {"public_key", hexEncoded(pubKeyData)}, - {"signature", hexEncoded(signature)} - }; - // clang-format on - output.set_json(json.dump()); - - return output; - } - - Proto::SigningOutput sign(const Proto::SigningInput& input) override { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - auto signature = privateKey.sign(mEncodedCall, TWCurveED25519); - return compile(signature, publicKey); - } - -private: - Data mEncodedCall; -}; - -// Standard transaction builder. -class TransactionBuilder final : public TransactionBase { -public: - TransactionBuilder() noexcept = default; - - TransactionBuilder& sender(Address sender) noexcept { - mSender = sender; - return *this; - } - - TransactionBuilder& sequenceNumber(std::uint64_t sequenceNumber) noexcept { - mSequenceNumber = sequenceNumber; - return *this; - } - - TransactionBuilder& payload(TransactionPayload payload) noexcept { - mPayload = std::move(payload); - return *this; - } - - TransactionBuilder& maxGasAmount(std::uint64_t maxGasAmount) noexcept { - mMaxGasAmount = maxGasAmount; - return *this; - } - - TransactionBuilder& gasUnitPrice(std::uint64_t gasUnitPrice) noexcept { - mGasUnitPrice = gasUnitPrice; - return *this; - } - - TransactionBuilder& expirationTimestampSecs(std::uint64_t expirationTimestampSecs) noexcept { - mExpirationTimestampSecs = expirationTimestampSecs; - return *this; - } - - TransactionBuilder& chainId(std::uint8_t chainId) noexcept { - mChainId = chainId; - return *this; - } - - BCS::Serializer prepareSerializer() noexcept { - BCS::Serializer serializer; - serializer << mSender << mSequenceNumber << mPayload << mMaxGasAmount << mGasUnitPrice << mExpirationTimestampSecs << mChainId; - return serializer; - } - - Data msgToSign() noexcept { - auto serialized = prepareSerializer().bytes; - auto preImageOutput = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size()); - append(preImageOutput, serialized); - return preImageOutput; - } - - TxCompiler::Proto::PreSigningOutput preImage() noexcept override { - TxCompiler::Proto::PreSigningOutput output; - auto signingMsg = msgToSign(); - // Aptos has no preImageHash. - output.set_data(signingMsg.data(), signingMsg.size()); - return output; - } - - Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) noexcept override { - Proto::SigningOutput output; - const auto& pubKeyData = publicKey.bytes; - - auto serializer = prepareSerializer(); - - output.set_raw_txn(serializer.bytes.data(), serializer.bytes.size()); - output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); - output.mutable_authenticator()->set_signature(signature.data(), signature.size()); - - serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; - output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); - - // https://fullnode.devnet.aptoslabs.com/v1/spec#/operations/submit_transaction - // clang-format off - nlohmann::json json = { - {"sender", mSender.string()}, - {"sequence_number", std::to_string(mSequenceNumber)}, - {"max_gas_amount", std::to_string(mMaxGasAmount)}, - {"gas_unit_price", std::to_string(mGasUnitPrice)}, - {"expiration_timestamp_secs", std::to_string(mExpirationTimestampSecs)}, - {"payload", payloadToJson(mPayload)}, - {"signature", { - {"type", "ed25519_signature"}, - {"public_key", hexEncoded(pubKeyData)}, - {"signature", hexEncoded(signature)}} - } - }; - // clang-format on - output.set_json(json.dump()); - return output; - } - - Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept override { - auto signingMsg = msgToSign(); - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto signature = privateKey.sign(signingMsg, TWCurveED25519); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - return compile(signature, publicKey); - } - -private: - Address mSender{}; - std::uint64_t mSequenceNumber{}; - TransactionPayload mPayload{}; - std::uint64_t mMaxGasAmount{}; - std::uint64_t mGasUnitPrice{}; - std::uint64_t mExpirationTimestampSecs{}; - std::uint8_t mChainId{}; -}; - -} // namespace TW::Aptos diff --git a/src/Aptos/TransactionPayload.cpp b/src/Aptos/TransactionPayload.cpp deleted file mode 100644 index e3bf73bccb7..00000000000 --- a/src/Aptos/TransactionPayload.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "rust/bindgen/WalletCoreRSBindgen.h" -#include -#include - -namespace TW::Aptos { - -EntryFunction::EntryFunction(ModuleId module, Identifier function, std::vector tyArgs, std::vector args, nlohmann::json jsonArgs) noexcept - : mModule(std::move(module)), mFunction(std::move(function)), mTyArgs(std::move(tyArgs)), mArgs(std::move(args)), mJsonArgs(std::move(jsonArgs)) { -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const EntryFunction& entryFunction) noexcept { - stream << entryFunction.module() << entryFunction.function() << entryFunction.tyArgs() << entryFunction.args(); - return stream; -} - -nlohmann::json payloadToJson(const TransactionPayload& payload) { - auto visit_functor = [](const TransactionPayload& value) -> nlohmann::json { - if (auto* entryFunction = std::get_if(&value); entryFunction) { - return entryFunction->json(); - } else { - return {}; - } - }; - - return std::visit(visit_functor, payload); -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, [[maybe_unused]] const Script& script) noexcept { - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, [[maybe_unused]] const ModuleBundle& moduleBundle) noexcept { - return stream; -} - -nlohmann::json EntryFunction::json() const noexcept { - nlohmann::json tyArgsJson = nlohmann::json::array(); - for (auto&& cur : mTyArgs) { - tyArgsJson.emplace_back(TypeTagToString(cur)); - } - // clang-format off - nlohmann::json out = { - {"type", "entry_function_payload"}, - {"function", mModule.shortString() + "::" + mFunction}, - {"type_arguments", tyArgsJson}, - {"arguments", mJsonArgs.empty() ? nlohmann::json::array() : mJsonArgs} - }; - // clang-format on - return out; -} - -EntryFunction EntryFunction::from_json(const nlohmann::json& payload) noexcept { - auto splitFunctor = [](std::string s, std::string_view delimiter) { - size_t pos_start = 0, pos_end, delim_len = delimiter.size(); - std::string token; - std::vector output; - - while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { - token = s.substr(pos_start, pos_end - pos_start); - pos_start = pos_end + delim_len; - output.emplace_back(token); - } - - output.emplace_back(s.substr(pos_start)); - return output; - }; - auto functionSplitted = splitFunctor(payload.at("function").get(), "::"); - auto moduleId = ModuleId(Address(functionSplitted[0]), functionSplitted[1]); - std::vector args; - for (auto&& cur : payload.at("arguments")) { - auto curStr = cur.get(); - auto res = Rust::parse_function_argument_to_bcs(curStr.c_str()); - if (res.code != Rust::OK_CODE) { - // TODO consider exiting this function. - args.emplace_back(); - continue; - } - args.emplace_back(parse_hex(res.result)); - Rust::free_string(res.result); - } - - std::vector tags; - - for (auto&& cur : payload.at("type_arguments")) { - auto curStr = cur.get(); - switch (Rust::parse_type_tag(curStr.c_str())) { - case Rust::ETypeTag::Bool: - break; - case Rust::ETypeTag::U8: - break; - case Rust::ETypeTag::U64: - break; - case Rust::ETypeTag::U128: - break; - case Rust::ETypeTag::Address: - break; - case Rust::ETypeTag::Signer: - break; - case Rust::ETypeTag::Vector: - break; - case Rust::ETypeTag::Struct: { - auto structSplitted = splitFunctor(curStr, "::"); - auto addr = Address(structSplitted[0]); - TypeTag tag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(addr, structSplitted[1], structSplitted[2], {})})}; - tags.emplace_back(tag); - break; - } - case Rust::ETypeTag::Error: - break; - default: - break; - } - } - - return EntryFunction(moduleId, functionSplitted[2], tags, {args}, payload.at("arguments")); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/TransactionPayload.h b/src/Aptos/TransactionPayload.h deleted file mode 100644 index 6041fc0ed8d..00000000000 --- a/src/Aptos/TransactionPayload.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include -#include - -namespace TW::Aptos { - -/// Call a Move entry function. -class EntryFunction { -public: - explicit EntryFunction(ModuleId module, Identifier function, std::vector tyArgs, std::vector args, nlohmann::json jsonArgs = {}) noexcept; - [[nodiscard]] const ModuleId& module() const noexcept { return mModule; } - [[nodiscard]] const Identifier& function() const noexcept { return mFunction; } - [[nodiscard]] const std::vector& tyArgs() const noexcept { return mTyArgs; } - [[nodiscard]] const std::vector& args() const noexcept { return mArgs; } - [[nodiscard]] nlohmann::json json() const noexcept; - static EntryFunction from_json(const nlohmann::json& json) noexcept; - -private: - ModuleId mModule; - Identifier mFunction; - std::vector mTyArgs; - std::vector mArgs; - nlohmann::json mJsonArgs; -}; - - -class Script { -}; - -class ModuleBundle { -}; - -BCS::Serializer& operator<<(BCS::Serializer& stream, const EntryFunction& entryFunction) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const Script& script) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleBundle& moduleBundle) noexcept; -using TransactionPayload = std::variant; -nlohmann::json payloadToJson(const TransactionPayload& payload); - -} // namespace TW::Aptos diff --git a/src/BCS.cpp b/src/BCS.cpp deleted file mode 100644 index fb6015cdf1b..00000000000 --- a/src/BCS.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Created by Clément Doumergue - -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "BCS.h" - -namespace TW::BCS { - -Serializer& operator<<(Serializer& stream, std::byte b) noexcept { - stream.add_byte(b); - return stream; -} - -Serializer& operator<<(Serializer& stream, uleb128 t) noexcept { - integral auto value = t.value; - - while (value >= 0x80) { - // Add the 7 lowest bits of data and set highest bit to 1 - stream << static_cast((value & 0x7f) | 0x80); - value >>= 7; - } - - // Add the remaining bits of data (highest bit is already 0 at this point) - stream << static_cast(value); - return stream; -} - -Serializer& operator<<(Serializer& stream, std::string_view sv) noexcept { - stream << uleb128{static_cast(sv.size())}; - stream.add_bytes(sv.begin(), sv.end()); - return stream; -} - -Serializer& operator<<(Serializer& stream, std::nullopt_t) noexcept { - stream << false; - return stream; -} - -} // namespace TW::BCS diff --git a/src/BCS.h b/src/BCS.h deleted file mode 100644 index 563c204b8cb..00000000000 --- a/src/BCS.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Created by Clément Doumergue - -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "Data.h" -#include "concepts/tw_concepts.h" - -namespace TW::BCS { - -/// Implementation of BCS encoding (as specified by the Diem project, see github.com/diem/bcs#detailed-specifications) - -struct Serializer { - Data bytes; - - void add_byte(std::byte b) noexcept { - bytes.emplace_back(static_cast(b)); - } - - template - void add_bytes(Iterator first, Iterator last) noexcept { - std::transform(first, last, std::back_inserter(bytes), [](auto&& c) { - return static_cast(c); - }); - } - - void clear() noexcept { - bytes.clear(); - } -}; - -struct uleb128 { - uint32_t value; -}; - -namespace details { - -template -concept aggregate_struct = std::is_class_v> && std::is_aggregate_v>; - -template -concept map_container = requires(T t) { - typename T::key_type; - typename T::mapped_type; - { std::declval().size() } -> std::same_as; - }; - -template - requires integral || - floating_point || - std::same_as || - std::same_as || - std::same_as || - aggregate_struct || - map_container -struct is_serializable { - static constexpr auto value = true; -}; - -template -struct is_serializable> { - static constexpr auto value = is_serializable::value; -}; - -template -struct is_serializable> { - static constexpr auto value = (is_serializable::value && ...); -}; - -template -struct is_serializable> { - static constexpr auto value = is_serializable::value && is_serializable::value; -}; - -template -struct is_serializable> { - static constexpr auto value = is_serializable::value; -}; - -template -struct is_serializable> { - static constexpr auto value = (is_serializable::value && ...); -}; - -template -Serializer& serialize_integral_impl(Serializer& stream, T t, std::index_sequence) noexcept { - const char* bytes = reinterpret_cast(&t); - // Add each byte in little-endian order - return (stream << ... << static_cast(bytes[Is])); -} - -template -Serializer& serialize_tuple_impl(Serializer& stream, const T& t, std::index_sequence) noexcept { - return (stream << ... << std::get(t)); -} - -template -struct dependent_false { - static constexpr auto value = false; -}; - -template -constexpr auto to_tuple(T&& t) { - if constexpr (std::is_empty_v) { - return std::make_tuple(); - } else if constexpr (requires { [&t] { auto&& [a0] = t; }; }) { - auto&& [a0] = std::forward(t); - return std::make_tuple(a0); - } else if constexpr (requires { [&t] { auto&& [a0, a1] = t; }; }) { - auto&& [a0, a1] = std::forward(t); - return std::make_tuple(a0, a1); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2] = t; }; }) { - auto&& [a0, a1, a2] = std::forward(t); - return std::make_tuple(a0, a1, a2); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3] = t; }; }) { - auto&& [a0, a1, a2, a3] = std::forward(t); - return std::make_tuple(a0, a1, a2, a3); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3, a4] = t; }; }) { - auto&& [a0, a1, a2, a3, a4] = std::forward(t); - return std::make_tuple(a0, a1, a2, a3, a4); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3, a4, a5] = t; }; }) { - auto&& [a0, a1, a2, a3, a4, a5] = std::forward(t); - return std::make_tuple(a0, a1, a2, a3, a4, a5); - } else { - static_assert(dependent_false::value, "the structure has more than 6 members"); - } -} - -template -Serializer& serialize_struct_impl(Serializer& stream, const T& t) noexcept { - return stream << to_tuple(t); -} -} // namespace details - -template -concept PrimitiveSerializable = details::is_serializable::value; - -template -concept CustomSerializable = requires(T t) { - { std::declval() << t } -> std::same_as; - }; - -template -concept Serializable = PrimitiveSerializable || CustomSerializable; - -Serializer& operator<<(Serializer& stream, std::byte b) noexcept; - -template -Serializer& operator<<(Serializer& stream, T t) noexcept { - return details::serialize_integral_impl(stream, t, std::make_index_sequence{}); -} - -Serializer& operator<<(Serializer& stream, uleb128 t) noexcept; - -Serializer& operator<<(Serializer& stream, std::string_view sv) noexcept; - -template -Serializer& operator<<(Serializer& stream, const std::optional o) noexcept { - if (o.has_value()) { - stream << true; - stream << o.value(); - } else { - stream << false; - } - return stream; -} - -Serializer& operator<<(Serializer& stream, std::nullopt_t) noexcept; - -template -Serializer& operator<<(Serializer& stream, const std::tuple& t) noexcept { - return details::serialize_tuple_impl(stream, t, std::make_index_sequence{}); -} - -template -Serializer& operator<<(Serializer& stream, const std::pair& t) noexcept { - return details::serialize_tuple_impl(stream, t, std::make_index_sequence<2>{}); -} - -template -Serializer& operator<<(Serializer& stream, const T& t) noexcept { - return details::serialize_struct_impl(stream, t); -} - -template -Serializer& operator<<(Serializer& stream, const std::vector& t) noexcept { - stream << uleb128{static_cast(t.size())}; - for (auto&& cur: t) { - stream << cur; - } - return stream; -} - -template -Serializer& operator<<(Serializer& stream, const std::variant& t) noexcept { - stream << uleb128{static_cast(t.index())}; - std::visit([&stream](auto&& value) { stream << value; }, t); - return stream; -} - -template -Serializer& operator<<(Serializer& stream, const T& t) noexcept { - stream << uleb128{static_cast(t.size())}; - for (auto&& [k, v] : t) { - stream << std::make_tuple(k, v); - } - return stream; -} - -} // namespace TW::BCS diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 0122f6d5eb6..86bde0a6669 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -530,7 +530,7 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c Proto::TransactionOutput Script::buildBRC20InscribeTransfer(const std::string& ticker, uint64_t amount, const Data& publicKey) { TW::Bitcoin::Proto::TransactionOutput out; - Rust::CByteArrayWrapper res = TW::Rust::tw_build_brc20_transfer_inscription(ticker.data(), amount, 0, publicKey.data(), publicKey.size()); + Rust::CByteArrayWrapper res = TW::Rust::tw_bitcoin_legacy_build_brc20_transfer_inscription(ticker.data(), amount, 0, publicKey.data(), publicKey.size()); auto result = res.data; out.ParseFromArray(result.data(), static_cast(result.size())); return out; @@ -538,7 +538,7 @@ Proto::TransactionOutput Script::buildBRC20InscribeTransfer(const std::string& t Proto::TransactionOutput Script::buildOrdinalNftInscription(const std::string& mimeType, const Data& payload, const Data& publicKey) { TW::Bitcoin::Proto::TransactionOutput out; - Rust::CByteArrayWrapper res = TW::Rust::tw_bitcoin_build_nft_inscription( + Rust::CByteArrayWrapper res = TW::Rust::tw_bitcoin_legacy_build_nft_inscription( mimeType.data(), payload.data(), payload.size(), diff --git a/src/Bitcoin/Signer.cpp b/src/Bitcoin/Signer.cpp index ff33e295c98..0781237f862 100644 --- a/src/Bitcoin/Signer.cpp +++ b/src/Bitcoin/Signer.cpp @@ -25,7 +25,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, std::optiona Proto::SigningOutput output; if (input.is_it_brc_operation()) { auto serializedInput = data(input.SerializeAsString()); - Rust::CByteArrayWrapper res = Rust::tw_taproot_build_and_sign_transaction(serializedInput.data(), serializedInput.size()); + Rust::CByteArrayWrapper res = Rust::tw_bitcoin_legacy_taproot_build_and_sign_transaction(serializedInput.data(), serializedInput.size()); output.ParseFromArray(res.data.data(), static_cast(res.data.size())); return output; } diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index 46dd2f4ba93..3a0cc8a4ac6 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -239,7 +239,7 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size } std::optional Transaction::calculateFee(const Data& encoded, uint64_t satVb) { - Rust::CUInt64ResultWrapper res = Rust::tw_bitcoin_calculate_transaction_fee(encoded.data(), encoded.size(), satVb); + Rust::CUInt64ResultWrapper res = Rust::tw_bitcoin_legacy_calculate_transaction_fee(encoded.data(), encoded.size(), satVb); if (res.isErr()) { return std::nullopt; } diff --git a/src/Coin.cpp b/src/Coin.cpp index d7a0d45a2a8..943324470a7 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -65,6 +65,9 @@ #include "TheOpenNetwork/Entry.h" #include "Sui/Entry.h" #include "Greenfield/Entry.h" +#include "InternetComputer/Entry.h" +#include "NativeEvmos/Entry.h" +#include "NativeInjective/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -121,6 +124,9 @@ Hedera::Entry HederaDP; TheOpenNetwork::Entry tonDP; Sui::Entry SuiDP; Greenfield::Entry GreenfieldDP; +InternetComputer::Entry InternetComputerDP; +NativeEvmos::Entry NativeEvmosDP; +NativeInjective::Entry NativeInjectiveDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -179,6 +185,9 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainTheOpenNetwork: entry = &tonDP; break; case TWBlockchainSui: entry = &SuiDP; break; case TWBlockchainGreenfield: entry = &GreenfieldDP; break; + case TWBlockchainInternetComputer: entry = &InternetComputerDP; break; + case TWBlockchainNativeEvmos: entry = &NativeEvmosDP; break; + case TWBlockchainNativeInjective: entry = &NativeInjectiveDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; @@ -237,7 +246,7 @@ namespace TW::internal { assert(dispatcher != nullptr); return dispatcher->normalizeAddress(coin, address); } -} +} // namespace TW::internal std::string TW::normalizeAddress(TWCoinType coin, const string& address) {; if (!TW::validateAddress(coin, address)) { diff --git a/src/CoinEntry.cpp b/src/CoinEntry.cpp index cc08f2787a7..f8ce744377b 100644 --- a/src/CoinEntry.cpp +++ b/src/CoinEntry.cpp @@ -6,7 +6,10 @@ #include "CoinEntry.h" #include "Coin.h" +#include "HexCoding.h" +#include "rust/Wrapper.h" #include +#include namespace TW { diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 01b06421f25..2886108b0d9 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -5,72 +5,35 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" -#include #include "Address.h" -#include "Signer.h" + +#include +#include +#include using namespace TW; using namespace std; namespace TW::Cosmos { -bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { - if (auto* hrp = std::get_if(&addressPrefix); hrp) { - return Address::isValid(address, *hrp); - } - return Address::isValid(coin, address); -} +// TODO call `signRustJSON` when it's done. +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { - if (std::holds_alternative(addressPrefix)) { - const std::string hrp = std::get(addressPrefix); - if (!hrp.empty()) { - return Address(hrp, publicKey, coin).string(); - } - } - return Address(coin, publicKey).string(); -} + auto inputData = data(input.SerializeAsString()); + Data dataOut; + sign(coin, inputData, dataOut); -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { - Address addr; - if (!Address::decode(address, addr)) { - return Data(); + if (dataOut.empty()) { + return {}; } - return addr.getKeyHash(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - auto serializedOut = Signer::sign(input, coin).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key, coin); -} - -Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [&coin](const auto& input, auto& output) { - auto pkVec = Data(input.public_key().begin(), input.public_key().end()); - auto preimage = Signer().signaturePreimage(input, pkVec, coin); - auto isEvmCosmosChain = [coin]() { - return coin == TWCoinTypeNativeInjective || coin == TWCoinTypeNativeEvmos || coin == TWCoinTypeNativeCanto; - }; - auto imageHash = isEvmCosmosChain() ? Hash::keccak256(preimage) : Hash::sha256(preimage); - output.set_data(preimage.data(), preimage.size()); - output.set_data_hash(imageHash.data(), imageHash.size()); - }); -} + Proto::SigningOutput output; + output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerSingleTemplate( - txInputData, signatures, publicKeys, - [coin](const auto& input, auto& output, const auto& signature, const auto& publicKey) { - auto signedTx = Signer().encodeTransaction(input, signature, publicKey, coin); - output.set_serialized(signedTx.data(), signedTx.size()); - }); + return output.json(); } } // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index 4bc3261f9c5..66b40cb1403 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -6,23 +6,17 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Cosmos { /// Entry point for implementation of Cosmos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry : public CoinEntry { +class Entry : public Rust::RustCoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const final; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; - Data addressToData(TWCoinType coin, const std::string& address) const final; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + ~Entry() override = default; bool supportsJSONSigning() const final { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; - - Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; }; } // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.cpp b/src/Cosmos/JsonSerialization.cpp deleted file mode 100644 index d08c4fa362a..00000000000 --- a/src/Cosmos/JsonSerialization.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "JsonSerialization.h" -#include "ProtobufSerialization.h" -#include "../Cosmos/Address.h" -#include "../proto/Cosmos.pb.h" -#include "Base64.h" -#include "PrivateKey.h" - -using namespace TW; - -namespace TW::Cosmos::Json { - -using json = nlohmann::json; -using string = std::string; - -const string TYPE_PREFIX_MSG_SEND = "cosmos-sdk/MsgSend"; -const string TYPE_PREFIX_MSG_DELEGATE = "cosmos-sdk/MsgDelegate"; -const string TYPE_PREFIX_MSG_UNDELEGATE = "cosmos-sdk/MsgUndelegate"; -const string TYPE_PREFIX_MSG_REDELEGATE = "cosmos-sdk/MsgBeginRedelegate"; -const string TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS = "cosmos-sdk/MsgSetWithdrawAddress"; -const string TYPE_PREFIX_MSG_WITHDRAW_REWARD = "cosmos-sdk/MsgWithdrawDelegationReward"; -const string TYPE_PREFIX_PUBLIC_KEY = "tendermint/PubKeySecp256k1"; -const string TYPE_EVMOS_PREFIX_PUBLIC_KEY = "ethermint/PubKeyEthSecp256k1"; -const string TYPE_PREFIX_WASM_MSG_EXECUTE = "wasm/MsgExecuteContract"; - -static inline std::string coinTypeToPrefixPublicKey(TWCoinType coin) noexcept { - if (coin == TWCoinTypeNativeEvmos) { - return TYPE_EVMOS_PREFIX_PUBLIC_KEY; - } - return TYPE_PREFIX_PUBLIC_KEY; -} - -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "block"; - case Proto::BroadcastMode::ASYNC: - return "async"; - default: return "sync"; - } -} - -static json broadcastJSON(json& j, Proto::BroadcastMode mode) { - return { - {"tx", j}, - {"mode", broadcastMode(mode)} - }; -} - -static json amountJSON(const Proto::Amount& amount) { - return { - {"amount", amount.amount()}, - {"denom", amount.denom()} - }; -} - -static json amountsJSON(const ::google::protobuf::RepeatedPtrField& amounts) { - json j = json::array(); - for (auto& amount : amounts) { - j.push_back(amountJSON(amount)); - } - return j; -} - -static json feeJSON(const Proto::Fee& fee) { - json js = json::array(); - - for (auto& amount : fee.amounts()) { - js.push_back(amountJSON(amount)); - } - - return { - {"amount", js}, - {"gas", std::to_string(fee.gas())} - }; -} - -static json messageSend(const Proto::Message_Send& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_SEND : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountsJSON(message.amounts())}, - {"from_address", message.from_address()}, - {"to_address", message.to_address()} - }} - }; -} - -static json messageDelegate(const Proto::Message_Delegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_DELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageUndelegate(const Proto::Message_Undelegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_UNDELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageRedelegate(const Proto::Message_BeginRedelegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_REDELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_src_address", message.validator_src_address()}, - {"validator_dst_address", message.validator_dst_address()}, - }} - }; -} - -static json messageWithdrawReward(const Proto::Message_WithdrawDelegationReward& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_WITHDRAW_REWARD : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageSetWithdrawAddress(const Proto::Message_SetWithdrawAddress& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"delegator_address", message.delegator_address()}, - {"withdraw_address", message.withdraw_address()} - }} - }; -} - - -// This method not only support token transfer, but also support all other types of contract call. -// https://docs.terra.money/Tutorials/Smart-contracts/Manage-CW20-tokens.html#interacting-with-cw20-contract -static json messageExecuteContract(const Proto::Message_ExecuteContract& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_WASM_MSG_EXECUTE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"sender", message.sender()}, - {"contract", message.contract()}, - {"execute_msg", message.execute_msg()}, - {"coins", amountsJSON(message.coins())} - }} - }; -} - -json messageWasmTerraTransfer(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { - return { - {"type", TYPE_PREFIX_WASM_MSG_EXECUTE}, - {"value", - { - {"sender", msg.sender_address()}, - {"contract", msg.contract_address()}, - {"execute_msg", Protobuf::wasmTerraExecuteTransferPayload(msg)}, - {"coins", json::array()} // used in case you are sending native tokens along with this message - } - } - }; -} - -static json messageRawJSON(const Proto::Message_RawJSON& message) { - return { - {"type", message.type()}, - {"value", json::parse(message.value())}, - }; -} - -static json messagesJSON(const Proto::SigningInput& input) { - json j = json::array(); - for (auto& msg : input.messages()) { - if (msg.has_send_coins_message()) { - j.push_back(messageSend(msg.send_coins_message())); - } else if (msg.has_stake_message()) { - j.push_back(messageDelegate(msg.stake_message())); - } else if (msg.has_unstake_message()) { - j.push_back(messageUndelegate(msg.unstake_message())); - } else if (msg.has_withdraw_stake_reward_message()) { - j.push_back(messageWithdrawReward(msg.withdraw_stake_reward_message())); - } else if (msg.has_set_withdraw_address_message()) { - j.push_back(messageSetWithdrawAddress(msg.set_withdraw_address_message())); - } else if (msg.has_restake_message()) { - j.push_back(messageRedelegate(msg.restake_message())); - } else if (msg.has_raw_json_message()) { - j.push_back(messageRawJSON(msg.raw_json_message())); - } else if (msg.has_execute_contract_message()) { - j.push_back(messageExecuteContract(msg.execute_contract_message())); - } else if (msg.has_transfer_tokens_message()) { - assert(false); // not suppored, use protobuf serialization - return json::array(); - } else if ((msg.has_wasm_terra_execute_contract_transfer_message())) { - j.push_back(messageWasmTerraTransfer(msg.wasm_terra_execute_contract_transfer_message())); - } else if (msg.has_transfer_tokens_message() || msg.has_wasm_terra_execute_contract_generic()) { - assert(false); // not supported, use protobuf serialization - return json::array(); - } - } - return j; -} - -json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin) { - return { - {"pub_key", { - {"type", coinTypeToPrefixPublicKey(coin)}, - {"value", Base64::encode(pubkey)} - }}, - {"signature", Base64::encode(signature)} - }; -} - -json signaturePreimageJSON(const Proto::SigningInput& input) { - return { - {"account_number", std::to_string(input.account_number())}, - {"chain_id", input.chain_id()}, - {"fee", feeJSON(input.fee())}, - {"memo", input.memo()}, - {"msgs", messagesJSON(input)}, - {"sequence", std::to_string(input.sequence())} - }; -} - - -json transactionJSON(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { - json tx = { - {"fee", feeJSON(input.fee())}, - {"memo", input.memo()}, - {"msg", messagesJSON(input)}, - {"signatures", json::array({ - signatureJSON(signature, Data(publicKey.bytes), coin) - })} - }; - return broadcastJSON(tx, input.mode()); -} - -json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin) { - auto privateKey = PrivateKey(input.private_key()); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - - return transactionJSON(input, publicKey, signature, coin); -} - -std::string buildJsonTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { - return transactionJSON(input, publicKey, signature, coin).dump(); -} - -} // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.h b/src/Cosmos/JsonSerialization.h deleted file mode 100644 index c8458bddf6b..00000000000 --- a/src/Cosmos/JsonSerialization.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Cosmos.pb.h" -#include -#include - -extern const std::string TYPE_PREFIX_MSG_SEND; -extern const std::string TYPE_PREFIX_MSG_TRANSFER; -extern const std::string TYPE_PREFIX_MSG_DELEGATE; -extern const std::string TYPE_PREFIX_MSG_UNDELEGATE; -extern const std::string TYPE_PREFIX_MSG_REDELEGATE; -extern const std::string TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS; -extern const std::string TYPE_PREFIX_MSG_WITHDRAW_REWARD; -extern const std::string TYPE_PREFIX_PUBLIC_KEY; - -namespace TW::Cosmos::Json { - -using string = std::string; -using json = nlohmann::json; - -json signaturePreimageJSON(const Proto::SigningInput& input); -json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin); -json transactionJSON(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); -std::string buildJsonTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); -json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin); - -} // namespace TW::Cosmos::json diff --git a/src/Cosmos/Protobuf/tx.proto b/src/Cosmos/Protobuf/tx.proto index b7973cda4bf..74e17ba25d7 100644 --- a/src/Cosmos/Protobuf/tx.proto +++ b/src/Cosmos/Protobuf/tx.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package cosmos; +package cosmos.tx.v1beta1; // Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/tx/v1beta1/tx.proto diff --git a/src/Cosmos/ProtobufSerialization.cpp b/src/Cosmos/ProtobufSerialization.cpp deleted file mode 100644 index 657fe49cbca..00000000000 --- a/src/Cosmos/ProtobufSerialization.cpp +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ProtobufSerialization.h" -#include "JsonSerialization.h" -#include "../proto/Cosmos.pb.h" -#include "Protobuf/coin.pb.h" -#include "Protobuf/bank_tx.pb.h" -#include "Protobuf/cosmwasm_wasm_v1_tx.pb.h" -#include "Protobuf/distribution_tx.pb.h" -#include "Protobuf/staking_tx.pb.h" -#include "Protobuf/authz_tx.pb.h" -#include "Protobuf/tx.pb.h" -#include "Protobuf/stride_liquid_staking.pb.h" -#include "Protobuf/gov_tx.pb.h" -#include "Protobuf/crypto_secp256k1_keys.pb.h" -#include "Protobuf/terra_wasm_v1beta1_tx.pb.h" -#include "Protobuf/ibc_applications_transfer_tx.pb.h" -#include "Protobuf/thorchain_bank_tx.pb.h" -#include "Protobuf/ethermint_keys.pb.h" -#include "Protobuf/injective_keys.pb.h" - -#include "PrivateKey.h" -#include "Data.h" -#include "Hash.h" -#include "Base64.h" -#include "uint256.h" - -using namespace TW; - -namespace TW::Cosmos::Protobuf { - -namespace internal { - -// Some of the Cosmos blockchains use different public key types for address deriving and transaction signing. -// `registry.json` contains the public key required to derive an address, -// while this function prepares the given public key to use it for transaction signing/compiling. -inline PublicKey preparePublicKey(const PublicKey& publicKey, TWCoinType coin) { - return coin == TWCoinTypeNativeEvmos ? publicKey.compressed() : publicKey; -} - -} // namespace internal - -using json = nlohmann::json; -using string = std::string; -const auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com' - -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "BROADCAST_MODE_BLOCK"; - case Proto::BroadcastMode::ASYNC: - return "BROADCAST_MODE_ASYNC"; - default: return "BROADCAST_MODE_SYNC"; - } -} - -static json broadcastJSON(std::string data, Proto::BroadcastMode mode) { - return { - {"tx_bytes", data}, - {"mode", broadcastMode(mode)} - }; -} - -cosmos::base::v1beta1::Coin convertCoin(const Proto::Amount& amount) { - cosmos::base::v1beta1::Coin coin; - coin.set_denom(amount.denom()); - coin.set_amount(amount.amount()); - return coin; -} - -// Convert messages from external protobuf to internal protobuf -google::protobuf::Any convertMessage(const Proto::Message& msg) { - google::protobuf::Any any; - switch (msg.message_oneof_case()) { - case Proto::Message::kSendCoinsMessage: - { - assert(msg.has_send_coins_message()); - const auto& send = msg.send_coins_message(); - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address(send.from_address()); - msgSend.set_to_address(send.to_address()); - for (auto i = 0; i < send.amounts_size(); ++i) { - *msgSend.add_amount() = convertCoin(send.amounts(i)); - } - any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kTransferTokensMessage: - { - assert(msg.has_transfer_tokens_message()); - const auto& transfer = msg.transfer_tokens_message(); - auto msgTransfer = ibc::applications::transfer::v1::MsgTransfer(); - msgTransfer.set_source_port(transfer.source_port()); - msgTransfer.set_source_channel(transfer.source_channel()); - *msgTransfer.mutable_token() = convertCoin(transfer.token()); - msgTransfer.set_sender(transfer.sender()); - msgTransfer.set_receiver(transfer.receiver()); - msgTransfer.mutable_timeout_height()->set_revision_number(transfer.timeout_height().revision_number()); - msgTransfer.mutable_timeout_height()->set_revision_height(transfer.timeout_height().revision_height()); - msgTransfer.set_timeout_timestamp(transfer.timeout_timestamp()); - any.PackFrom(msgTransfer, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kStakeMessage: - { - assert(msg.has_stake_message()); - const auto& stake = msg.stake_message(); - auto msgDelegate = cosmos::staking::v1beta1::MsgDelegate(); - msgDelegate.set_delegator_address(stake.delegator_address()); - msgDelegate.set_validator_address(stake.validator_address()); - *msgDelegate.mutable_amount() = convertCoin(stake.amount()); - any.PackFrom(msgDelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kUnstakeMessage: - { - assert(msg.has_unstake_message()); - const auto& unstake = msg.unstake_message(); - auto msgUndelegate = cosmos::staking::v1beta1::MsgUndelegate(); - msgUndelegate.set_delegator_address(unstake.delegator_address()); - msgUndelegate.set_validator_address(unstake.validator_address()); - *msgUndelegate.mutable_amount() = convertCoin(unstake.amount()); - any.PackFrom(msgUndelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kRestakeMessage: - { - assert(msg.has_restake_message()); - const auto& restake = msg.restake_message(); - auto msgRedelegate = cosmos::staking::v1beta1::MsgBeginRedelegate(); - msgRedelegate.set_delegator_address(restake.delegator_address()); - msgRedelegate.set_validator_src_address(restake.validator_src_address()); - msgRedelegate.set_validator_dst_address(restake.validator_dst_address()); - *msgRedelegate.mutable_amount() = convertCoin(restake.amount()); - any.PackFrom(msgRedelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWithdrawStakeRewardMessage: - { - assert(msg.has_withdraw_stake_reward_message()); - const auto& withdraw = msg.withdraw_stake_reward_message(); - auto msgWithdraw = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward(); - msgWithdraw.set_delegator_address(withdraw.delegator_address()); - msgWithdraw.set_validator_address(withdraw.validator_address()); - any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kSetWithdrawAddressMessage: - { - assert(msg.has_set_withdraw_address_message()); - const auto& withdraw = msg.set_withdraw_address_message(); - auto msgWithdraw = cosmos::distribution::v1beta1::MsgSetWithdrawAddress(); - msgWithdraw.set_delegator_address(withdraw.delegator_address()); - msgWithdraw.set_withdraw_address(withdraw.withdraw_address()); - any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kExecuteContractMessage: - { - assert(msg.has_execute_contract_message()); - const auto& execContract = msg.execute_contract_message(); - auto executeContractMsg = terra::wasm::v1beta1::MsgExecuteContract(); - executeContractMsg.set_sender(execContract.sender()); - executeContractMsg.set_contract(execContract.contract()); - executeContractMsg.set_execute_msg(execContract.execute_msg()); - for (auto i = 0; i < execContract.coins_size(); ++i){ - *executeContractMsg.add_coins() = convertCoin(execContract.coins(i)); - } - any.PackFrom(executeContractMsg, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractTransferMessage: - { - assert(msg.has_wasm_terra_execute_contract_transfer_message()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_transfer_message(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmTerraExecuteTransferPayload(wasmExecute).dump(); - msgExecute.set_execute_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractSendMessage: - { - assert(msg.has_wasm_terra_execute_contract_send_message()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_send_message(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmTerraExecuteSendPayload(wasmExecute).dump(); - msgExecute.set_execute_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kThorchainSendMessage: - { - assert(msg.has_thorchain_send_message()); - const auto& send = msg.thorchain_send_message(); - auto msgSend =types::MsgSend(); - msgSend.set_from_address(send.from_address()); - msgSend.set_to_address(send.to_address()); - for (auto i = 0; i < send.amounts_size(); ++i) { - *msgSend.add_amount() = convertCoin(send.amounts(i)); - } - any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractGeneric: { - assert(msg.has_wasm_terra_execute_contract_generic()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_generic(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - msgExecute.set_execute_msg(wasmExecute.execute_msg()); - - for (auto i = 0; i < wasmExecute.coins_size(); ++i) { - *msgExecute.add_coins() = convertCoin(wasmExecute.coins(i)); - } - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmExecuteContractTransferMessage: - { - assert(msg.has_wasm_execute_contract_transfer_message()); - const auto& wasmExecute = msg.wasm_execute_contract_transfer_message(); - auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmExecuteTransferPayload(wasmExecute).dump(); - msgExecute.set_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmExecuteContractSendMessage: - { - assert(msg.has_wasm_execute_contract_send_message()); - const auto& wasmExecute = msg.wasm_execute_contract_send_message(); - auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmExecuteSendPayload(wasmExecute).dump(); - msgExecute.set_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmExecuteContractGeneric: { - assert(msg.has_wasm_execute_contract_generic()); - const auto& wasmExecute = msg.wasm_execute_contract_generic(); - auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - msgExecute.set_msg(wasmExecute.execute_msg()); - - for (auto i = 0; i < wasmExecute.coins_size(); ++i) { - *msgExecute.add_funds() = convertCoin(wasmExecute.coins(i)); - } - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kAuthGrant: { - assert(msg.has_auth_grant()); - const auto& authGrant = msg.auth_grant(); - auto msgAuthGrant = cosmos::authz::v1beta1::MsgGrant(); - msgAuthGrant.set_grantee(authGrant.grantee()); - msgAuthGrant.set_granter(authGrant.granter()); - auto* mtAuth = msgAuthGrant.mutable_grant()->mutable_authorization(); - // There is multiple grant possibilities, but we add support staking/compounding only for now. - switch (authGrant.grant_type_case()) { - case Proto::Message_AuthGrant::kGrantStake: - mtAuth->PackFrom(authGrant.grant_stake(), ProtobufAnyNamespacePrefix); - mtAuth->set_type_url("/cosmos.staking.v1beta1.StakeAuthorization"); - break; - case Proto::Message_AuthGrant::GRANT_TYPE_NOT_SET: - break; - } - auto* mtExp = msgAuthGrant.mutable_grant()->mutable_expiration(); - mtExp->set_seconds(authGrant.expiration()); - any.PackFrom(msgAuthGrant, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kAuthRevoke: { - assert(msg.has_auth_revoke()); - const auto& authRevoke = msg.auth_revoke(); - auto msgAuthRevoke = cosmos::authz::v1beta1::MsgRevoke(); - msgAuthRevoke.set_granter(authRevoke.granter()); - msgAuthRevoke.set_grantee(authRevoke.grantee()); - msgAuthRevoke.set_msg_type_url(authRevoke.msg_type_url()); - any.PackFrom(msgAuthRevoke, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kMsgVote: { - assert(msg.has_msg_vote()); - const auto& vote = msg.msg_vote(); - auto msgVote = cosmos::gov::v1beta1::MsgVote(); - // LCOV_EXCL_START - switch (vote.option()) { - case Proto::Message_VoteOption__UNSPECIFIED: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_UNSPECIFIED); - break; - case Proto::Message_VoteOption_YES: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_YES); - break; - case Proto::Message_VoteOption_ABSTAIN: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_ABSTAIN); - break; - case Proto::Message_VoteOption_NO: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_NO); - break; - case Proto::Message_VoteOption_NO_WITH_VETO: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_NO_WITH_VETO); - break; - case Proto::Message_VoteOption_Message_VoteOption_INT_MIN_SENTINEL_DO_NOT_USE_: - msgVote.set_option(cosmos::gov::v1beta1::VoteOption_INT_MIN_SENTINEL_DO_NOT_USE_); - break; - case Proto::Message_VoteOption_Message_VoteOption_INT_MAX_SENTINEL_DO_NOT_USE_: - msgVote.set_option(cosmos::gov::v1beta1::VoteOption_INT_MAX_SENTINEL_DO_NOT_USE_); - break; - } - // LCOV_EXCL_STOP - msgVote.set_proposal_id(vote.proposal_id()); - msgVote.set_voter(vote.voter()); - any.PackFrom(msgVote, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kMsgStrideLiquidStakingStake: { - const auto& stride_liquid_staking_stake = msg.msg_stride_liquid_staking_stake(); - auto liquid_staking_msg = stride::stakeibc::MsgLiquidStake(); - liquid_staking_msg.set_creator(stride_liquid_staking_stake.creator()); - liquid_staking_msg.set_amount(stride_liquid_staking_stake.amount()); - liquid_staking_msg.set_host_denom(stride_liquid_staking_stake.host_denom()); - any.PackFrom(liquid_staking_msg, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kMsgStrideLiquidStakingRedeem: { - const auto& stride_liquid_staking_redeem = msg.msg_stride_liquid_staking_redeem(); - auto liquid_staking_msg = stride::stakeibc::MsgRedeemStake(); - liquid_staking_msg.set_creator(stride_liquid_staking_redeem.creator()); - liquid_staking_msg.set_amount(stride_liquid_staking_redeem.amount()); - liquid_staking_msg.set_receiver(stride_liquid_staking_redeem.receiver()); - liquid_staking_msg.set_host_zone(stride_liquid_staking_redeem.host_zone()); - any.PackFrom(liquid_staking_msg, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kThorchainDepositMessage: { - assert(msg.has_thorchain_deposit_message()); - const auto& deposit = msg.thorchain_deposit_message(); - types::MsgDeposit msgDeposit; - msgDeposit.set_memo(deposit.memo()); - msgDeposit.set_signer(deposit.signer()); - for (auto i = 0; i < deposit.coins_size(); ++i) { - auto* coin = msgDeposit.add_coins(); - auto originalAsset = deposit.coins(i).asset(); - coin->mutable_asset()->set_chain(originalAsset.chain()); - coin->mutable_asset()->set_symbol(originalAsset.symbol()); - coin->mutable_asset()->set_ticker(originalAsset.ticker()); - coin->mutable_asset()->set_synth(originalAsset.synth()); - coin->set_amount(deposit.coins(i).amount()); - coin->set_decimals(deposit.coins(i).decimals()); - } - any.PackFrom(msgDeposit, ProtobufAnyNamespacePrefix); - - return any; - } - - default: - throw std::invalid_argument(std::string("Message not supported ") + std::to_string(msg.message_oneof_case())); - } -} - -std::string buildProtoTxBody(const Proto::SigningInput& input) { - if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { - return input.messages(0).sign_direct_message().body_bytes(); - } - - if (input.messages_size() < 1) { - throw std::invalid_argument("No message found"); - } - assert(input.messages_size() >= 1); - auto txBody = cosmos::TxBody(); - for (auto i = 0; i < input.messages_size(); ++i) { - const auto msgAny = convertMessage(input.messages(i)); - *txBody.add_messages() = msgAny; - } - txBody.set_memo(input.memo()); - txBody.set_timeout_height(0); - - return txBody.SerializeAsString(); -} - -std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin) { - // AuthInfo - const auto privateKey = PrivateKey(input.private_key()); - const auto publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); - return buildAuthInfo(input, publicKey, coin); -} - -std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) { - const auto pbk = internal::preparePublicKey(publicKey, coin); - - if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { - return input.messages(0).sign_direct_message().auth_info_bytes(); - } - // AuthInfo - auto authInfo = cosmos::AuthInfo(); - auto* signerInfo = authInfo.add_signer_infos(); - - signerInfo->mutable_mode_info()->mutable_single()->set_mode(cosmos::signing::v1beta1::SIGN_MODE_DIRECT); - signerInfo->set_sequence(input.sequence()); - switch(coin) { - case TWCoinTypeNativeEvmos: { - auto pubKey = ethermint::crypto::v1::ethsecp256k1::PubKey(); - pubKey.set_key(pbk.bytes.data(), pbk.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - break; - } - case TWCoinTypeNativeInjective: { - auto pubKey = injective::crypto::v1beta1::ethsecp256k1::PubKey(); - pubKey.set_key(pbk.bytes.data(), pbk.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - break; - } - default: { - auto pubKey = cosmos::crypto::secp256k1::PubKey(); - pubKey.set_key(pbk.bytes.data(), pbk.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - } - } - - auto* fee = authInfo.mutable_fee(); - for (auto i = 0; i < input.fee().amounts_size(); ++i) { - *fee->add_amount() = convertCoin(input.fee().amounts(i)); - } - - fee->set_gas_limit(input.fee().gas()); - fee->set_payer(""); - fee->set_granter(""); - // tip is omitted - return authInfo.SerializeAsString(); -} - -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin) { - // SignDoc Preimage - auto signDoc = cosmos::SignDoc(); - signDoc.set_body_bytes(serializedTxBody); - signDoc.set_auth_info_bytes(serializedAuthInfo); - signDoc.set_chain_id(input.chain_id()); - signDoc.set_account_number(input.account_number()); - const auto serializedSignDoc = signDoc.SerializeAsString(); - - Data hashToSign; - switch(coin) { - case TWCoinTypeNativeInjective: - case TWCoinTypeNativeEvmos: { - hashToSign = Hash::keccak256(serializedSignDoc); - break; - } - default: { - hashToSign = Hash::sha256(serializedSignDoc); - } - } - - const auto privateKey = PrivateKey(input.private_key()); - auto signedHash = privateKey.sign(hashToSign, TWCurveSECP256k1); - auto signature = Data(signedHash.begin(), signedHash.end() - 1); - return signature; -} - -std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature) { - auto txRaw = cosmos::TxRaw(); - txRaw.set_body_bytes(serializedTxBody); - txRaw.set_auth_info_bytes(serializedAuthInfo); - *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); - return txRaw.SerializeAsString(); -} - -std::string signaturePreimageProto(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) { - // SignDoc Preimage - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, publicKey, coin); - - auto signDoc = cosmos::SignDoc(); - signDoc.set_body_bytes(serializedTxBody); - signDoc.set_auth_info_bytes(serializedAuthInfo); - signDoc.set_chain_id(input.chain_id()); - signDoc.set_account_number(input.account_number()); - return signDoc.SerializeAsString(); -} - -std::string buildProtoTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, publicKey, coin); - - auto txRaw = cosmos::TxRaw(); - txRaw.set_body_bytes(serializedTxBody); - txRaw.set_auth_info_bytes(serializedAuthInfo); - *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); - auto data = txRaw.SerializeAsString(); - return broadcastJSON(Base64::encode(Data(data.begin(), data.end())), input.mode()).dump(); -} - -std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx) { - const string serializedBase64 = Base64::encode(TW::data(serializedTx)); - const json jsonSerialized = { - {"tx_bytes", serializedBase64}, - {"mode", broadcastMode(input.mode())} - }; - return jsonSerialized.dump(); -} - -json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg) { - return { - {"transfer", - { - {"amount", toString(load(data(msg.amount())))}, - {"recipient", msg.recipient_address()} - } - } - }; -} - -json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg) { - return { - {"send", - { - {"amount", toString(load(data(msg.amount())))}, - {"contract", msg.recipient_contract_address()}, - {"msg", msg.msg()} - } - } - }; -} - -json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { - return { - {"transfer", - { - {"amount", toString(load(data(msg.amount())))}, - {"recipient", msg.recipient_address()} - } - } - }; -} - -json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg) { - return { - {"send", - { - {"amount", toString(load(data(msg.amount())))}, - {"contract", msg.recipient_contract_address()}, - {"msg", msg.msg()} - } - } - }; -} - -} // namespace diff --git a/src/Cosmos/ProtobufSerialization.h b/src/Cosmos/ProtobufSerialization.h deleted file mode 100644 index 6eae28e431f..00000000000 --- a/src/Cosmos/ProtobufSerialization.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Cosmos.pb.h" - -#include -#include - -#include - -namespace TW::Cosmos::Protobuf { - -std::string buildProtoTxBody(const Proto::SigningInput& input); - -std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin); -std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin); - -std::string signaturePreimageProto(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin); - -std::string buildProtoTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); -std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature); -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin); - -std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx); - -nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); - -nlohmann::json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg); - -nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); - -nlohmann::json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg); - -nlohmann::json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg); - -} // namespace TW::Cosmos::protobuf diff --git a/src/Cosmos/Signer.cpp b/src/Cosmos/Signer.cpp deleted file mode 100644 index bceb5b1bd45..00000000000 --- a/src/Cosmos/Signer.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright 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 "JsonSerialization.h" -#include "ProtobufSerialization.h" - -#include "PrivateKey.h" -#include "Data.h" -#include - -namespace TW::Cosmos { - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, TWCoinType coin) noexcept { - switch (input.signing_mode()) { - case Proto::JSON: - return signJsonSerialized(input, coin); - - case Proto::Protobuf: - default: - return signProtobuf(input, coin); - } -} - -std::string Signer::signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const { - switch (input.signing_mode()) { - case Proto::JSON: - return Json::signaturePreimageJSON(input).dump(); - - case Proto::Protobuf: - default: - auto pbk = PublicKey(publicKey, TWCoinTypePublicKeyType(coin)); - return Protobuf::signaturePreimageProto(input, pbk, coin); - } -} - -Proto::SigningOutput Signer::signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept { - auto key = PrivateKey(input.private_key()); - auto preimage = Json::signaturePreimageJSON(input).dump(); - auto hash = Hash::sha256(preimage); - auto signedHash = key.sign(hash, TWCurveSECP256k1); - - auto output = Proto::SigningOutput(); - auto signature = Data(signedHash.begin(), signedHash.end() - 1); - auto txJson = Json::transactionJSON(input, signature, coin); - output.set_json(txJson.dump()); - output.set_signature(signature.data(), signature.size()); - output.set_serialized(""); - output.set_error_message(""); - output.set_signature_json(txJson["tx"]["signatures"].dump()); - return output; -} - -Proto::SigningOutput Signer::signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept { - using namespace Protobuf; - using namespace Json; - try { - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, coin); - const auto signature = buildSignature(input, serializedTxBody, serializedAuthInfo, coin); - auto serializedTxRaw = buildProtoTxRaw(serializedTxBody, serializedAuthInfo, signature); - - auto output = Proto::SigningOutput(); - const std::string jsonSerialized = Protobuf::buildProtoTxJson(input, serializedTxRaw); - auto publicKey = PrivateKey(input.private_key()).getPublicKey(TWPublicKeyTypeSECP256k1); - auto signatures = nlohmann::json::array({signatureJSON(signature, publicKey.bytes, coin)}); - output.set_serialized(jsonSerialized); - output.set_signature(signature.data(), signature.size()); - output.set_json(""); - output.set_error_message(""); - output.set_signature_json(signatures.dump()); - return output; - } catch (const std::exception& ex) { - auto output = Proto::SigningOutput(); - output.set_error(Common::Proto::Error_internal); - output.set_error_message(std::string("Error: ") + ex.what()); - return output; - } -} - -std::string Signer::signJSON(const std::string& json, const Data& key, TWCoinType coin) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input, coin); - return output.json(); -} - -std::string Signer::encodeTransaction(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey, TWCoinType coin) const { - switch (input.signing_mode()) { - case Proto::JSON: - return Json::buildJsonTxRaw(input, publicKey, signature, coin); - - case Proto::Protobuf: - default: - return Protobuf::buildProtoTxRaw(input, publicKey, signature, coin); - } -} -} // namespace TW::Cosmos diff --git a/src/Cosmos/Signer.h b/src/Cosmos/Signer.h deleted file mode 100644 index b63836fc2e7..00000000000 --- a/src/Cosmos/Signer.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Cosmos.pb.h" - -#include - -namespace TW::Cosmos { - -/// Helper class that performs Cosmos transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - std::string encodeTransaction(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey, TWCoinType coin) const; - std::string signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const; - - /// Signs a Proto::SigningInput transaction, using Json serialization - static Proto::SigningOutput signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - /// Signs a Proto::SigningInput transaction, using binary Protobuf serialization - static Proto::SigningOutput signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key, TWCoinType coin); -}; - -} // namespace TW::Cosmos diff --git a/src/Ethereum/ABI.h b/src/Ethereum/ABI.h deleted file mode 100644 index 6764769a2da..00000000000 --- a/src/Ethereum/ABI.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ABI/Array.h" -#include "ABI/Bytes.h" -#include "ABI/Function.h" -#include "ABI/ParamAddress.h" -#include "ABI/ParamBase.h" -#include "ABI/Parameters.h" -#include "ABI/ParamFactory.h" -#include "ABI/ParamNumber.h" -#include "ABI/ParamStruct.h" -#include "ABI/Tuple.h" diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp deleted file mode 100644 index b47fc8b034c..00000000000 --- a/src/Ethereum/ABI/Array.cpp +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Array.h" -#include "ParamFactory.h" -#include "ValueEncoder.h" -#include - -#include - -#include - -namespace TW::Ethereum::ABI { - -using namespace TW; -using json = nlohmann::json; - -int ParamArray::addParam(const std::shared_ptr& param) { - assert(param != nullptr); - if (param == nullptr) { - return -1; - } - if (_params.getCount() >= 1 && param->getType() != getProtoType()) { - return -2; - } // do not add different types - return _params.addParam(param); -} - -void ParamArray::addParams(const std::vector>& params) { - for (auto p : params) { - addParam(p); - } -} - -std::shared_ptr ParamArray::getProtoElem() const { - if (_params.getCount() >= 1) { - return _params.getParamUnsafe(0); - } - return _proto; -} - -std::string ParamArray::getProtoType() const { - const auto proto = getProtoElem(); - return (proto != nullptr) ? proto->getType() : "__empty__"; -} - -size_t ParamArray::getSize() const { - return 32 + _params.getSize(); -} - -void ParamArray::encode(Data& data) const { - size_t n = _params.getCount(); - ValueEncoder::encodeUInt256(uint256_t(n), data); - _params.encode(data); -} - -bool ParamArray::decode(const Data& encoded, size_t& offset_inout) { - size_t origOffset = offset_inout; - // read length - uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { - return false; - } - // check if length is in the size_t range - auto len = static_cast(len256); - if (len256 != uint256_t(len)) { - return false; - } - // check number of values - auto n = _params.getCount(); - if (n == 0 || n > len) { - // Encoded length is less than params count, unsafe to continue decoding - return false; - } - if (n < len) { - // pad with first type - auto first = _params.getParamUnsafe(0); - for (size_t i = 0; i < len - n; i++) { - _params.addParam(first->clone()); - } - } - - // read values - auto res = _params.decode(encoded, offset_inout); - - // padding - offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return res; -} - -bool ParamArray::setValueJson(const std::string& value) { - if (_params.getCount() < 1) { - // no single element - return false; - } - auto valuesJson = json::parse(value, nullptr, false); - if (valuesJson.is_discarded()) { - return false; - } - if (!valuesJson.is_array()) { - return false; - } - // make sure enough elements are in the array - while (_params.getCount() < valuesJson.size()) { - addParam(getProtoElem()->clone()); - } - int cnt = 0; - for (const auto& e : valuesJson) { - std::string eString = e.is_string() ? e.get() : e.dump(); - _params.getParamUnsafe(cnt)->setValueJson(eString); - ++cnt; - } - return true; -} - -Data ParamArray::hashStruct() const { - if (_params.getCount() == 0) { - return Hash::keccak256(Data()); - } - Data hash(32); - Data hashes = _params.encodeHashes(); - if (hashes.size() > 0) { - hash = Hash::keccak256(hashes); - } - return hash; -} - -void ParamArray::fillExtraTypesMap(ExtraTypesMap& extraTypes) const { - if (const auto& proto = getProtoElem(); proto != nullptr) { - proto->fillExtraTypesMap(extraTypes); - } -} - -std::shared_ptr ParamArray::clone() const { - auto newArray = std::make_shared(); - newArray->_params = _params.clone(); - newArray->_proto = _proto->clone(); - return newArray; -} - -void ParamArrayFix::encode(Data& data) const { - this->_params.encode(data); -} - -bool ParamArrayFix::decode(const Data& encoded, size_t& offset_inout) { - return this->_params.decode(encoded, offset_inout); -} - -bool ParamArrayFix::setValueJson(const std::string& value) { - auto valuesJson = json::parse(value, nullptr, false); - if (valuesJson.is_discarded() || !valuesJson.is_array() || _params.getCount() != valuesJson.size()) { - return false; - } - - std::size_t idx{0}; - for (auto&& e : valuesJson) { - std::string eString = e.is_string() ? e.get() : e.dump(); - _params.getParamUnsafe(idx)->setValueJson(eString); - ++idx; - } - return true; -} - -std::shared_ptr ParamArrayFix::clone() const { - auto newArray = std::make_shared(); - newArray->_params = _params.clone(); - return newArray; -} - -void ParamArrayFix::addParams(const Params& params) { - auto addParamFunctor = [this](auto&& param) { - if (param == nullptr) { - throw std::runtime_error("param can't be nullptr"); - } - if (_params.getCount() >= 1 && param->getType() != _params.getParamUnsafe(0)->getType()) { - throw std::runtime_error("params need to be the same type"); - } // do not add different types - _params.addParam(param); - }; - std::for_each(begin(params), end(params), addParamFunctor); -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h deleted file mode 100644 index 4638c38f171..00000000000 --- a/src/Ethereum/ABI/Array.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ParamNumber.h" -#include "Parameters.h" - -namespace TW::Ethereum::ABI { - -/// Dynamic array of the same types, "[]" -/// Normally has at least one element. Empty array can have prototype set so its type is known. -/// Empty with no prototype is possible, but should be avoided. -class ParamArray final : public ParamCollection { -private: - ParamSet _params; - std::shared_ptr _proto; // an optional prototype element, determines the array type, useful in empty array case - -private: - std::shared_ptr getProtoElem() const; // the first element if exists, otherwise the proto element - std::string getProtoType() const; - -public: - ParamArray() = default; - ParamArray(const std::shared_ptr& param1) - : ParamCollection() { addParam(param1); } - ParamArray(const std::vector>& params) - : ParamCollection() { setVal(params); } - void setVal(const std::vector>& params) { addParams(params); } - std::vector> const& getVal() const { return _params.getParams(); } - int addParam(const std::shared_ptr& param); - void addParams(const std::vector>& params); - void setProto(const std::shared_ptr& proto) { _proto = proto; } - std::shared_ptr getParam(int paramIndex) { return _params.getParamUnsafe(paramIndex); } - - std::string getType() const override { return getProtoType() + "[]"; } - size_t getSize() const override; - bool isDynamic() const override { return true; } - size_t getCount() const override { return _params.getCount(); } - void encode(Data& data) const override; - bool decode(const Data& encoded, size_t& offset_inout) override; - bool setValueJson(const std::string& value) override; - Data hashStruct() const override; - void fillExtraTypesMap(ExtraTypesMap& extraTypes) const override; - std::shared_ptr clone() const override; -}; - -/// Fixed-size array of the same type e.g, "type[4]" -class ParamArrayFix final : public ParamCollection { -public: - //! Public Definitions - using Params = std::vector>; - - //! Public constructors - ParamArrayFix() = default; - - explicit ParamArrayFix(const Params& params) noexcept(false) - : ParamCollection() { - this->addParams(params); - } - - //! Public member methods - [[nodiscard]] std::size_t getCount() const override { return _params.getCount(); } - [[nodiscard]] size_t getSize() const override { return _params.getSize(); } - [[nodiscard]] bool isDynamic() const override { return false; } - [[nodiscard]] std::string getType() const override { return _params.getParamUnsafe(0)->getType() + "[" + std::to_string(_params.getCount()) + "]"; } - void encode(Data& data) const override; - bool decode(const Data& encoded, size_t& offset_inout) override; - bool setValueJson(const std::string& value) override; - std::shared_ptr clone() const override; - -private: - //! Private member functions - void addParams(const Params& params) noexcept(false); - - //! Private member fields - ParamSet _params; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp deleted file mode 100644 index 5cbee7fc970..00000000000 --- a/src/Ethereum/ABI/Bytes.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bytes.h" -#include "ParamNumber.h" -#include "ValueEncoder.h" -#include -#include - -#include - -namespace TW::Ethereum::ABI { - -void ParamByteArray::encodeBytes(const Data& bytes, Data& data) { - ValueEncoder::encodeUInt256(uint256_t(bytes.size()), data); - - const auto count = bytes.size(); - const auto padding = ValueEncoder::padNeeded32(count); - data.insert(data.end(), bytes.begin(), bytes.begin() + count); - append(data, Data(padding)); -} - -bool ParamByteArray::decodeBytes(const Data& encoded, Data& decoded, size_t& offset_inout) { - size_t origOffset = offset_inout; - // read len - uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { - return false; - } - // check if length is in the size_t range - auto len = static_cast(len256); - if (len256 != uint256_t(len)) { - return false; - } - // check if there is enough data - if (encoded.size() < offset_inout + len) { - return false; - } - // read data - decoded = Data(encoded.begin() + offset_inout, encoded.begin() + offset_inout + len); - offset_inout += len; - // padding - offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return true; -} - -bool ParamByteArray::setValueJson(const std::string& value) { - setVal(parse_hex(value)); - return true; -} - -Data ParamByteArray::hashStruct() const { - return Hash::keccak256(_bytes); -} - -std::shared_ptr ParamByteArray::clone() const { - return std::make_shared(_bytes); -} - -void ParamByteArrayFix::setVal(const Data& val) { - if (val.size() > _n) { // crop right - _bytes = subData(val, 0, _n); - } else { - _bytes = val; - if (_bytes.size() < _n) { // pad on right - append(_bytes, Data(_n - _bytes.size())); - } - } - assert(_bytes.size() == _n); -} - -void ParamByteArrayFix::encode(Data& data) const { - const auto count = _bytes.size(); - const auto padding = ValueEncoder::padNeeded32(count); - data.insert(data.end(), _bytes.begin(), _bytes.begin() + count); - append(data, Data(padding)); -} - -bool ParamByteArrayFix::decodeBytesFix(const Data& encoded, size_t n, Data& decoded, - size_t& offset_inout) { - size_t origOffset = offset_inout; - if (encoded.size() < offset_inout + n) { - // not enough data - return false; - } - if (decoded.size() < n) { - append(decoded, Data(n - decoded.size())); - } - std::copy(encoded.begin() + offset_inout, encoded.begin() + (offset_inout + n), - decoded.begin()); - offset_inout += n; - // padding - offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return true; -} - -bool ParamByteArrayFix::setValueJson(const std::string& value) { - setVal(parse_hex(value)); - return true; -} - -Data ParamByteArrayFix::hashStruct() const { - if (_bytes.size() > 32) { - return Hash::keccak256(_bytes); - } - return ParamBase::hashStruct(); -} - -std::shared_ptr ParamByteArrayFix::clone() const { - return std::make_shared(_n, _bytes); -} - -void ParamString::encodeString(const std::string& decoded, Data& data) { - auto bytes = Data(decoded.begin(), decoded.end()); - ParamByteArray::encodeBytes(bytes, data); -} - -bool ParamString::decodeString(const Data& encoded, std::string& decoded, size_t& offset_inout) { - Data decodedData; - if (!ParamByteArray::decodeBytes(encoded, decodedData, offset_inout)) { - return false; - } - decoded = std::string(decodedData.begin(), decodedData.end()); - return true; -} - -Data ParamString::hashStruct() const { - Data hash(32); - Data encoded = data(_str); - hash = Hash::keccak256(encoded); - return hash; -} - -std::shared_ptr ParamString::clone() const { - return std::make_shared(_str); -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.h b/src/Ethereum/ABI/Bytes.h deleted file mode 100644 index 3fc92f4c78e..00000000000 --- a/src/Ethereum/ABI/Bytes.h +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ValueEncoder.h" -#include - -namespace TW::Ethereum::ABI { - -/// Dynamic array of bytes "bytes" -class ParamByteArray final: public ParamCollection -{ -private: - Data _bytes; -public: - ParamByteArray() = default; - ParamByteArray(const Data& val) : ParamCollection() { setVal(val); } - void setVal(const Data& val) { _bytes = val; } - const Data& getVal() const { return _bytes; } - std::string getType() const override { return "bytes"; }; - size_t getSize() const override { return 32 + ValueEncoder::paddedTo32(_bytes.size()); } - bool isDynamic() const override { return true; } - size_t getCount() const override { return _bytes.size(); } - static void encodeBytes(const Data& bytes, Data& data); - void encode(Data& data) const override { encodeBytes(_bytes, data); } - static bool decodeBytes(const Data& encoded, Data& decoded, size_t& offset_inout); - bool decode(const Data& encoded, size_t& offset_inout) override { - return decodeBytes(encoded, _bytes, offset_inout); - } - bool setValueJson(const std::string& value) override; - Data hashStruct() const override; - std::shared_ptr clone() const override; -}; - -/// Fixed-size array of bytes, "bytes" -class ParamByteArrayFix final: public ParamCollection -{ -private: - size_t _n; - Data _bytes; -public: - ParamByteArrayFix(size_t n): ParamCollection(), _n(n), _bytes(Data(_n)) {} - ParamByteArrayFix(size_t n, const Data& val): ParamCollection(), _n(n), _bytes(Data(_n)) { setVal(val); } - void setVal(const Data& val); - const std::vector& getVal() const { return _bytes; } - std::string getType() const override { return "bytes" + std::to_string(_n); }; - size_t getSize() const override { return ValueEncoder::paddedTo32(_bytes.size()); } - bool isDynamic() const override { return false; } - size_t getCount() const override { return _bytes.size(); } - void encode(Data& data) const override; - static bool decodeBytesFix(const Data& encoded, size_t n, Data& decoded, size_t& offset_inout); - bool decode(const Data& encoded, size_t& offset_inout) override { - return decodeBytesFix(encoded, _n, _bytes, offset_inout); - } - bool setValueJson(const std::string& value) override; - Data hashStruct() const override; - std::shared_ptr clone() const override; -}; - -/// Var-length string parameter -class ParamString final: public ParamCollection -{ -private: - std::string _str; -public: - ParamString() = default; - ParamString(const std::string& val): ParamCollection() { setVal(val); } - void setVal(const std::string& val) { _str = val; } - const std::string& getVal() const { return _str; } - std::string getType() const override { return "string"; }; - size_t getSize() const override { return 32 + ValueEncoder::paddedTo32(_str.size()); } - bool isDynamic() const override { return true; } - size_t getCount() const override { return _str.size(); } - static void encodeString(const std::string& decoded, Data& data); - void encode(Data& data) const override { ParamString::encodeString(_str, data); } - static bool decodeString(const Data& encoded, std::string& decoded, size_t& offset_inout); - bool decode(const Data& encoded, size_t& offset_inout) override { - return decodeString(encoded, _str, offset_inout); - } - bool setValueJson(const std::string& value) override { _str = value; return true; } - Data hashStruct() const override; - std::shared_ptr clone() const override; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Function.cpp b/src/Ethereum/ABI/Function.cpp index d249c9e2752..ffe84e940f4 100644 --- a/src/Ethereum/ABI/Function.cpp +++ b/src/Ethereum/ABI/Function.cpp @@ -6,51 +6,207 @@ #include "Function.h" -#include +#include "rust/Wrapper.h" +#include "TrustWalletCore/TWCoinType.h" namespace TW::Ethereum::ABI { -Data Function::getSignature() const { - auto typ = getType(); - auto hash = Hash::keccak256(Data(typ.begin(), typ.end())); - auto signature = Data(hash.begin(), hash.begin() + 4); - return signature; +static constexpr std::size_t FUNCTION_SIGNATURE_LEN = 4; + +int Function::addParam(AbiProto::Param paramType, AbiProto::Token paramValue, bool isOutput) { + if (isOutput) { + auto idx = outputValues.size(); + *outputs.add_params() = std::move(paramType); + outputValues.emplace_back(std::move(paramValue)); + return static_cast(idx); + } + + auto idx = inputValues.size(); + *inputs.add_params() = std::move(paramType); + inputValues.emplace_back(std::move(paramValue)); + return static_cast(idx); } -void Function::encode(Data& data) const { - Data signature = getSignature(); - append(data, signature); - _inParams.encode(data); +int Function::addUintParam(uint32_t bits, const Data& encodedValue, bool isOutput) { + AbiProto::Param paramType; + paramType.mutable_param()->mutable_number_uint()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_uint(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addParam(std::move(paramType), std::move(paramValue), isOutput); } -bool Function::decodeOutput(const Data& encoded, size_t& offset_inout) { - // read parameter values - if (!_outParams.decode(encoded, offset_inout)) { - return false; +int Function::addIntParam(uint32_t bits, const Data& encodedValue, bool isOutput) { + AbiProto::Param paramType; + paramType.mutable_param()->mutable_number_int()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_int(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addParam(std::move(paramType), std::move(paramValue), isOutput); +} + +int Function::addInArrayParam(int idx, AbiProto::ParamType paramType, AbiProto::Token paramValue) { + if (idx < 0) { + return -1; } - return true; + + auto idxSize = static_cast(idx); + if (idxSize >= inputValues.size()) { + return -1; + } + + auto& arrayToken = inputValues[idxSize]; + auto& arrayType = *inputs.mutable_params(idx)->mutable_param(); + + if (!arrayToken.has_array() || !arrayType.has_array()) { + return -1; + } + + auto arrayInElementIdx = arrayToken.array().elements_size(); + + *arrayToken.mutable_array()->add_elements() = std::move(paramValue); + *arrayToken.mutable_array()->mutable_element_type() = paramType; + // Override the element type. + *arrayType.mutable_array()->mutable_element_type() = std::move(paramType); + + return arrayInElementIdx; } -bool Function::decodeInput(const Data& encoded, size_t& offset_inout) { - // read 4-byte hash - auto p = ParamByteArrayFix(4); - if (!p.decode(encoded, offset_inout)) { - return false; +int Function::addInArrayUintParam(int idx, uint32_t bits, const Data& encodedValue) { + AbiProto::ParamType paramType; + paramType.mutable_number_uint()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_uint(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addInArrayParam(idx, std::move(paramType), std::move(paramValue)); +} + +int Function::addInArrayIntParam(int idx, uint32_t bits, const Data& encodedValue) { + AbiProto::ParamType paramType; + paramType.mutable_number_int()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_int(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addInArrayParam(idx, std::move(paramType), std::move(paramValue)); +} + +MaybeToken Function::getParam(int idx, bool isOutput) const { + const auto& values = isOutput ? outputValues : inputValues; + + if (idx < 0) { + return {}; } - std::vector hash = p.getVal(); - // adjust offset; hash is NOT padded to 32 bytes - offset_inout = offset_inout - 32 + 4; - // verify hash - Data hashExpect = getSignature(); - if (hash != hashExpect) { - // invalid hash + + auto idxSize = static_cast(idx); + if (idxSize >= values.size()) { + return {}; + } + + return values[idxSize]; +} + +bool Function::decode(const Data& encoded, bool isOutput) { + AbiProto::ParamsDecodingInput input; + + input.set_encoded(encoded.data(), encoded.size()); + if (isOutput) { + *input.mutable_abi_params() = outputs; + } else { + *input.mutable_abi_params() = inputs; + } + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_params(TWCoinTypeEthereum, inputData.get()); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return false; } - // read parameters - if (!_inParams.decode(encoded, offset_inout)) { + + AbiProto::ParamsDecodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != AbiProto::AbiError::OK) { return false; } + + std::vector decoded; + for (const auto ¶m : output.tokens()) { + decoded.emplace_back(param); + } + + if (isOutput) { + outputValues = decoded; + } else { + inputValues = decoded; + } return true; } +std::string Function::getType() const { + AbiProto::FunctionGetTypeInput input; + input.set_function_name(name); + *input.mutable_inputs() = inputs.params(); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWStringWrapper outputPtr = Rust::tw_ethereum_abi_function_get_signature(TWCoinTypeEthereum, inputData.get()); + + return outputPtr.toStringOrDefault(); +} + +MaybeData Function::encodeFunctionCall(const std::string& functionName, const Tokens& tokens) { + AbiProto::FunctionEncodingInput input; + input.set_function_name(functionName); + for (const auto& token : tokens) { + *input.add_tokens() = token; + } + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_encode_function(TWCoinTypeEthereum, inputData.get()); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; + } + + AbiProto::FunctionEncodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != AbiProto::AbiError::OK) { + return {}; + } + + return data(output.encoded()); +} + +MaybeData Function::encodeFunctionCall(const std::string& functionName, const BaseParams& params) { + Tokens namedParams; + for (const auto& param : params) { + namedParams.push_back(param->toToken()); + } + return encodeFunctionCall(functionName, namedParams); +} + +MaybeData Function::encodeParams(const BaseParams& params) { + auto encoded = encodeFunctionCall("", params); + if (!encoded.has_value() || encoded.value().size() < FUNCTION_SIGNATURE_LEN) { + return {}; + } + + // The encoded data includes the function call signature (4 bytes). Erase it. + return subData(encoded.value(), FUNCTION_SIGNATURE_LEN); +} + } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Function.h b/src/Ethereum/ABI/Function.h index d331ea030f8..fbed1fcbb4d 100644 --- a/src/Ethereum/ABI/Function.h +++ b/src/Ethereum/ABI/Function.h @@ -6,72 +6,97 @@ #pragma once -#include "ParamBase.h" -#include "Parameters.h" -#include "Bytes.h" +#include "ProtoParam.h" +#include "proto/EthereumAbi.pb.h" +#include "../../HexCoding.h" #include "../../uint256.h" -#include "../../Hash.h" +#include +#include #include namespace TW::Ethereum::ABI { -/// Non-generic version of Function, templated version is impossible to pass around to and back over C interface -/// (void* looses the template parameters). +namespace AbiProto = EthereumAbi::Proto; + +using MaybeToken = std::optional; +using MaybeData = std::optional; +using Tokens = std::vector; + class Function { public: - std::string name; - ParamSet _inParams; - ParamSet _outParams; - - Function(std::string name) : name(std::move(name)) {} - Function(std::string name, const std::vector>& inParams) - : name(std::move(name)), _inParams(ParamSet(inParams)) {} - virtual ~Function() {} - /// Add an input parameter. Returns the index of the parameter. - int addInParam(std::shared_ptr param) { - return _inParams.addParam(param); - } - /// Add an output parameter. Returns the index of the parameter. - int addOutParam(std::shared_ptr param) { - return _outParams.addParam(param); - } - /// Add an input or output parameter. Returns the index of the parameter. - int addParam(std::shared_ptr param, bool isOutput = false) { - return isOutput ? _outParams.addParam(param) : _inParams.addParam(param); - } - /// Get an input parameter. - bool getInParam(int paramIndex, std::shared_ptr& param_out) { - return _inParams.getParam(paramIndex, param_out); - } - /// Get an output parameter. - bool getOutParam(int paramIndex, std::shared_ptr& param_out) { - return _outParams.getParam(paramIndex, param_out); + explicit Function(std::string name): name(std::move(name)) {} + + /// Adds an input or output parameter. Returns the index of the parameter. + int addParam(AbiProto::Param paramType, AbiProto::Token paramValue, bool isOutput = false); + + /// Adds an input or output uint parameter. Returns the index of the parameter. + int addUintParam(uint32_t bits, const Data& encodedValue, bool isOutput = false); + + /// Adds an input or output int parameter. Returns the index of the parameter. + int addIntParam(uint32_t bits, const Data& encodedValue, bool isOutput = false); + + /// Adds a parameter to the input array. + /// Please note the array should be present at `inputValues[idx]`. + int addInArrayParam(int idx, AbiProto::ParamType paramType, AbiProto::Token paramValue); + + /// Adds a uint parameter to the input array. + /// Please note the array should be present at `inputValues[idx]`. + int addInArrayUintParam(int idx, uint32_t bits, const Data& encodedValue); + + /// Adds an int parameter to the input array. + /// Please note the array should be present at `inputValues[idx]`. + int addInArrayIntParam(int idx, uint32_t bits, const Data& encodedValue); + + /// Returns an input or output parameter. + MaybeToken getParam(int idx, bool isOutput = false) const; + + /// Returns the data of an input or output uint parameter. + Data getUintParamData(int idx, uint32_t bits, bool isOutput = false) const { + auto param = getParam(idx, isOutput); + if (!param.has_value() || !param->has_number_uint() || param->number_uint().bits() != bits) { + return store(0); + } + return data(param->number_uint().value()); } - /// Get an input or output parameter. - bool getParam(int paramIndex, std::shared_ptr& param_out, bool isOutput = false) { - return isOutput ? _outParams.getParam(paramIndex, param_out) : _inParams.getParam(paramIndex, param_out); + + /// Returns an input or output uint parameter. + template + T getUintParam(int idx, uint32_t bits, bool isOutput = false) const { + auto valueData = getUintParamData(idx, bits, isOutput); + auto val256 = load(valueData); + return static_cast(val256); } - /// Return the function type signature, of the form "baz(int32,uint256)" - std::string getType() const { - return name + _inParams.getType(); + + /// Encodes a function call to Eth ABI binary. + MaybeData encodeInput() const { + return encodeFunctionCall(name, inputValues); } - /// Return the 4-byte function signature - Data getSignature() const; + /// Decode binary, fill input or output parameters. + bool decode(const Data& encoded, bool isOutput = false); - virtual void encode(Data& data) const; + /// Returns the function type signature, of the form "baz(int32,uint256)". + std::string getType() const; - /// Decode binary, fill output parameters - bool decodeOutput(const Data& encoded, size_t& offset_inout); - /// Decode binary, fill input parameters - bool decodeInput(const Data& encoded, size_t& offset_inout); -}; + /// Encodes a function call to Eth ABI binary. + static MaybeData encodeFunctionCall(const std::string& functionName, const Tokens& params); -inline void encode(const Function& func, Data& data) { - func.encode(data); -} + /// Encodes a function call to Eth ABI binary. + static MaybeData encodeFunctionCall(const std::string& functionName, const BaseParams& params); + + /// Encodes params to Eth ABI binary. + static MaybeData encodeParams(const BaseParams& params); + +private: + std::string name; + AbiProto::AbiParams inputs; + AbiProto::AbiParams outputs; + + Tokens inputValues; + Tokens outputValues; +}; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamAddress.cpp b/src/Ethereum/ABI/ParamAddress.cpp deleted file mode 100644 index 1707f9f2084..00000000000 --- a/src/Ethereum/ABI/ParamAddress.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamAddress.h" -#include -#include - -namespace TW::Ethereum::ABI { - -Data ParamAddress::getData() const { - Data data = store(_val.getVal(), bytes); - return data; -} - -bool ParamAddress::setValueJson(const std::string& value) { - _val.setVal(load(parse_hex(value))); - return true; -} - -std::shared_ptr ParamAddress::clone() const { - return std::make_shared(getData()); -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamAddress.h b/src/Ethereum/ABI/ParamAddress.h deleted file mode 100644 index c9ae0fa9d16..00000000000 --- a/src/Ethereum/ABI/ParamAddress.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamNumber.h" -#include - -namespace TW::Ethereum::ABI { - -/// 160-bit Address parameter, "address". Padded to the right, treated like ParamUInt160 -class ParamAddress final: public ParamBase -{ -private: - ParamUIntN _val; -public: - static const size_t bytes = 20; - ParamAddress(): _val(bytes * 8) {} - explicit ParamAddress(const Data& val): _val(bytes * 8, load(val)) {} - std::string getType() const override { return "address"; }; - size_t getSize() const override { return _val.getSize(); } - bool isDynamic() const override { return _val.isDynamic(); } - void encode(Data& data) const override { _val.encode(data); } - bool decode(const Data& encoded, size_t& offset_inout) override { return _val.decode(encoded, offset_inout); } - // get the value as (20-byte) byte array (as opposed to uint256_t) - Data getData() const; - bool setValueJson(const std::string& value) override; - std::shared_ptr clone() const override; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamBase.h b/src/Ethereum/ABI/ParamBase.h deleted file mode 100644 index 882a9571e0f..00000000000 --- a/src/Ethereum/ABI/ParamBase.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 { - -// A map of `StructName -> StructType` key-values, where `StructType` is a full type including the structure name. -// Referenced struct type should be sorted by name see: https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype -using ExtraTypesMap = std::map>; - -/// Abstract base class for parameters. -class ParamBase -{ -public: - virtual ~ParamBase() = default; - virtual std::string getType() const = 0; - virtual size_t getSize() const = 0; - virtual bool isDynamic() const = 0; - virtual void encode(Data& data) const = 0; - virtual bool decode(const Data& encoded, size_t& offset_inout) = 0; - virtual bool setValueJson(const std::string& value) = 0; - // EIP712-style hash of the value (used for signing); default implementation - virtual Data hashStruct() const; - // Helper for EIP712 encoding; fill the given `extraTypes` (recursively). - virtual void fillExtraTypesMap([[maybe_unused]] ExtraTypesMap& extraTypes) const {} - // Creates a copy of this element. - // This method **must** be implemented in a `final` class only. - virtual std::shared_ptr clone() const = 0; -}; - -/// Collection of parameters base class -class ParamCollection: public ParamBase -{ -public: - virtual size_t getCount() const = 0; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamFactory.cpp b/src/Ethereum/ABI/ParamFactory.cpp deleted file mode 100644 index cef102441a2..00000000000 --- a/src/Ethereum/ABI/ParamFactory.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamFactory.h" -#include "ParamAddress.h" -#include "HexCoding.h" - -#include -#include - -using namespace std; -using namespace boost::algorithm; -using json = nlohmann::json; - -namespace TW::Ethereum::ABI { - -static int parseBitSize(const std::string& type) { - int size = stoi(type); - if (size < 8 || size > 256 || size % 8 != 0 || - size == 8 || size == 16 || size == 32 || size == 64 || size == 256) { - throw invalid_argument("invalid bit size"); - } - return size; -} - -static std::shared_ptr makeUInt(const std::string& type) { - auto bits = parseBitSize(type); - return make_shared(bits); -} - -static std::shared_ptr makeInt(const std::string& type) { - auto bits = parseBitSize(type); - return make_shared(bits); -} - -static bool isArrayType(const std::string& type) { - return ends_with(type, "[]") && type.length() >= 3; -} - -static std::string getArrayElemType(const std::string& arrayType) { - if (isArrayType(arrayType)) { - return arrayType.substr(0, arrayType.length() - 2); - } - return ""; -} - -std::shared_ptr ParamFactory::make(const std::string& type) { - shared_ptr param; - if (isArrayType(type)) { - auto elemType = getArrayElemType(type); - auto elemParam = make(elemType); - if (!elemParam) { - return param; - } - param = make_shared(elemParam); - } else if (type == "address") { - param = make_shared(); - } else if (type == "uint8") { - param = make_shared(); - } else if (type == "uint16") { - param = make_shared(); - } else if (type == "uint32") { - param = make_shared(); - } else if (type == "uint64") { - param = make_shared(); - } else if (type == "uint256" || type == "uint") { - param = make_shared(); - } else if (type == "int8") { - param = make_shared(); - } else if (type == "int16") { - param = make_shared(); - } else if (type == "int32") { - param = make_shared(); - } else if (type == "int64") { - param = make_shared(); - } else if (type == "int256" || type == "int") { - param = make_shared(); - } else if (starts_with(type, "uint")) { - param = makeUInt(type.substr(4, type.size() - 1)); - } else if (starts_with(type, "int")) { - param = makeInt(type.substr(3, type.size() - 1)); - } else if (type == "bool") { - param = make_shared(); - } else if (type == "bytes") { - param = make_shared(); - } else if (starts_with(type, "bytes")) { - auto bits = stoi(type.substr(5, type.size() - 1)); - param = make_shared(bits); - } else if (type == "string") { - param = make_shared(); - } - return param; -} - -std::string joinArrayElems(const std::vector& strings) { - auto array = json::array(); - for (const auto& string : strings) { - // parse to prevent quotes on simple values - auto value = json::parse(string, nullptr, false); - if (value.is_discarded()) { - // fallback - value = json(string); - } - array.push_back(value); - } - return array.dump(); -} - -std::shared_ptr ParamFactory::makeNamed(const std::string& name, const std::string& type) { - auto param = make(type); - if (!param) { - return nullptr; - } - return std::make_shared(name, param); -} - -bool ParamFactory::isPrimitive(const std::string& type) { - if (starts_with(type, "address")) { - return true; - } else if (starts_with(type, "uint")) { - return true; - } else if (starts_with(type, "int")) { - return true; - } else if (starts_with(type, "bool")) { - return true; - } else if (starts_with(type, "bytes")) { - return true; - } else if (starts_with(type, "string")) { - return true; - } - return false; -} - -std::string ParamFactory::getValue(const std::shared_ptr& param, const std::string& type) { - std::string result = ""; - if (isArrayType(type)) { - auto values = getArrayValue(param, type); - result = joinArrayElems(values); - } else if (type == "address") { - auto value = dynamic_pointer_cast(param); - result = hexEncoded(value->getData()); - } else if (type == "uint8") { - result = boost::lexical_cast((uint)dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint16") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint32") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint64") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint256" || type == "uint") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int8") { - result = boost::lexical_cast((int)dynamic_pointer_cast(param)->getVal()); - } else if (type == "int16") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int32") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int64") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int256" || type == "int") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (starts_with(type, "uint")) { - auto value = dynamic_pointer_cast(param); - result = boost::lexical_cast(value->getVal()); - } else if (starts_with(type, "int")) { - auto value = dynamic_pointer_cast(param); - result = boost::lexical_cast(value->getVal()); - } else if (type == "bool") { - auto value = dynamic_pointer_cast(param); - result = value->getVal() ? "true" : "false"; - } else if (type == "bytes") { - auto value = dynamic_pointer_cast(param); - result = hexEncoded(value->getVal()); - } else if (starts_with(type, "bytes")) { - auto value = dynamic_pointer_cast(param); - result = hexEncoded(value->getVal()); - } else if (type == "string") { - auto value = dynamic_pointer_cast(param); - result = value->getVal(); - } - return result; -} - -std::vector ParamFactory::getArrayValue(const std::shared_ptr& param, const std::string& type) { - if (!isArrayType(type)) { - return std::vector(); - } - auto array = dynamic_pointer_cast(param); - if (!array) { - return std::vector(); - } - auto elemType = getArrayElemType(type); - auto elems = array->getVal(); - std::vector values(elems.size()); - for (auto i = 0ul; i < elems.size(); ++i) { - values[i] = getValue(elems[i], elemType); - } - return values; -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamFactory.h b/src/Ethereum/ABI/ParamFactory.h deleted file mode 100644 index abeb490ce05..00000000000 --- a/src/Ethereum/ABI/ParamFactory.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Array.h" -#include "Bytes.h" -#include "ParamBase.h" -#include "ParamStruct.h" - -#include -#include - -#include "Wasm.h" - -namespace TW::Ethereum::ABI { - -/// Factory creates concrete ParamBase class from string type. -class ParamFactory { -public: - /// Create a param of given type - static std::shared_ptr make(const std::string& type); - /// Create a named param, with given name and type - static std::shared_ptr makeNamed(const std::string& name, const std::string& type); - /// Check if given type is a primitive type - static bool isPrimitive(const std::string& type); - - static std::string getValue(const std::shared_ptr& param, const std::string& type); - static std::vector getArrayValue(const std::shared_ptr& param, - const std::string& type); -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamNumber.cpp b/src/Ethereum/ABI/ParamNumber.cpp deleted file mode 100644 index 9def217a589..00000000000 --- a/src/Ethereum/ABI/ParamNumber.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamNumber.h" - -#include -#include -#include - -#include - -#include -#include - -namespace TW::Ethereum::ABI { - -bool ParamUInt256::setUInt256FromValueJson(uint256_t& dest, const std::string& value) { - // try hex string or number - if (value.length() >= 3 && value.substr(0, 2) == "0x") { - dest = load(parse_hex(value)); - return true; - } - return boost::conversion::detail::try_lexical_convert(value, dest); -} - -bool ParamInt256::setInt256FromValueJson(int256_t& dest, const std::string& value) { - // try hex string or number - if (value.length() >= 3 && value.substr(0, 2) == "0x") { - dest = ValueEncoder::int256FromUint256(load(parse_hex(value))); - return true; - } - return boost::conversion::detail::try_lexical_convert(value, dest); -} - -bool ParamBool::setValueJson(const std::string& value) { - if (value == "true" || value == "1") { - setVal(true); - return true; - } - if (value == "false" || value == "0") { - setVal(false); - return true; - } - return false; -} - -bool ParamUInt8::setValueJson(const std::string& value) { - uint16_t val; - if (!boost::conversion::detail::try_lexical_convert(value, val)) { - return false; - } - setVal(static_cast(val)); - return true; -} - -bool ParamInt8::setValueJson(const std::string& value) { - int16_t val; - if (!boost::conversion::detail::try_lexical_convert(value, val)) { - return false; - } - setVal(static_cast(val)); - return true; -} - -void ParamUIntN::setVal(uint256_t val) { - // mask it to the given bits - _val = val & _mask; -} - -bool ParamUIntN::decode(const Data& encoded, size_t& offset_inout) { - uint256_t temp; - auto res = decodeNumber(encoded, temp, offset_inout); - setVal(temp); - return res; -} - -void ParamUIntN::init() { - _mask = maskForBits(bits); -} - -uint256_t ParamUIntN::maskForBits(size_t bits) { - assert(bits >= 8 && bits <= 256 && (bits % 8) == 0); - // exclude predefined sizes - assert(bits != 8 && bits != 16 && bits != 32 && bits != 64 && bits != 256); - return (uint256_t(1) << bits) - 1; -} - -void ParamIntN::setVal(int256_t val) { - // mask it to the given bits - if (val < 0) { - _val = ValueEncoder::int256FromUint256(~((~((uint256_t)val)) & _mask)); - } else { - _val = ValueEncoder::int256FromUint256(((uint256_t)val) & _mask); - } -} - -bool ParamIntN::decodeNumber(const Data& encoded, int256_t& decoded, size_t& offset_inout) { - uint256_t valU; - auto res = ABI::decode(encoded, valU, offset_inout); - decoded = ValueEncoder::int256FromUint256(valU); - return res; -} - -bool ParamIntN::decode(const Data& encoded, size_t& offset_inout) { - int256_t temp; - auto res = decodeNumber(encoded, temp, offset_inout); - setVal(temp); - return res; -} - -void ParamIntN::init() { - _mask = ParamUIntN::maskForBits(bits); -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamNumber.h b/src/Ethereum/ABI/ParamNumber.h deleted file mode 100644 index a9b9ea6f631..00000000000 --- a/src/Ethereum/ABI/ParamNumber.h +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ValueEncoder.h" - -#include -#include - -#include - -#include - -namespace TW::Ethereum::ABI { - - -inline bool decode(const Data& encoded, uint256_t& decoded, size_t& offset_inout) -{ - decoded = 0u; - if (encoded.empty() || (encoded.size() < (ValueEncoder::encodedIntSize + offset_inout))) { - return false; - } - decoded = loadWithOffset(encoded, offset_inout); - offset_inout += ValueEncoder::encodedIntSize; - return true; -} - -/// Generic parameter class for numeric types, like bool, uint32, int64, etc. All are stored on 256 bits. -template -class ParamNumberType : public ParamBase -{ -public: - ParamNumberType() = default; - explicit ParamNumberType(const T& val): _val(val) { } - void setVal(T val) { _val = val; } - T getVal() const { return _val; } - virtual std::string getType() const = 0; - virtual size_t getSize() const { return ValueEncoder::encodedIntSize; } - virtual bool isDynamic() const { return false; } - virtual void encode(Data& data) const { - // cast up - ValueEncoder::encodeUInt256(static_cast(_val), data); - } - static bool decodeNumber(const Data& encoded, T& decoded, size_t& offset_inout) { - uint256_t val256; - if (!ABI::decode(encoded, val256, offset_inout)) { return false; } - // cast down - decoded = static_cast(val256); - return true; - } - virtual bool decode(const Data& encoded, size_t& offset_inout) { - return decodeNumber(encoded, _val, offset_inout); - } - virtual bool setValueJson(const std::string& value) { - return boost::conversion::detail::try_lexical_convert(value, _val); - } -protected: - T _val; -}; - -class ParamUInt256 final : public ParamNumberType -{ -public: - ParamUInt256() : ParamNumberType(uint256_t(0)) {} - explicit ParamUInt256(const uint256_t& val) : ParamNumberType(val) {} - std::string getType() const override { return "uint256"; } - uint256_t getVal() const { return ParamNumberType::getVal(); } - bool setValueJson(const std::string& value) override { return setUInt256FromValueJson(_val, value); } - static bool setUInt256FromValueJson(uint256_t& dest, const std::string& value); - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamInt256 final : public ParamNumberType -{ -public: - ParamInt256() : ParamNumberType(int256_t(0)) {} - explicit ParamInt256(const int256_t& val) : ParamNumberType(val) {} - std::string getType() const override { return "int256"; } - int256_t getVal() const { return ParamNumberType::getVal(); } - bool setValueJson(const std::string& value) override { return setInt256FromValueJson(_val, value); } - static bool setInt256FromValueJson(int256_t& dest, const std::string& value); - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamBool final : public ParamNumberType -{ -public: - ParamBool() : ParamNumberType(false) {} - explicit ParamBool(bool val) : ParamNumberType(val) {} - std::string getType() const override { return "bool"; } - bool getVal() const { return ParamNumberType::getVal(); } - bool setValueJson(const std::string& value) override; - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamUInt8 final : public ParamNumberType -{ -public: - ParamUInt8() : ParamNumberType(0) {} - explicit ParamUInt8(uint8_t val) : ParamNumberType(val) {} - std::string getType() const override { return "uint8"; } - bool setValueJson(const std::string& value) override; - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamInt8 final : public ParamNumberType -{ -public: - ParamInt8() : ParamNumberType(0) {} - explicit ParamInt8(int8_t val) : ParamNumberType(val) {} - std::string getType() const override { return "int8"; } - bool setValueJson(const std::string& value) override; - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamUInt16 final : public ParamNumberType -{ -public: - ParamUInt16() : ParamNumberType(0) {} - explicit ParamUInt16(uint16_t val) : ParamNumberType(val) {} - std::string getType() const override { return "uint16"; } - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamInt16 final : public ParamNumberType -{ -public: - ParamInt16() : ParamNumberType(0) {} - explicit ParamInt16(int16_t val) : ParamNumberType(val) {} - std::string getType() const override { return "int16"; } - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamUInt32 final : public ParamNumberType -{ -public: - ParamUInt32() : ParamNumberType(0) {} - explicit ParamUInt32(uint32_t val) : ParamNumberType(val) {} - std::string getType() const override { return "uint32"; } - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamInt32 final : public ParamNumberType -{ -public: - ParamInt32() : ParamNumberType(0) {} - explicit ParamInt32(int32_t val) : ParamNumberType(val) {} - std::string getType() const override { return "int32"; } - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamUInt64 final : public ParamNumberType -{ -public: - ParamUInt64() : ParamNumberType(0) {} - explicit ParamUInt64(uint64_t val) : ParamNumberType(val) {} - std::string getType() const override { return "uint64"; } - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -class ParamInt64 final : public ParamNumberType -{ -public: - ParamInt64() : ParamNumberType(0) {} - explicit ParamInt64(int64_t val) : ParamNumberType(val) {} - std::string getType() const override { return "int64"; } - std::shared_ptr clone() const override { return std::make_shared(_val); } -}; - -/// Generic parameter class for all other bit sizes, like UInt24, 40, 48, ... 248. -/// For predefined sizes (8, 16, 32, 64, 256) use the sepcial types like UInt32. -/// Stored on 256 bits. -class ParamUIntN final : public ParamBase -{ -public: - const size_t bits; - ParamUIntN(size_t bits_in) : bits(bits_in) { init(); } - ParamUIntN(size_t bits_in, uint256_t val) : bits(bits_in) { init(); setVal(val); } - void setVal(uint256_t val); - uint256_t getVal() const { return _val; } - std::string getType() const override { return "uint" + std::to_string(bits); } - size_t getSize() const override { return ValueEncoder::encodedIntSize; } - bool isDynamic() const override { return false; } - void encode(Data& data) const override { ValueEncoder::encodeUInt256(_val, data); } - static bool decodeNumber(const Data& encoded, uint256_t& decoded, size_t& offset_inout) { - return ABI::decode(encoded, decoded, offset_inout); - } - bool decode(const Data& encoded, size_t& offset_inout) override; - static uint256_t maskForBits(size_t bits); - bool setValueJson(const std::string& value) override { return ParamUInt256::setUInt256FromValueJson(_val, value); } - std::shared_ptr clone() const override { return std::make_shared(bits, _val); } - -private: - void init(); - uint256_t _val; - uint256_t _mask; -}; - -/// Generic parameter class for all other bit sizes, like Int24, 40, 48, ... 248. -/// For predefined sizes (8, 16, 32, 64, 256) use the sepcial types like Int32. -/// Stored on 256 bits. -class ParamIntN final : public ParamBase -{ -public: - const size_t bits; - ParamIntN(size_t bits_in) : bits(bits_in) { init(); } - ParamIntN(size_t bits_in, int256_t val) : bits(bits_in) { init(); setVal(val); } - void setVal(int256_t val); - int256_t getVal() const { return _val; } - std::string getType() const override { return "int" + std::to_string(bits); } - size_t getSize() const override { return ValueEncoder::encodedIntSize; } - bool isDynamic() const override { return false; } - void encode(Data& data) const override { ValueEncoder::encodeUInt256((uint256_t)_val, data); } - static bool decodeNumber(const Data& encoded, int256_t& decoded, size_t& offset_inout); - bool decode(const Data& encoded, size_t& offset_inout) override; - bool setValueJson(const std::string& value) override { return ParamInt256::setInt256FromValueJson(_val, value); } - std::shared_ptr clone() const override { return std::make_shared(bits, _val); } - -private: - void init(); - int256_t _val; - uint256_t _mask; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamStruct.cpp b/src/Ethereum/ABI/ParamStruct.cpp deleted file mode 100644 index 9d1c2690ad8..00000000000 --- a/src/Ethereum/ABI/ParamStruct.cpp +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamStruct.h" -#include "ValueEncoder.h" -#include "ParamFactory.h" -#include -#include - -#include -#include - -#include -#include - -namespace TW::Ethereum::ABI { - -using json = nlohmann::json; - -static const Data EipStructPrefix = parse_hex("1901"); -static const auto Eip712Domain = "EIP712Domain"; - -std::string ParamNamed::getType() const { - return _param->getType() + " " + _name; -} - -std::shared_ptr ParamNamed::cloneNamed() const { - return std::make_shared(_name, _param->clone()); -} - -ParamSetNamed::~ParamSetNamed() { - _params.clear(); -} - -/// Returns the index of the parameter -int ParamSetNamed::addParam(const std::shared_ptr& param) { - if (param.get() == nullptr) { - return -1; - } - assert(param.get() != nullptr); - _params.push_back(param); - return static_cast(_params.size() - 1); -} - -void ParamSetNamed::addParams(const std::vector>& params) { - for (auto p : params) { - addParam(p); - } -} - -std::string ParamSetNamed::getType() const { - std::string t = "("; - int cnt = 0; - for (auto p : _params) { - if (cnt++ > 0) { - t += ","; - } - t += p->getType(); - } - t += ")"; - return t; -} - -Data ParamSetNamed::encodeHashes() const { - Data hashes; - for (auto p : _params) { - append(hashes, p->hashStruct()); - } - return hashes; -} - -void ParamSetNamed::fillExtraTypesMap(ExtraTypesMap& extraTypes) const { - for (auto& p : _params) { - p->fillExtraTypesMap(extraTypes); - } -} - -std::shared_ptr ParamSetNamed::findParamByName(const std::string& name) const { - for (auto& p : _params) { - if (p->_name == name) { - return p; - } - } - return nullptr; -} - -ParamSetNamed ParamSetNamed::clone() const { - ParamSetNamed newSet; - for (const auto& p : _params) { - newSet.addParam(p->cloneNamed()); - } - return newSet; -} - -Data ParamStruct::hashType() const { - return Hash::keccak256(TW::data(encodeType())); -} - -Data ParamStruct::encodeHashes() const { - Data hashes; - Data paramsHashes = _params.encodeHashes(); - if (paramsHashes.size() > 0) { - auto fullType = encodeType(); - hashes = Hash::keccak256(TW::data(fullType)); - append(hashes, paramsHashes); - } - return hashes; -} - -Data ParamStruct::hashStruct() const { - Data hash(32); - Data hashes = encodeHashes(); - if (hashes.size() > 0) { - hash = Hash::keccak256(hashes); - } - return hash; -} - -std::string ParamStruct::encodeType() const { - ExtraTypesMap extraTypes; - fillExtraTypesMap(extraTypes); - auto structFullType = extraTypes[_name]; - extraTypes.erase(_name); - - for (const auto& [paramName, paramType] : extraTypes) { - structFullType += paramType; - } - return structFullType; -} - -void ParamStruct::fillExtraTypesMap(ExtraTypesMap& extraTypes) const { - if (extraTypes.contains(_name)) { - return; - } - extraTypes[_name] = _name + _params.getType(); - _params.fillExtraTypesMap(extraTypes); -} - -std::shared_ptr ParamStruct::clone() const { - return std::make_shared(_name, _params.clone()); -} - -Data ParamStruct::hashStructJson(const std::string& messageJson) { - auto message = json::parse(messageJson, nullptr, false); - if (message.is_discarded()) { - throw std::invalid_argument("Could not parse Json"); - } - if (!message.is_object()) { - throw std::invalid_argument("Expecting Json object"); - } - if (!message.contains("primaryType") || !message["primaryType"].is_string()) { - throw std::invalid_argument("Top-level string field 'primaryType' missing"); - } - if (!message.contains("domain") || !message["domain"].is_object()) { - throw std::invalid_argument("Top-level object field 'domain' missing"); - } - if (!message.contains("message") || !message["message"].is_object()) { - throw std::invalid_argument("Top-level object field 'message' missing"); - } - if (!message.contains("types") || !message["types"].is_object()) { - throw std::invalid_argument("Top-level object field 'types' missing"); - } - - // concatenate hashes - Data hashes = EipStructPrefix; - - auto domainStruct = makeStruct( - Eip712Domain, - message["domain"].dump(), - message["types"].dump()); - if (domainStruct) { - TW::append(hashes, domainStruct->hashStruct()); - - auto messageStruct = makeStruct( - message["primaryType"].get(), - message["message"].dump(), - message["types"].dump()); - if (messageStruct) { - const auto messageHash = messageStruct->hashStruct(); - TW::append(hashes, messageHash); - return Hash::keccak256(hashes); - } - } - return {}; // fallback -} - -std::shared_ptr findType(const std::string& typeName, const std::vector>& types) { - for (auto& t : types) { - if (t->getType() == typeName) { - return t; - } - } - return nullptr; -} - -std::shared_ptr ParamStruct::makeStruct(const std::string& structType, const std::string& valueJson, const std::string& typesJson) { - try { - // parse types - auto types = makeTypes(typesJson); - // find type info - auto typeInfo = findType(structType, types); - if (!typeInfo) { - throw std::invalid_argument("Type not found, " + structType); - } - auto values = json::parse(valueJson, nullptr, false); - if (values.is_discarded()) { - throw std::invalid_argument("Could not parse value Json"); - } - if (!values.is_object()) { - throw std::invalid_argument("Expecting object"); - } - std::vector> params; - const auto& typeParams = typeInfo->getParams(); - // iterate through the type; order is important and field order in the value json is not defined - for (auto i = 0ul; i < typeParams.getCount(); ++i) { - auto name = typeParams.getParam(static_cast(i))->getName(); - auto type = typeParams.getParam(static_cast(i))->getParam()->getType(); - // look for it in value (may throw) - auto value = values[name]; - // first try simple params - auto paramVal = ParamFactory::make(type); - if (paramVal) { - if (!values.is_null()) { - std::string valueString = value.is_string() ? value.get() : value.dump(); - if (!paramVal->setValueJson(valueString)) { - throw std::invalid_argument("Could not set type for param " + name); - } - } - params.push_back(std::make_shared(name, paramVal)); - } else if (type.length() >= 2 && type.substr(type.length() - 2, 2) == "[]") { - // array of struct - auto arrayType = type.substr(0, type.length() - 2); - auto subTypeInfo = findType(arrayType, types); - if (!subTypeInfo) { - throw std::invalid_argument("Could not find type for array sub-struct " + arrayType); - } - if (!value.is_array()) { - throw std::invalid_argument("Value must be array for type " + type); - } - std::vector> paramsArray; - if (value.size() == 0) { - // empty array - auto subStruct = makeStruct(arrayType, "{}", typesJson); - if (!subStruct) { - throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + "{}"); - } - assert(subStruct); - auto tmp = std::make_shared(paramsArray); - tmp->setProto(subStruct); - params.push_back(std::make_shared(name, tmp)); - } else { - for (const auto& e : value) { - auto subStruct = makeStruct(arrayType, e.dump(), typesJson); - if (!subStruct) { - throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + e.dump()); - } - assert(subStruct); - paramsArray.push_back(subStruct); - } - params.push_back(std::make_shared(name, std::make_shared(paramsArray))); - } - } else { - // try if sub struct - auto subTypeInfo = findType(type, types); - if (!subTypeInfo) { - throw std::invalid_argument("Could not find type for sub-struct " + type); - } - if (value.is_null()) { - params.push_back(std::make_shared(name, std::make_shared(type, std::vector>{}))); - } else { - auto subStruct = makeStruct(type, value.dump(), typesJson); - if (!subStruct) { - throw std::invalid_argument("Could not process sub-struct " + type); - } - assert(subStruct); - params.push_back(std::make_shared(name, subStruct)); - } - } - } - return std::make_shared(structType, params); - } catch (const std::invalid_argument& ex) { - throw; - } catch (const std::exception& ex) { - throw std::invalid_argument(std::string("Could not process Json: ") + ex.what()); - } catch (...) { - throw std::invalid_argument("Could not process Json"); - } -} - -std::shared_ptr ParamStruct::makeType(const std::string& structName, const std::string& structJson, const std::vector>& extraTypes, bool ignoreMissingType) { - try { - if (structName.empty()) { - throw std::invalid_argument("Missing type name"); - } - auto jsonValue = json::parse(structJson, nullptr, false); - if (jsonValue.is_discarded()) { - throw std::invalid_argument("Could not parse type Json"); - } - if (!jsonValue.is_array()) { - throw std::invalid_argument("Expecting array"); - } - std::vector> params; - for (auto& p2 : jsonValue) { - auto name = p2["name"].get(); - auto type = p2["type"].get(); - if (name.empty() || type.empty()) { - throw std::invalid_argument("Expecting 'name' and 'type', in " + structName); - } - auto named = ParamFactory::makeNamed(name, type); - if (named) { - // simple type (incl. array of simple type) - params.push_back(named); - } else if (type == structName) { - // recursive to self - params.push_back(std::make_shared(name, std::make_shared(type, std::vector>{}))); - } else if (type.length() >= 2 && type.substr(type.length() - 2, 2) == "[]") { - // array of struct - auto arrayType = type.substr(0, type.length() - 2); - if (ignoreMissingType) { - params.push_back(std::make_shared(name, std::make_shared(std::make_shared(arrayType, std::vector>{})))); - } else { - // try array struct from extra types - auto p2struct = findType(arrayType, extraTypes); - if (!p2struct) { - throw std::invalid_argument("Unknown struct array type " + arrayType); - } - params.push_back(std::make_shared(name, std::make_shared(p2struct))); - } - } else { - if (ignoreMissingType) { - params.push_back(std::make_shared(name, std::make_shared(type, std::vector>{}))); - } else { - // try struct from extra types - auto p2struct = findType(type, extraTypes); - if (!p2struct) { - throw std::invalid_argument("Unknown type " + type); - } - params.push_back(std::make_shared(name, p2struct)); - } - } - } - if (params.size() == 0) { - throw std::invalid_argument("No valid params found"); - } - return std::make_shared(structName, params); - } catch (const std::invalid_argument& ex) { - throw; - } catch (const std::exception& ex) { - throw std::invalid_argument(std::string("Could not process Json: ") + ex.what()); - } catch (...) { - throw std::invalid_argument("Could not process Json"); - } -} - -std::vector> ParamStruct::makeTypes(const std::string& structTypes) { - try { - auto jsonValue = json::parse(structTypes, nullptr, false); - if (jsonValue.is_discarded()) { - throw std::invalid_argument("Could not parse types Json"); - } - if (!jsonValue.is_object()) { - throw std::invalid_argument("Expecting object"); - } - // do it in 2 passes, as type order may be undefined - std::vector> types1; - for (json::iterator it = jsonValue.begin(); it != jsonValue.end(); it++) { - // may throw - auto struct1 = makeType(it.key(), it.value().dump(), {}, true); - types1.push_back(struct1); - } - std::vector> types2; - for (json::iterator it = jsonValue.begin(); it != jsonValue.end(); it++) { - // may throw - auto struct1 = makeType(it.key(), it.value().dump(), types1, false); - types2.push_back(struct1); - } - return types2; - } catch (const std::invalid_argument& ex) { - throw; - } catch (const std::exception& ex) { - throw std::invalid_argument(std::string("Could not process Json: ") + ex.what()); - } catch (...) { - throw std::invalid_argument("Could not process Json"); - } -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamStruct.h b/src/Ethereum/ABI/ParamStruct.h deleted file mode 100644 index 0368029c776..00000000000 --- a/src/Ethereum/ABI/ParamStruct.h +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" - -#include -#include -#include - -namespace TW::Ethereum::ABI { - -/// A named parameter. -class ParamNamed final : public ParamBase -{ -public: - std::string _name; - std::shared_ptr _param; - -public: - explicit ParamNamed(const std::string& name, std::shared_ptr param): _name(name), _param(param) {} - - std::string getName() const { return _name; } - std::shared_ptr getParam() const { return _param; } - std::string getType() const override; - size_t getSize() const override { return _param->getSize(); } - bool isDynamic() const override { return _param->isDynamic(); } - void encode(Data& data) const override { return _param->encode(data); } - bool decode(const Data& encoded, size_t& offset_inout) override { return _param->decode(encoded, offset_inout); } - bool setValueJson(const std::string& value) override { return _param->setValueJson(value); } - Data hashStruct() const override { return _param->hashStruct(); } - void fillExtraTypesMap(ExtraTypesMap& extraTypes) const override { _param->fillExtraTypesMap(extraTypes); } - std::shared_ptr clone() const override { return cloneNamed(); } - std::shared_ptr cloneNamed() const; -}; - -/// A collection of named parameters. See also: ParamStruct -class ParamSetNamed { -private: - std::vector> _params; - -public: - ParamSetNamed() = default; - ParamSetNamed(const std::shared_ptr& param1) { addParam(param1); } - ParamSetNamed(const std::vector>& params) { addParams(params); } - virtual ~ParamSetNamed(); - - /// Returns the index of the parameter - int addParam(const std::shared_ptr& param); - void addParams(const std::vector>& params); - size_t getCount() const { return _params.size(); } - std::shared_ptr getParam(int idx) const { return _params[idx]; } - std::string getType() const; - Data encodeHashes() const; - void fillExtraTypesMap(ExtraTypesMap& extraTypes) const; - std::shared_ptr findParamByName(const std::string& name) const; - ParamSetNamed clone() const; -}; - -/// A named structure (set of parameters plus a type name). -class ParamStruct final : public ParamCollection -{ -private: - std::string _name; - ParamSetNamed _params; - -public: - ParamStruct() = default; - ParamStruct(const std::string& name, const std::vector>& params) : ParamCollection(), _name(name), _params(ParamSetNamed(params)) {} - ParamStruct(const std::string& name, ParamSetNamed&& params) : ParamCollection(), _name(name), _params(std::move(params)) {} - - std::string getType() const override { return _name; } - const ParamSetNamed& getParams() const { return _params; } - - /// Compute the hash of a struct, used for signing, according to EIP712 - Data hashStruct() const override; - /// Get full type, extended by used sub-types, of the form 'Mail(Person from,Person to,string contents)Person(string name,address wallet)' - std::string encodeType() const; - /// Get the hash of the full type. - Data hashType() const; - - size_t getSize() const override { return _params.getCount(); } - bool isDynamic() const override { return true; } - size_t getCount() const override { return _params.getCount(); } - void encode([[maybe_unused]] Data& data) const override {} - bool decode([[maybe_unused]] const Data& encoded, [[maybe_unused]] size_t& offset_inout) override { return true; } - bool setValueJson([[maybe_unused]] const std::string& value) override { return false; } // see makeStruct - Data encodeHashes() const; - void fillExtraTypesMap(ExtraTypesMap& extraTypes) const override; - std::shared_ptr clone() const override; - std::shared_ptr findParamByName(const std::string& name) const { return _params.findParamByName(name); } - - /// Compute the hash of a struct, used for signing, according to EIP712 ("v4"). - /// Input is a Json object (as string), with following fields: - /// - types: map of used struct types (see makeTypes()) - /// - primaryType: the type of the message (string) - /// - domain: EIP712 domain specifier values - /// - message: the message (object). - /// Throws on error. - /// Example input: - /// R"({ - /// "types": { - /// "EIP712Domain": [ - /// {"name": "name", "type": "string"}, - /// {"name": "version", "type": "string"}, - /// {"name": "chainId", "type": "uint256"}, - /// {"name": "verifyingContract", "type": "address"} - /// ], - /// "Person": [ - /// {"name": "name", "type": "string"}, - /// {"name": "wallet", "type": "address"} - /// ] - /// }, - /// "primaryType": "Person", - /// "domain": { - /// "name": "Ether Person", - /// "version": "1", - /// "chainId": 1, - /// "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - /// }, - /// "message": { - /// "name": "Cow", - /// "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - /// } - /// })"); - static Data hashStructJson(const std::string& messageJson); - - /// Make a named struct, described by a json string (with values), and its type info (may contain type info of sub-types also). - /// Throws on error. - static std::shared_ptr makeStruct(const std::string& structType, const std::string& valueJson, const std::string& typesJson); - - /// Parse a json with a list of types, and build a vector of named structs. Structs params have the given name and type, and empty value. - /// Ex. input: R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}], "Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}]})" - /// Order does not matter. Note the quote delimiters. - /// Throws on error. - static std::vector> makeTypes(const std::string& structTypes); - - /// Make a named struct, with the given types, with empty values. - /// Similar to makeTypes, but works with only one type. - /// Ex. input: "Person", R"([{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}])" - /// Throws on error. - static std::shared_ptr makeType(const std::string& structName, const std::string& structJson, const std::vector>& extraTypes = {}, bool ignoreMissingType = false); -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp deleted file mode 100644 index 973443fa540..00000000000 --- a/src/Ethereum/ABI/Parameters.cpp +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Parameters.h" -#include "ValueEncoder.h" -#include - -#include -#include - -namespace TW::Ethereum::ABI { - -ParamSet::~ParamSet() { - _params.clear(); -} - -/// Returns the index of the parameter -int ParamSet::addParam(const std::shared_ptr& param) { - if (param.get() == nullptr) { - return -1; - } - assert(param.get() != nullptr); - _params.push_back(param); - return static_cast(_params.size() - 1); -} - -void ParamSet::addParams(const std::vector>& params) { - for (auto p : params) { - addParam(p); - } -} - -bool ParamSet::getParam(size_t paramIndex, std::shared_ptr& param_out) const { - if (paramIndex >= _params.size() || paramIndex < 0) { - return false; - } - param_out = _params[paramIndex]; - return true; -} - -std::shared_ptr ParamSet::getParamUnsafe(size_t paramIndex) const { - if (_params.size() == 0) { - // zero parameter, nothing to return. This may cause trouble (segfault) - return nullptr; - } - if (paramIndex >= _params.size() || paramIndex < 0) { - // invalid index, return the first instead of nullptr - return _params[0]; - } - return _params[paramIndex]; -} - -/// Return the function type signature, of the form "baz(int32,uint256)" -std::string ParamSet::getType() const { - std::string t = "("; - int cnt = 0; - for (auto p : _params) { - if (cnt++ > 0) { - t += ","; - } - t += p->getType(); - } - t += ")"; - return t; -} - -bool ParamSet::isDynamic() const { - for (const auto& p : _params) { - if (p->isDynamic()) { - return true; - } - } - return false; -} - -size_t ParamSet::getSize() const { - // 2-pass encoding - size_t s = 0; - for (auto p : _params) { - if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { - // offset used - s += 32; - } - s += p->getSize(); - } - return ValueEncoder::paddedTo32(s); -} - -size_t ParamSet::getHeadSize() const { - size_t s = 0; - for (auto p : _params) { - if (p->isDynamic()) { - s += 32; - } else { - s += p->getSize(); - } - } - return s; -} - -void ParamSet::encode(Data& data) const { - // 2-pass encoding - size_t headSize = getHeadSize(); - size_t dynamicOffset = 0; - - // pass 1: small values or indices - for (auto p : _params) { - if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { - // include only offset - ValueEncoder::encodeUInt256(uint256_t(headSize + dynamicOffset), data); - dynamicOffset += p->getSize(); - } else { - // encode small data - p->encode(data); - } - } - - // pass 2: dynamic values - for (auto p : _params) { - if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { - // encode large data - p->encode(data); - } - } -} - -bool ParamSet::decode(const Data& encoded, size_t& offset_inout) { - size_t arrayOffset = offset_inout; - - for (const auto& p : _params) { - // Decode a dynamic element. - if (p->isDynamic()) { - uint256_t index; - if (!ABI::decode(encoded, index, offset_inout)) { - return false; - } - - // Check if length is in the size_t range. - auto indexSize = static_cast(index); - if (indexSize != index) { - return false; - } - - // Calculate an offset relative to the beginning of this array. - size_t newOffset = arrayOffset + indexSize; - if (!p->decode(encoded, newOffset)) { - return false; - } - - continue; - } - - // Otherwise decode a static element, e.g `p->isDynamic() == false`. - if (!p->decode(encoded, offset_inout)) { - return false; - } - } - - return true; -} - -Data ParamSet::encodeHashes() const { - Data hashes; - for (auto p : _params) { - append(hashes, p->hashStruct()); - } - return hashes; -} - -ParamSet ParamSet::clone() const { - ParamSet newSet; - for (const auto& p : _params) { - newSet.addParam(p->clone()); - } - - return newSet; -} - -Data Parameters::hashStruct() const { - Data hash(32); - Data hashes = _params.encodeHashes(); - if (hashes.size() > 0) { - hash = Hash::keccak256(hashes); - } - return hash; -} - -std::shared_ptr Parameters::clone() const { - auto newParams = std::make_shared(); - newParams->_params = _params.clone(); - return newParams; -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h deleted file mode 100644 index cc6d0844cd7..00000000000 --- a/src/Ethereum/ABI/Parameters.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ParamNumber.h" - -#include -#include -#include - -namespace TW::Ethereum::ABI { - -/// A set of parameters -class ParamSet { -private: - std::vector> _params; - -public: - ParamSet() = default; - ParamSet(const std::shared_ptr& param1) { addParam(param1); } - ParamSet(const std::vector>& params) { addParams(params); } - virtual ~ParamSet(); - - /// Returns the index of the parameter - int addParam(const std::shared_ptr& param); - void addParams(const std::vector>& params); - bool getParam(size_t paramIndex, std::shared_ptr& param_out) const; - std::shared_ptr getParamUnsafe(size_t paramIndex) const; - size_t getCount() const { return _params.size(); } - std::vector> const& getParams() const { return _params; } - /// Return the function type signature, of the form "baz(int32,uint256)" - std::string getType() const; - bool isDynamic() const; - size_t getSize() const; - virtual void encode(Data& data) const; - virtual bool decode(const Data& encoded, size_t& offset_inout); - Data encodeHashes() const; - /// Creates a copy of this set with exactly the same params. - ParamSet clone() const; - -private: - size_t getHeadSize() const; -}; - -/// Collection of different parameters, dynamic length, "(,,...)". -class Parameters final : public ParamCollection -{ -private: - ParamSet _params; - -public: - Parameters() = default; - Parameters(const std::vector>& params) : ParamCollection(), _params(ParamSet(params)) {} - void addParam(const std::shared_ptr& param) { _params.addParam(param); } - void addParams(const std::vector>& params) { _params.addParams(params); } - std::shared_ptr getParam(size_t paramIndex) const { return _params.getParamUnsafe(paramIndex); } - std::string getType() const override { return _params.getType(); } - size_t getSize() const override { return _params.getSize(); } - bool isDynamic() const override { return true; } - size_t getCount() const override { return _params.getCount(); } - void encode(Data& data) const override { _params.encode(data); } - bool decode(const Data& encoded, size_t& offset_inout) override { return _params.decode(encoded, offset_inout); } - bool setValueJson([[maybe_unused]] const std::string& value) override { return false; } - Data hashStruct() const override; - std::shared_ptr clone() const override; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ProtoParam.h b/src/Ethereum/ABI/ProtoParam.h new file mode 100644 index 00000000000..30acb10abc4 --- /dev/null +++ b/src/Ethereum/ABI/ProtoParam.h @@ -0,0 +1,115 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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/EthereumAbi.pb.h" +#include "uint256.h" + +namespace TW::Ethereum::ABI { + +namespace AbiProto = EthereumAbi::Proto; + +struct BaseProtoParam { + virtual ~BaseProtoParam() noexcept = default; + + virtual AbiProto::Token toToken() const = 0; +}; + +using BaseParams = std::vector>; + +class ProtoBool final: public BaseProtoParam { +public: + explicit ProtoBool(bool val): m_value(val) { + } + + ~ProtoBool() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_boolean(m_value); + return proto; + } + +private: + bool m_value = false; +}; + +class ProtoUInt256 final: public BaseProtoParam { +public: + explicit ProtoUInt256(const uint256_t &num): m_number(store(num)) { + } + + explicit ProtoUInt256(Data numData): m_number(std::move(numData)) { + } + + ~ProtoUInt256() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.mutable_number_uint()->set_bits(256); + proto.mutable_number_uint()->set_value(m_number.data(), m_number.size()); + return proto; + } + +private: + Data m_number; +}; + +class ProtoByteArray final: public BaseProtoParam { +public: + explicit ProtoByteArray(Data data): m_data(std::move(data)) { + } + + ~ProtoByteArray() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_byte_array(m_data.data(), m_data.size()); + return proto; + } + +private: + Data m_data; +}; + +class ProtoString final: public BaseProtoParam { +public: + explicit ProtoString(std::string str): m_string(std::move(str)) { + } + + ~ProtoString() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_string_value(m_string.data(), m_string.size()); + return proto; + } + +private: + std::string m_string; +}; + +class ProtoAddress final: public BaseProtoParam { +public: + ProtoAddress() = default; + + explicit ProtoAddress(std::string addr): m_address(std::move(addr)) { + } + + ~ProtoAddress() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_address(m_address); + return proto; + } + +private: + std::string m_address {"0x0000000000000000000000000000000000000000"}; +}; + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Tuple.cpp b/src/Ethereum/ABI/Tuple.cpp deleted file mode 100644 index 1f661b4aeca..00000000000 --- a/src/Ethereum/ABI/Tuple.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tuple.h" - -#include - -namespace TW::Ethereum::ABI { - -int ParamTuple::addParam(std::shared_ptr param) { - return _params.addParam(param); -} - -std::shared_ptr ParamTuple::clone() const { - auto newTuple = std::make_shared(); - newTuple->_params = _params.clone(); - return newTuple; -} - -} // namespace TW::Ethereum::ABI \ No newline at end of file diff --git a/src/Ethereum/ABI/Tuple.h b/src/Ethereum/ABI/Tuple.h deleted file mode 100644 index 2911703987e..00000000000 --- a/src/Ethereum/ABI/Tuple.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "Parameters.h" -#include "Data.h" - -namespace TW::Ethereum::ABI { - -/// A Tuple is a collection of parameters -class ParamTuple final : public ParamCollection -{ -public: - ParamSet _params; - - ParamTuple() = default; - explicit ParamTuple(const std::vector>& params) : _params(ParamSet(params)) {} - - /// Add a parameter. Returns the index of the parameter. - int addParam(std::shared_ptr param); - /// Get a parameter. - bool getParam(int paramIndex, std::shared_ptr& param_out) { - return _params.getParam(paramIndex, param_out); - } - /// Return the type signature, of the form "(int32,uint256)" - std::string getType() const override { return _params.getType(); } - - size_t getSize() const override { return _params.getSize(); } - bool isDynamic() const override { return _params.isDynamic(); } - void encode(Data& data) const override { return _params.encode(data); } - bool decode(const Data& encoded, size_t& offset_inout) override { return _params.decode(encoded, offset_inout); } - bool setValueJson([[maybe_unused]] const std::string& value) override { return false; } - size_t getCount() const override { return _params.getCount(); } - std::shared_ptr clone() const override; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueDecoder.cpp b/src/Ethereum/ABI/ValueDecoder.cpp index 86544101070..ac46303ce05 100644 --- a/src/Ethereum/ABI/ValueDecoder.cpp +++ b/src/Ethereum/ABI/ValueDecoder.cpp @@ -5,8 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "ValueDecoder.h" -#include "Array.h" -#include "ParamFactory.h" +#include "proto/EthereumAbi.pb.h" +#include "rust/Wrapper.h" +#include "TrustWalletCore/TWCoinType.h" namespace TW::Ethereum::ABI { @@ -17,29 +18,27 @@ uint256_t ValueDecoder::decodeUInt256(const Data& data) { return load(data); } -std::string ValueDecoder::decodeValue(const Data& data, const std::string& type) { - auto param = ParamFactory::make(type); - if (!param) { +std::string ValueDecoder::decodeValue(const Data& encoded, const std::string& type) { + EthereumAbi::Proto::ValueDecodingInput input; + input.set_encoded(encoded.data(), encoded.size()); + input.set_param_type(type); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_value(TWCoinTypeEthereum, inputData.get()); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return ""; } - size_t offset = 0; - if (!param->decode(data, offset)) { + + EthereumAbi::Proto::ValueDecodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != EthereumAbi::Proto::AbiError::OK) { return ""; } - return ParamFactory::getValue(param, param->getType()); -} -std::vector ValueDecoder::decodeArray(const Data& data, const std::string& type) { - auto param = ParamFactory::make(type); - if (!param) { - return std::vector{}; - } - size_t offset = 0; - if (!param->decode(data, offset)) { - return std::vector{}; - } - auto values = ParamFactory::getArrayValue(param, type); - return values; + return output.param_str(); } } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueDecoder.h b/src/Ethereum/ABI/ValueDecoder.h index aee43f8655a..049bda9231a 100644 --- a/src/Ethereum/ABI/ValueDecoder.h +++ b/src/Ethereum/ABI/ValueDecoder.h @@ -19,7 +19,5 @@ class ValueDecoder { static uint256_t decodeUInt256(const Data& data); // Decode an arbitrary type, return value as string static std::string decodeValue(const Data& data, const std::string& type); - // Decode an array of given simple types; return each element as a string in a vector - static std::vector decodeArray(const Data& data, const std::string& elementType); }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index e6cc3e92c56..d3fb86ba45a 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.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 "ABI.h" +#include "ABI/Function.h" #include "AddressChecksum.h" #include "EIP1014.h" #include "Hash.h" @@ -15,23 +15,21 @@ namespace TW::Barz { -using ParamBasePtr = std::shared_ptr; -using ParamCollection = std::vector; - std::string getCounterfactualAddress(const Proto::ContractAddressInput input) { - auto params = Ethereum::ABI::ParamTuple(); - params.addParam(std::make_shared(parse_hex(input.account_facet()))); - params.addParam(std::make_shared(parse_hex(input.verification_facet()))); - params.addParam(std::make_shared(parse_hex(input.entry_point()))); - params.addParam(std::make_shared(parse_hex(input.facet_registry()))); - params.addParam(std::make_shared(parse_hex(input.default_fallback()))); - params.addParam(std::make_shared(parse_hex(input.public_key()))); - - Data encoded; - params.encode(encoded); + auto encodedData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(input.account_facet()), + std::make_shared(input.verification_facet()), + std::make_shared(input.entry_point()), + std::make_shared(input.facet_registry()), + std::make_shared(input.default_fallback()), + std::make_shared(parse_hex(input.public_key())), + }); + if (!encodedData.has_value()) { + return {}; + } Data initCode = parse_hex(input.bytecode()); - append(initCode, encoded); + append(initCode, encodedData.value()); const Data initCodeHash = Hash::keccak256(initCode); Data salt = store(input.salt(), 32); @@ -39,16 +37,18 @@ std::string getCounterfactualAddress(const Proto::ContractAddressInput input) { } Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet, const uint32_t salt) { - auto createAccountFunc = Ethereum::ABI::Function("createAccount", ParamCollection{ - std::make_shared(parse_hex(verificationFacet)), - std::make_shared(publicKey.bytes), - std::make_shared(salt)}); - Data createAccountFuncEncoded; - createAccountFunc.encode(createAccountFuncEncoded); + auto createAccountFuncEncoded = Ethereum::ABI::Function::encodeFunctionCall("createAccount", Ethereum::ABI::BaseParams { + std::make_shared(verificationFacet), + std::make_shared(publicKey.bytes), + std::make_shared(salt), + }); + if (!createAccountFuncEncoded.has_value()) { + return {}; + } Data envelope; append(envelope, parse_hex(factoryAddress)); - append(envelope, createAccountFuncEncoded); + append(envelope, createAccountFuncEncoded.value()); return envelope; } @@ -67,22 +67,24 @@ Data getFormattedSignature(const Data& signature, const Data challenge, const Da const auto parsedSignatureOptional = ASN::AsnParser::ecdsa_signature_from_der(signature); if (!parsedSignatureOptional.has_value()) { - return Data(); + return {}; } const Data parsedSignature = parsedSignatureOptional.value(); const Data rValue = subData(parsedSignature, 0, 32); const Data sValue = subData(parsedSignature, 32, 64); - auto params = Ethereum::ABI::ParamTuple(); - params.addParam(std::make_shared(uint256_t(hexEncoded(rValue)))); - params.addParam(std::make_shared(uint256_t(hexEncoded(sValue)))); - params.addParam(std::make_shared(authenticatorData)); - params.addParam(std::make_shared(clientDataJSONPre)); - params.addParam(std::make_shared(clientDataJSONPost)); + auto encoded = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(rValue), + std::make_shared(sValue), + std::make_shared(authenticatorData), + std::make_shared(clientDataJSONPre), + std::make_shared(clientDataJSONPost), + }); - Data encoded; - params.encode(encoded); - return encoded; + if (encoded.has_value()) { + return encoded.value(); + } + return {}; } } // namespace TW::Barz diff --git a/src/Ethereum/ContractCall.cpp b/src/Ethereum/ContractCall.cpp index f2850c21dc4..d8d996c2deb 100644 --- a/src/Ethereum/ContractCall.cpp +++ b/src/Ethereum/ContractCall.cpp @@ -5,160 +5,36 @@ // 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 "proto/EthereumAbi.pb.h" +#include "TrustWalletCore/TWCoinType.h" using namespace std; using json = nlohmann::json; namespace TW::Ethereum::ABI { -static void fillArray(ParamSet& paramSet, const string& type) { - auto baseType = string(type.begin(), type.end() - 2); - auto value = ParamFactory::make(baseType); - auto param = make_shared(value); - paramSet.addParam(param); -} - -static void fill(ParamSet& paramSet, const string& type) { - if (boost::algorithm::ends_with(type, "[]")) { - fillArray(paramSet, type); - } else { - auto param = ParamFactory::make(type); - paramSet.addParam(param); - } -} - -static vector getArrayValue(const ParamSet& paramSet, const string& type, int idx) { - shared_ptr param; - paramSet.getParam(idx, param); - return ParamFactory::getArrayValue(param, type); -} - -static json buildInputs(const ParamSet& paramSet, const json& registry); // forward - -static json getTupleValue(const ParamSet& paramSet, [[maybe_unused]] const string& type, int idx, const json& typeInfo) { - shared_ptr param; - paramSet.getParam(idx, param); - auto paramTuple = dynamic_pointer_cast(param); - if (!paramTuple.get()) { - return {}; - } - return buildInputs(paramTuple->_params, typeInfo["components"]); -} - -static vector getArrayValueTuple(const ParamSet& paramSet, int idx, const json& typeInfo) { - shared_ptr param; - paramSet.getParam(idx, param); - - auto array = dynamic_pointer_cast(param); - if (!array) { - return {}; - } - - std::vector values; - for (const auto& tuple : array->getVal()) { - if (auto paramTuple = dynamic_pointer_cast(tuple); paramTuple) { - values.emplace_back(buildInputs(paramTuple->_params, typeInfo["components"])); - } - } - - return values; -} - -static string getValue(const ParamSet& paramSet, const string& type, int idx) { - shared_ptr param; - paramSet.getParam(idx, param); - return ParamFactory::getValue(param, type); -} - -static json buildInputs(const ParamSet& paramSet, const json& registry) { - auto inputs = json::array(); - for (auto i = 0ul; i < registry.size(); i++) { - auto info = registry[i]; - auto type = info["type"]; - auto input = json{ - {"name", info["name"]}, - {"type", type} - }; - if (type == "tuple[]") { - input["components"] = getArrayValueTuple(paramSet, static_cast(i), info); - } else if (boost::algorithm::ends_with(type.get(), "[]")) { - input["value"] = json(getArrayValue(paramSet, type, static_cast(i))); - } else if (type == "tuple") { - input["components"] = getTupleValue(paramSet, type, static_cast(i), info); - } else if (type == "bool") { - input["value"] = getValue(paramSet, type, static_cast(i)) == "true" ? json(true) : json(false); - } else { - input["value"] = getValue(paramSet, type, static_cast(i)); - } - inputs.push_back(input); - } - return inputs; -} - -void fillTuple(ParamSet& paramSet, const json& jsonSet); // forward - -void fillTupleArray(ParamSet& paramSet, const json& jsonSet); // forward - -void decodeParamSet(ParamSet& paramSet, const json& jsonSet) { - for (auto& comp : jsonSet) { - if (comp["type"] == "tuple") { - fillTuple(paramSet, comp["components"]); - } else if (comp["type"] == "tuple[]") { - fillTupleArray(paramSet, comp["components"]); - } else { - fill(paramSet, comp["type"]); - } - } -} - -void fillTuple(ParamSet& paramSet, const json& jsonSet) { - std::shared_ptr param = make_shared(); - decodeParamSet(param->_params, jsonSet); - paramSet.addParam(param); -} - -void fillTupleArray(ParamSet& paramSet, const json& jsonSet) { - std::shared_ptr tuple = make_shared(); - decodeParamSet(tuple->_params, jsonSet); - - auto tupleArray = make_shared(tuple); - paramSet.addParam(tupleArray); -} - -optional decodeCall(const Data& call, const json& abi) { - // check bytes length - if (call.size() <= 4) { - return {}; - } +optional decodeCall(const Data& call, const std::string& abi) { + EthereumAbi::Proto::ContractCallDecodingInput input; + input.set_encoded(call.data(), call.size()); + input.set_smart_contract_abi_json(abi); - auto methodId = hex(Data(call.begin(), call.begin() + 4)); + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_contract_call(TWCoinTypeEthereum, inputData.get()); - if (abi.find(methodId) == abi.end()) { + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return {}; } - // build Function with types - const auto registry = abi[methodId]; - auto func = Function(registry["name"]); - decodeParamSet(func._inParams, registry["inputs"]); + EthereumAbi::Proto::ContractCallDecodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); - // decode inputs - size_t offset = 0; - auto success = func.decodeInput(call, offset); - if (!success) { + if (output.error() != EthereumAbi::Proto::AbiError::OK) { return {}; } - // build output json - auto decoded = json{ - {"function", func.getType()}, - {"inputs", buildInputs(func._inParams, registry["inputs"])}, - }; - return decoded.dump(); + return output.decoded_json(); } } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ContractCall.h b/src/Ethereum/ContractCall.h index d07f11712aa..0060abfddba 100644 --- a/src/Ethereum/ContractCall.h +++ b/src/Ethereum/ContractCall.h @@ -12,5 +12,5 @@ #include namespace TW::Ethereum::ABI { - std::optional decodeCall(const Data& call, const nlohmann::json& abi); + std::optional decodeCall(const Data& call, const std::string& abi); } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/EIP1967.cpp b/src/Ethereum/EIP1967.cpp index 55a6907d8f1..58106977ebe 100644 --- a/src/Ethereum/EIP1967.cpp +++ b/src/Ethereum/EIP1967.cpp @@ -5,11 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "EIP1014.h" -#include "AddressChecksum.h" +#include "ABI/Function.h" #include "Hash.h" #include "HexCoding.h" -#include -#include "ABI.h" namespace TW::Ethereum { @@ -18,13 +16,16 @@ static const std::string eip1967ProxyBytecodeHex = R"(0x608060405260405162000c51 Data getEIP1967ProxyInitCode(const std::string& logicAddress, const Data& data) { Data initCode = parse_hex(eip1967ProxyBytecodeHex); - auto proxyConstructorParams = ABI::ParamTuple(); - proxyConstructorParams.addParam(std::make_shared(parse_hex(logicAddress))); - proxyConstructorParams.addParam(std::make_shared(data)); - Data proxyConstructorParamsEncoded; - proxyConstructorParams.encode(proxyConstructorParamsEncoded); + auto proxyConstructorParamsEncoded = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(logicAddress), + std::make_shared(data), + }); - append(initCode, proxyConstructorParamsEncoded); + if (!proxyConstructorParamsEncoded.has_value()) { + return {}; + } + + append(initCode, proxyConstructorParamsEncoded.value()); return initCode; } diff --git a/src/Ethereum/ERC4337.cpp b/src/Ethereum/ERC4337.cpp deleted file mode 100644 index 6adad8f6739..00000000000 --- a/src/Ethereum/ERC4337.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ABI.h" -#include "AddressChecksum.h" -#include "EIP1014.h" -#include "EIP1967.h" -#include "Hash.h" -#include "HexCoding.h" -#include - -namespace TW::Ethereum { - -using ParamBasePtr = std::shared_ptr; -using ParamCollection = std::vector; - -// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol#L57 -Data getERC4337ExecuteBytecode(const Data& toAddress, const uint256_t& value, const Data& data) { - auto executeFunc = ABI::Function("execute", ParamCollection{ - std::make_shared(toAddress), - std::make_shared(value), - std::make_shared(data)}); - Data executeFuncEncoded; - executeFunc.encode(executeFuncEncoded); - return executeFuncEncoded; -} - -// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol#L65 -Data getERC4337ExecuteBatchBytecode(const std::vector& toAddresses, const std::vector& amounts, const std::vector& payloads) { - auto addressesParam = ABI::ParamArray(); - for (const auto& toAddress: toAddresses) { - addressesParam.addParam(std::make_shared(toAddress)); - } - auto valuesParam = ABI::ParamArray(); - for (const auto& amount: amounts) { - valuesParam.addParam(std::make_shared(amount)); - } - auto payloadsParam = ABI::ParamArray(); - for (const auto& payload: payloads) { - payloadsParam.addParam(std::make_shared(payload)); - } - auto executeFunc = ABI::Function("executeBatch", ParamCollection{ - std::make_shared(addressesParam), - std::make_shared(valuesParam), - std::make_shared(payloadsParam)}); - Data executeFuncEncoded; - executeFunc.encode(executeFuncEncoded); - return executeFuncEncoded; -} - - -} // namespace TW::Ethereum diff --git a/src/Ethereum/ERC4337.h b/src/Ethereum/ERC4337.h deleted file mode 100644 index cabf114c675..00000000000 --- a/src/Ethereum/ERC4337.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust 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 "uint256.h" - -namespace TW::Ethereum { - -Data getERC4337ExecuteBytecode(const Data& toAddress, const uint256_t& value, const Data& data); -Data getERC4337ExecuteBatchBytecode(const std::vector& toAddresses, const std::vector& values, const std::vector& payloads); - -} diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 901ec3fa8a5..78834004477 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -7,90 +7,31 @@ #include "Entry.h" #include "Address.h" -#include "proto/Common.pb.h" -#include "proto/TransactionCompiler.pb.h" -#include "Signer.h" +#include "HexCoding.h" +#include "proto/Ethereum.pb.h" -#include "proto/TransactionCompiler.pb.h" +#include namespace TW::Ethereum { -using namespace std; +// TODO call `signRustJSON` when it's done. +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address::isValid(address); -} - -string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { - // normalized with EIP55 checksum - return Address(address).string(); -} - -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address(publicKey).string(); -} + auto inputData = data(input.SerializeAsString()); + Data dataOut; + sign(coin, inputData, dataOut); -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { - const auto addr = Address(address); - return {addr.bytes.begin(), addr.bytes.end()}; -} - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} - -Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - const auto transaction = Signer::build(input); - const auto chainId = load(data(input.chain_id())); // retrieve chainId from input - auto preHash = transaction->preHash(chainId); - auto preImage = transaction->serialize(chainId); - output.set_data_hash(preHash.data(), preHash.size()); - output.set_data(preImage.data(), preImage.size()); - }); -} - -void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerTemplate( - txInputData, [&](const auto& input, auto& output) { - if (signatures.size() != 1) { - output.set_error(Common::Proto::Error_signatures_count); - output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); - return; - } - output = Signer::compile(input, signatures[0]); - }); -} - -Data Entry::buildTransactionInput([[maybe_unused]] TWCoinType coinType, [[maybe_unused]] const std::string& from, const std::string& to, const uint256_t& amount, [[maybe_unused]] const std::string& asset, [[maybe_unused]] const std::string& memo, const std::string& chainId) const { - Proto::SigningInput input; - - auto chainIdData = store(uint256_t(1)); - if (chainId.length() > 0) { - // parse amount - uint256_t chainIdUint256{chainId}; - chainIdData = store(chainIdUint256); - } - input.set_chain_id(chainIdData.data(), chainIdData.size()); - - if (!Address::isValid(to)) { - throw std::invalid_argument("Invalid to address"); + if(dataOut.empty()) { + return {}; } - input.set_to_address(to); - - auto& transfer = *input.mutable_transaction()->mutable_transfer(); - const auto amountData = store(amount); - transfer.set_amount(amountData.data(), amountData.size()); - // not set: nonce, gasPrice, gasLimit, tx_mode (need to be set afterwards) + Proto::SigningOutput output; + output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); - const auto txInputData = data(input.SerializeAsString()); - return txInputData; + return hex(output.encoded()); } } // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index e5d3d41c30d..f2a5203a0d2 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -6,25 +6,16 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Ethereum { /// Entry point for Ethereum and Ethereum-fork coins. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry : public CoinEntry { +class Entry : public Rust::RustCoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const final; - std::string normalizeAddress(TWCoinType coin, const std::string& address) const final; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const final; - Data addressToData(TWCoinType coin, const std::string& address) const final; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; - bool supportsJSONSigning() const final { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; - - Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; - Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const final; + bool supportsJSONSigning() const final { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; }; } // namespace TW::Ethereum diff --git a/src/Ethereum/MessageSigner.cpp b/src/Ethereum/MessageSigner.cpp index 4f31657921e..a24ee7a81c5 100644 --- a/src/Ethereum/MessageSigner.cpp +++ b/src/Ethereum/MessageSigner.cpp @@ -4,65 +4,117 @@ // 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 "MessageSigner.h" -#include -#include -#include +#include +#include +#include "rust/Wrapper.h" +#include "TrustWalletCore/TWCoinType.h" namespace TW::Ethereum { -Data MessageSigner::generateMessage(const std::string& message) { - std::string prefix(1, MessageSigner::EthereumPrefix); - std::stringstream ss; - ss << prefix << MessageSigner::MessagePrefix << std::to_string(message.size()) << message; - Data signableMessage = Hash::keccak256(data(ss.str())); - return signableMessage; +std::string signMessageRust(const PrivateKey& privateKey, const std::string& message, Proto::MessageType msgType, MessageSigner::MaybeChainId chainId) { + Proto::MessageSigningInput input; + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_message(message); + input.set_message_type(msgType); + + if (chainId.has_value()) { + input.mutable_chain_id()->set_chain_id(static_cast(chainId.value())); + } + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_message_signer_sign(inputData.get(), TWCoinTypeEthereum); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; + } + + Proto::MessageSigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != Common::Proto::SigningError::OK) { + return {}; + } + return output.signature(); +} + +Data messagePreImageHashRust(const std::string& message, Proto::MessageType msgType) { + Proto::MessageSigningInput input; + input.set_message(message); + input.set_message_type(msgType); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_message_signer_pre_image_hashes(inputData.get(), TWCoinTypeEthereum); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; + } + + TxCompiler::Proto::PreSigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != Common::Proto::SigningError::OK) { + return {}; + } + return data(output.data_hash()); +} + +bool verifyMessageRust(const PublicKey& publicKey, const std::string& message, const std::string& signature) { + Proto::MessageVerifyingInput input; + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_message(message); + input.set_signature(signature); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + return Rust::tw_message_signer_verify(inputData.get(), TWCoinTypeEthereum); } std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& message, MessageType msgType, MaybeChainId chainId) { - auto signableMessage = generateMessage(message); - return signHash(privateKey, signableMessage, msgType, chainId); + auto protoMsgType = Proto::MessageType::MessageType_legacy; + switch (msgType) { + case MessageType::Eip155: { + protoMsgType = Proto::MessageType::MessageType_eip155; + break; + } + case MessageType::ImmutableX: { + protoMsgType = Proto::MessageType::MessageType_immutable_x; + break; + } + default: { + break; + } + } + + return signMessageRust(privateKey, message, protoMsgType, chainId); } std::string MessageSigner::signTypedData(const PrivateKey& privateKey, const std::string& data, MessageType msgType, MessageSigner::MaybeChainId chainId) { - if (msgType == MessageType::Eip155 && nlohmann::json::accept(data)) { - auto json = nlohmann::json::parse(data); - if (json.contains("types") && json.at("types").contains("EIP712Domain")) { - const auto& chainIdValue = json.at("domain").at("chainId"); - std::size_t actualChainId{0}; - if (chainIdValue.is_string()) { - actualChainId = std::stoull(chainIdValue.get()); - } else { - actualChainId = chainIdValue.get(); - } - if (actualChainId != *chainId) { - return "EIP712 chainId is different than the current chainID."; - } + auto protoMsgType = Proto::MessageType::MessageType_typed; + switch (msgType) { + case MessageType::Eip155: { + protoMsgType = Proto::MessageType::MessageType_typed_eip155; + break; + } + default: { + break; } } - auto signableMessage = ABI::ParamStruct::hashStructJson(data); - return signHash(privateKey, signableMessage, msgType, chainId); + + return signMessageRust(privateKey, data, protoMsgType, chainId); } bool MessageSigner::verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept { - Data msg = generateMessage(message); - //! If it's json && EIP712Domain then we hash the struct - if (nlohmann::json::accept(message)) { - auto json = nlohmann::json::parse(message); - if (json.contains("types") && json.at("types").contains("EIP712Domain")) { - msg = ABI::ParamStruct::hashStructJson(message); - } - } - auto rawSignature = parse_hex(signature); - auto recovered = publicKey.recover(rawSignature, msg); - return recovered == publicKey && publicKey.verify(rawSignature, msg); + return verifyMessageRust(publicKey, message, signature); +} + +Data MessageSigner::messagePreImageHash(const std::string& message) noexcept { + return messagePreImageHashRust(message, Proto::MessageType::MessageType_legacy); } -std::string MessageSigner::signHash(const PrivateKey& privateKey, const Data& signableMessage, MessageType msgType, TW::Ethereum::MessageSigner::MaybeChainId chainId) { - auto data = privateKey.sign(signableMessage, TWCurveSECP256k1); - prepareSignature(data, msgType, chainId); - return hex(data); +Data MessageSigner::typedDataPreImageHash(const std::string& data) noexcept { + return messagePreImageHashRust(data, Proto::MessageType::MessageType_typed); } void MessageSigner::prepareSignature(Data& signature, MessageType msgType, TW::Ethereum::MessageSigner::MaybeChainId chainId) noexcept { diff --git a/src/Ethereum/MessageSigner.h b/src/Ethereum/MessageSigner.h index b6042d23bdd..5aeac0e05e4 100644 --- a/src/Ethereum/MessageSigner.h +++ b/src/Ethereum/MessageSigner.h @@ -43,10 +43,17 @@ class MessageSigner { /// \param signature signature to verify the message against /// \return true if the message match the signature, false otherwise static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept; - static constexpr auto MessagePrefix = "Ethereum Signed Message:\n"; - static constexpr std::uint8_t EthereumPrefix{0x19}; - static Data generateMessage(const std::string& message); - static std::string signHash(const PrivateKey& privateKey, const Data& signableMessage, MessageType msgType, TW::Ethereum::MessageSigner::MaybeChainId chainId); + + /// Computes a hash of the message following EIP-191. + /// \param message message to hash + /// \return hash of the tuped data. + static Data messagePreImageHash(const std::string& message) noexcept; + + /// Computes a hash of the typed data according to EIP-712 V4. + /// \param data json data + /// \return hash of the tuped data. + static Data typedDataPreImageHash(const std::string& data) noexcept; + static void prepareSignature(Data& signature, MessageType msgType, MaybeChainId chainId = std::nullopt) noexcept; }; diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index f8179cbaadf..486162a58de 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -6,196 +6,41 @@ #include "RLP.h" -#include "../BinaryCoding.h" -#include "../Numeric.h" +#include "BinaryCoding.h" +#include "TrustWalletCore/TWCoinType.h" +#include "rust/Wrapper.h" #include namespace TW::Ethereum { -Data RLP::encode(const uint256_t& value) noexcept { - using boost::multiprecision::cpp_int; +Data RLP::encode(const EthereumRlp::Proto::EncodingInput& input) { + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_rlp_encode(TWCoinTypeEthereum, inputData.get()); - Data bytes; - export_bits(value, std::back_inserter(bytes), 8); - - if (bytes.empty() || (bytes.size() == 1 && bytes[0] == 0)) { - return {0x80}; + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; } - return encode(bytes); -} + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); -Data RLP::encodeList(const Data& encoded) noexcept { - auto result = encodeHeader(encoded.size(), 0xc0, 0xf7); - result.reserve(result.size() + encoded.size()); - result.insert(result.end(), encoded.begin(), encoded.end()); - return result; + return data(output.encoded()); } -Data RLP::encode(const Data& data) noexcept { - if (data.size() == 1 && data[0] <= 0x7f) { - // Fits in single byte, no header - return data; - } - - auto encoded = encodeHeader(data.size(), 0x80, 0xb7); - encoded.insert(encoded.end(), data.begin(), data.end()); - return encoded; +Data RLP::encodeString(const std::string& s) { + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_string_item(s); + return encode(input); } -Data RLP::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept { - if (size < 56) { - return {static_cast(smallTag + size)}; - } +Data RLP::encodeU256(const uint256_t & num) { + auto numData = store(num); - const auto sizeData = putVarInt(size); - - auto header = Data(); - header.reserve(1 + sizeData.size()); - header.push_back(largeTag + static_cast(sizeData.size())); - header.insert(header.end(), sizeData.begin(), sizeData.end()); - return header; -} - -Data RLP::putVarInt(uint64_t i) noexcept { - Data bytes; // accumulate bytes here, in reverse order - do { - // take LSB byte, append - bytes.push_back(i & 0xff); - i = i >> 8; - } while (i); - assert(bytes.size() >= 1 && bytes.size() <= 8); - std::reverse(bytes.begin(), bytes.end()); - return bytes; -} - -uint64_t RLP::parseVarInt(size_t size, const Data& data, size_t index) { - if (size < 1 || size > 8) { - throw std::invalid_argument("invalid length length"); - } - if (data.size() - index < size) { - throw std::invalid_argument("Not enough data for varInt"); - } - if (size >= 2 && data[index] == 0) { - throw std::invalid_argument("multi-byte length must have no leading zero"); - } - uint64_t val = 0; - for (auto i = 0U; i < size; ++i) { - val = val << 8; - val += data[index + i]; - } - return static_cast(val); -} - -RLP::DecodedItem RLP::decodeList(const Data& input) { - RLP::DecodedItem item; - auto remainder = input; - while (true) { - auto listItem = RLP::decode(remainder); - item.decoded.push_back(listItem.decoded[0]); - if (listItem.remainder.size() == 0) { - break; - } else { - remainder = listItem.remainder; - } - } - return item; -} - -RLP::DecodedItem RLP::decode(const Data& input) { - if (input.size() == 0) { - throw std::invalid_argument("can't decode empty rlp data"); - } - RLP::DecodedItem item; - auto inputLen = input.size(); - auto prefix = input[0]; - if (prefix <= 0x7f) { - // 00--7f: a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. - item.decoded.emplace_back(Data{input[0]}); - item.remainder = subData(input, 1); - return item; - } - if (prefix <= 0xb7) { - // 80--b7: short string - // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string - // The range of the first byte is [0x80, 0xb7] - - // empty string - if (prefix == 0x80) { - item.decoded.emplace_back(); - item.remainder = subData(input, 1); - return item; - } - - auto strLen = prefix - 0x80; - if (strLen == 1 && input[1] <= 0x7f) { - throw std::invalid_argument("single byte below 128 must be encoded as itself"); - } - - if (inputLen < (1U + strLen)) { - throw std::invalid_argument(std::string("invalid short string, length ") + std::to_string(strLen)); - } - item.decoded.push_back(subData(input, 1, strLen)); - item.remainder = subData(input, 1 + strLen); - - return item; - } - if (prefix <= 0xbf) { - // b8--bf: long string - auto lenOfStrLen = size_t(prefix - 0xb7); - auto strLen = static_cast(parseVarInt(lenOfStrLen, input, 1)); - bool isStrLenInvalid = inputLen < lenOfStrLen - || checkAddUnsignedOverflow(1U + lenOfStrLen, strLen) - || inputLen < (1U + lenOfStrLen + strLen); - if (isStrLenInvalid) { - throw std::invalid_argument(std::string("Invalid rlp encoding length, length ") + std::to_string(strLen)); - } - auto data = subData(input, 1 + lenOfStrLen, strLen); - item.decoded.push_back(data); - item.remainder = subData(input, 1 + lenOfStrLen + strLen); - return item; - } - if (prefix <= 0xf7) { - // c0--f7: a list between 0-55 bytes long - auto listLen = size_t(prefix - 0xc0); - if (inputLen < (1 + listLen)) { - throw std::invalid_argument(std::string("Invalid rlp string length, length ") + std::to_string(listLen)); - } - // empty list - if (listLen == 0) { - item.remainder = subData(input, 1); - return item; - } - - // decode list - auto listItem = decodeList(subData(input, 1, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = subData(input, 1 + listLen); - return item; - } - // f8--ff - auto lenOfListLen = size_t(prefix - 0xf7); - auto listLen = static_cast(parseVarInt(lenOfListLen, input, 1)); - if (listLen < 56) { - throw std::invalid_argument("length below 56 must be encoded in one byte"); - } - auto isListLenInvalid = inputLen < lenOfListLen - || checkAddUnsignedOverflow(1U + lenOfListLen, listLen) - || inputLen < (1U + lenOfListLen + listLen); - if (isListLenInvalid) { - throw std::invalid_argument(std::string("Invalid rlp list length, length ") + std::to_string(listLen)); - } - - // decode list - auto listItem = decodeList(subData(input, 1 + lenOfListLen, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = subData(input, 1 + lenOfListLen + listLen); - return item; + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_number_u256(numData.data(), numData.size()); + return encode(input); } } // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index c0366c023b1..57c0ef18113 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.h @@ -6,9 +6,9 @@ #pragma once -#include "Transaction.h" #include "Data.h" -#include "../uint256.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" #include #include @@ -20,87 +20,11 @@ namespace TW::Ethereum { /// /// - SeeAlso: https://github.com/ethereum/wiki/wiki/RLP struct RLP { - /// Encodes a string; - static Data encode(const std::string& string) noexcept { - return encode(Data(string.begin(), string.end())); - } + static Data encode(const EthereumRlp::Proto::EncodingInput& input); - static Data encode(uint8_t number) noexcept { return encode(uint256_t(number)); } + static Data encodeString(const std::string& s); - static Data encode(uint16_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(int32_t number) noexcept { - if (number < 0) { - return {}; // RLP cannot encode negative numbers - } - return encode(static_cast(number)); - } - - static Data encode(uint32_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(int64_t number) noexcept { - if (number < 0) { - return {}; // RLP cannot encode negative numbers - } - return encode(static_cast(number)); - } - - static Data encode(uint64_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(const uint256_t& number) noexcept; - - /// Wraps encoded data as a list. - static Data encodeList(const Data& encoded) noexcept; - - /// Encodes a block of data. - static Data encode(const Data& data) noexcept; - - /// Encodes a static array. - template - static Data encode(const std::array& data) noexcept { - if (N == 1 && data[0] <= 0x7f) { - // Fits in single byte, no header - return Data(data.begin(), data.end()); - } - - auto encoded = encodeHeader(data.size(), 0x80, 0xb7); - encoded.insert(encoded.end(), data.begin(), data.end()); - return encoded; - } - - /// Encodes a list of elements. - template - static Data encodeList(T elements) noexcept { - auto encodedData = Data(); - for (const auto& el : elements) { - auto encoded = encode(el); - if (encoded.empty()) { - return {}; - } - encodedData.insert(encodedData.end(), encoded.begin(), encoded.end()); - } - - auto encoded = encodeHeader(encodedData.size(), 0xc0, 0xf7); - encoded.insert(encoded.end(), encodedData.begin(), encodedData.end()); - return encoded; - } - - /// Encodes a list header. - static Data encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept; - - struct DecodedItem { - std::vector decoded; - Data remainder; - }; - - static DecodedItem decodeList(const Data& input); - /// Decodes data, remainder from RLP encoded data - static DecodedItem decode(const Data& data); - - /// Returns the representation of an integer using the least number of bytes needed, between 1 and 8 bytes, big endian - static Data putVarInt(uint64_t i) noexcept; - /// Parses an integer of given size, between 1 and 8 bytes, big endian - static uint64_t parseVarInt(size_t size, const Data& data, size_t index); + static Data encodeU256(const uint256_t& num); }; } // namespace TW::Ethereum diff --git a/src/Ethereum/Signer.cpp b/src/Ethereum/Signer.cpp deleted file mode 100644 index 8684e149e85..00000000000 --- a/src/Ethereum/Signer.cpp +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "HexCoding.h" -#include "Coin.h" -#include -#include - -namespace TW::Ethereum { - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - try { - uint256_t chainID = load(input.chain_id()); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Signer::build(input); - - auto signature = sign(key, chainID, transaction); - - auto output = Proto::SigningOutput(); - - auto pre_hash = transaction->preHash(chainID); - output.set_pre_hash(pre_hash.data(), pre_hash.size()); - - auto encoded = transaction->encoded(signature, chainID); - output.set_encoded(encoded.data(), encoded.size()); - - auto v = store(signature.v, 1); - output.set_v(v.data(), v.size()); - auto r = store(signature.r, 32); - output.set_r(r.data(), r.size()); - auto s = store(signature.s, 32); - output.set_s(s.data(), s.size()); - - output.set_data(transaction->payload.data(), transaction->payload.size()); - - return output; - } catch (std::exception&) { - return Proto::SigningOutput(); - } -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); - return hex(output.encoded()); -} - -Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature) noexcept { - try { - uint256_t chainID = load(input.chain_id()); - auto transaction = Signer::build(input); - - // prepare Signature - const Signature sigStruct = signatureDataToStruct(signature, transaction->usesReplayProtection(), chainID); - - auto output = Proto::SigningOutput(); - - auto pre_hash = transaction->preHash(chainID); - output.set_pre_hash(pre_hash.data(), pre_hash.size()); - - auto encoded = transaction->encoded(sigStruct, chainID); - output.set_encoded(encoded.data(), encoded.size()); - - auto v = store(sigStruct.v, 1); - output.set_v(v.data(), v.size()); - auto r = store(sigStruct.r, 32); - output.set_r(r.data(), r.size()); - auto s = store(sigStruct.s, 32); - output.set_s(s.data(), s.size()); - - output.set_data(transaction->payload.data(), transaction->payload.size()); - return output; - } catch (std::exception&) { - return Proto::SigningOutput(); - } -} - -Signature Signer::signatureDataToStruct(const Data& signature, bool includeEip155, const uint256_t& chainID) noexcept { - if (!includeEip155) { - return signatureDataToStructSimple(signature); - } - return signatureDataToStructWithEip155(chainID, signature); -} - -Signature Signer::signatureDataToStructSimple(const Data& signature) noexcept { - boost::multiprecision::uint256_t r, s, v; - import_bits(r, signature.begin(), signature.begin() + 32); - import_bits(s, signature.begin() + 32, signature.begin() + 64); - import_bits(v, signature.begin() + 64, signature.begin() + 65); - return Signature{r, s, v}; -} - -Data Signer::simpleStructToSignatureData(const Signature& signature) noexcept { - Data fullSignature; - - auto r = store(signature.r, 32); - append(fullSignature, r); - auto s = store(signature.s, 32); - append(fullSignature, s); - auto v = store(signature.v, 1); - append(fullSignature, v); - - return fullSignature; -} - -Signature Signer::signatureDataToStructWithEip155(const uint256_t& chainID, const Data& signature) noexcept { - Signature rsv = signatureDataToStructSimple(signature); - // Embed chainID in V param, for replay protection, legacy (EIP155) - if (chainID != 0) { - rsv.v += 35 + chainID + chainID; - } else { - rsv.v += 27; - } - return rsv; -} - -Signature Signer::sign(const PrivateKey& privateKey, const Data& hash, bool includeEip155, const uint256_t& chainID) noexcept { - auto signature = privateKey.sign(hash, TWCurveSECP256k1); - return signatureDataToStruct(signature, includeEip155, chainID); -} - -// May throw -Data addressStringToData(const std::string& asString) { - if (asString.empty()) { - return {}; - } - // only ronin address prefix is not 0x - if (asString.compare(0, 2, "0x") != 0) { - return TW::addressToData(TWCoinTypeRonin, asString); - } - return TW::addressToData(TWCoinTypeEthereum, asString); -} - -std::shared_ptr Signer::build(const Proto::SigningInput& input) { - Data toAddress = addressStringToData(input.to_address()); - uint256_t nonce = load(input.nonce()); - uint256_t gasPrice = load(input.gas_price()); - uint256_t gasLimit = load(input.gas_limit()); - uint256_t maxInclusionFeePerGas = load(input.max_inclusion_fee_per_gas()); - uint256_t maxFeePerGas = load(input.max_fee_per_gas()); - - // EIP4337 - Data entryPointAddress = addressStringToData(input.user_operation().entry_point()); - Data senderAddress = addressStringToData(input.user_operation().sender()); - Data initCode = Data(input.user_operation().init_code().begin(), input.user_operation().init_code().end()); - uint256_t preVerificationGas = load(input.user_operation().pre_verification_gas()); - uint256_t verificationGasLimit = load(input.user_operation().verification_gas_limit()); - Data paymasterAndData = Data(input.user_operation().paymaster_and_data().begin(), input.user_operation().paymaster_and_data().end()); - - switch (input.transaction().transaction_oneof_case()) { - case Proto::Transaction::kTransfer: { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildNativeTransfer( - nonce, gasPrice, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildNativeTransfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - - case Proto::TransactionMode::UserOp: - return UserOperation::buildNativeTransfer( - entryPointAddress, - senderAddress, - toAddress, - load(input.transaction().transfer().amount()), - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode, - Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end()) - ); - } - } - - case Proto::Transaction::kErc20Transfer: { - Data tokenToAddress = addressStringToData(input.transaction().erc20_transfer().to()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC20Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC20Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - - case Proto::TransactionMode::UserOp: - return UserOperation::buildERC20Transfer( - entryPointAddress, - senderAddress, - toAddress, - tokenToAddress, - load(input.transaction().erc20_transfer().amount()), - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode); - } - } - - case Proto::Transaction::kErc20Approve: { - Data spenderAddress = addressStringToData(input.transaction().erc20_approve().spender()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC20Approve( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC20Approve( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - - case Proto::TransactionMode::UserOp: - return UserOperation::buildERC20Approve( - entryPointAddress, - senderAddress, - toAddress, - spenderAddress, - load(input.transaction().erc20_approve().amount()), - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode); - } - } - - case Proto::Transaction::kErc721Transfer: { - Data tokenToAddress = addressStringToData(input.transaction().erc721_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc721_transfer().from()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC721Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC721Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - - case Proto::TransactionMode::UserOp: - return UserOperation::buildERC721Transfer( - entryPointAddress, - senderAddress, - toAddress, - tokenFromAddress, - tokenToAddress, - load(input.transaction().erc721_transfer().token_id()), - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode); - } - } - - case Proto::Transaction::kErc1155Transfer: { - Data tokenToAddress = addressStringToData(input.transaction().erc1155_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc1155_transfer().from()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC1155Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), - /* value */ load(input.transaction().erc1155_transfer().value()), - /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC1155Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), - /* value */ load(input.transaction().erc1155_transfer().value()), - /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); - - case Proto::TransactionMode::UserOp: - return UserOperation::buildERC1155Transfer( - entryPointAddress, - senderAddress, - toAddress, - tokenFromAddress, - tokenToAddress, - load(input.transaction().erc1155_transfer().token_id()), - load(input.transaction().erc1155_transfer().value()), - Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end()), - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode); - } - } - - case Proto::Transaction::kBatch: { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return nullptr; - case Proto::TransactionMode::Enveloped: // Eip1559 - return nullptr; - case Proto::TransactionMode::UserOp: - std::vector addresses; - std::vector amounts; - std::vector payloads; - for (int i=0; i < input.transaction().batch().calls().size(); i++) { - addresses.push_back(addressStringToData(input.transaction().batch().calls()[i].address())); - amounts.push_back(load(input.transaction().batch().calls()[i].amount())); - payloads.push_back(Data(input.transaction().batch().calls()[i].payload().begin(), input.transaction().batch().calls()[i].payload().end())); - } - - return UserOperation::buildBatch( - entryPointAddress, - senderAddress, - addresses, - amounts, - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode, - payloads - ); - } - } - - case Proto::Transaction::kContractGeneric: - default: { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildNativeTransfer( - nonce, gasPrice, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildNativeTransfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - - case Proto::TransactionMode::UserOp: - return UserOperation::buildNativeTransfer( - entryPointAddress, - senderAddress, - toAddress, - load(input.transaction().contract_generic().amount()), - nonce, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - paymasterAndData, - initCode, - Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - } - } - } -} - -Signature Signer::sign(const PrivateKey& privateKey, const uint256_t& chainID, std::shared_ptr transaction) noexcept { - auto preHash = transaction->preHash(chainID); - return Signer::sign(privateKey, preHash, transaction->usesReplayProtection(), chainID); -} - -} // namespace TW::Ethereum diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h deleted file mode 100644 index 3a28793b2f9..00000000000 --- a/src/Ethereum/Signer.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "RLP.h" -#include "Transaction.h" -#include "Data.h" -#include "../Hash.h" -#include "../PrivateKey.h" -#include "../proto/Ethereum.pb.h" -#include "../uint256.h" - -#include -#include -#include -#include - -namespace TW::Ethereum { - -/// Helper class that performs Ethereum transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); - - public: - Signer() = delete; - - /// Signs the given transaction. - static Signature sign(const PrivateKey& privateKey, const uint256_t& chainID, std::shared_ptr transaction) noexcept; - /// Compiles a Proto::SigningInput transaction, with external signature - static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature) noexcept; - - /// build Transaction from signing input - static std::shared_ptr build(const Proto::SigningInput& input); - - /// Signs a hash with the given private key for the given chain identifier. - /// - /// \returns the r, s, and v values of the transaction signature - static Signature sign(const PrivateKey& privateKey, const Data& hash, bool includeEip155, const uint256_t& chainID) noexcept; - - /// Break up the signature into the R, S, and V values. - /// \returns the r, s, and v values of the transaction signature - static Signature signatureDataToStruct(const Data& signature, bool includeEip155, const uint256_t& chainID) noexcept; - - /// Break up the signature into the R, S, and V values, with no replay protection. - /// \returns the r, s, and v values of the transaction signature - static Signature signatureDataToStructSimple(const Data& signature) noexcept; - - /// Converts R, S, and V values into the full signature, with no replay protection. - /// \returns the full signature bytes - static Data simpleStructToSignatureData(const Signature& signature) noexcept; - - /// Break up the signature into the R, S, and V values, and include chainID in V for replay protection (Eip155) - /// \returns the r, s, and v values of the transaction signature - static Signature signatureDataToStructWithEip155(const uint256_t& chainID, const Data& signature) noexcept; -}; - -} // namespace TW::Ethereum diff --git a/src/Ethereum/Transaction.cpp b/src/Ethereum/Transaction.cpp deleted file mode 100644 index ec5a3badf1e..00000000000 --- a/src/Ethereum/Transaction.cpp +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Transaction.h" -#include "Ethereum/ABI.h" -#include "HexCoding.h" -#include "RLP.h" -#include "Signer.h" -#include -#include -#include - -namespace TW::Ethereum { - -using json = nlohmann::json; -using ParamBasePtr = std::shared_ptr; -using ParamCollection = std::vector; -using UserOperationPtr = std::shared_ptr; - -static const Data EmptyListEncoded = parse_hex("c0"); - -/// TransactionNonTyped -std::shared_ptr -TransactionNonTyped::buildNativeTransfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& toAddress, const uint256_t& amount, const Data& data) { - return std::make_shared(nonce, gasPrice, gasLimit, toAddress, amount, data); -} - -std::shared_ptr -TransactionNonTyped::buildERC20Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20TransferCall(toAddress, amount)); -} - -std::shared_ptr -TransactionNonTyped::buildERC20Approve(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20ApproveCall(spenderAddress, amount)); -} - -std::shared_ptr -TransactionNonTyped::buildERC721Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC721TransferFromCall(from, to, tokenId)); -} - -std::shared_ptr -TransactionNonTyped::buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC1155TransferFromCall(from, to, tokenId, value, data)); -} - -Data TransactionNonTyped::preHash(const uint256_t chainID) const { - return Hash::keccak256(serialize(chainID)); -} - -Data TransactionNonTyped::serialize(const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); - return RLP::encodeList(encoded); -} - -Data TransactionNonTyped::encoded(const Signature& signature, [[maybe_unused]] const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, RLP::encode(signature.v)); - append(encoded, RLP::encode(signature.r)); - append(encoded, RLP::encode(signature.s)); - return RLP::encodeList(encoded); -} - -Data TransactionNonTyped::buildERC20TransferCall(const Data& to, const uint256_t& amount) { - // clang-format off - auto func = ABI::Function("transfer", ParamCollection{ - std::make_shared(to), - std::make_shared(amount) - }); - // clang-format on - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionNonTyped::buildERC20ApproveCall(const Data& spender, const uint256_t& amount) { - // clang-format off - auto func = ABI::Function("approve", ParamCollection{ - std::make_shared(spender), - std::make_shared(amount) - }); - // clang-format on - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionNonTyped::buildERC721TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId) { - // clang-format off - auto func = ABI::Function("transferFrom", ParamCollection{ - std::make_shared(from), - std::make_shared(to), - std::make_shared(tokenId) - }); - // clang-format on - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionNonTyped::buildERC1155TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - // clang-format off - auto func = ABI::Function("safeTransferFrom", ParamCollection{ - std::make_shared(from), - std::make_shared(to), - std::make_shared(tokenId), - std::make_shared(value), - std::make_shared(data) - }); - // clang-format on - Data payload; - func.encode(payload); - return payload; -} - -/// TransactionEip1559 -Data TransactionEip1559::preHash(const uint256_t chainID) const { - return Hash::keccak256(serialize(chainID)); -} - -Data TransactionEip1559::serialize(const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(maxInclusionFeePerGas)); - append(encoded, RLP::encode(maxFeePerGas)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, EmptyListEncoded); // empty accessList - - Data envelope; - append(envelope, static_cast(type)); - append(envelope, RLP::encodeList(encoded)); - return envelope; -} - -Data TransactionEip1559::encoded(const Signature& signature, const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(maxInclusionFeePerGas)); - append(encoded, RLP::encode(maxFeePerGas)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, EmptyListEncoded); // empty accessList - append(encoded, RLP::encode(signature.v)); - append(encoded, RLP::encode(signature.r)); - append(encoded, RLP::encode(signature.s)); - - Data envelope; - append(envelope, static_cast(type)); - append(envelope, RLP::encodeList(encoded)); - return envelope; -} - -std::shared_ptr -TransactionEip1559::buildNativeTransfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& toAddress, const uint256_t& amount, const Data& data) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, toAddress, amount, data); -} - -std::shared_ptr -TransactionEip1559::buildERC20Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC20TransferCall(toAddress, amount)); -} - -std::shared_ptr -TransactionEip1559::buildERC20Approve(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC20ApproveCall(spenderAddress, amount)); -} - -std::shared_ptr -TransactionEip1559::buildERC721Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC721TransferFromCall(from, to, tokenId)); -} - -std::shared_ptr -TransactionEip1559::buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC1155TransferFromCall(from, to, tokenId, value, data)); -} - -/// UserOperation -Data UserOperation::preHash(const uint256_t chainID) const { - auto params = ABI::ParamTuple(ParamCollection{ - std::make_shared(32, Hash::keccak256(serialize(chainID))), - std::make_shared(entryPoint), - std::make_shared(chainID)}); - Data encoded; - params.encode(encoded); - return Hash::keccak256(encoded); -} - -Data UserOperation::serialize([[maybe_unused]] const uint256_t chainID) const { - auto params = ABI::ParamTuple(ParamCollection{ - std::make_shared(sender), - std::make_shared(nonce), - std::make_shared(32, Hash::keccak256(initCode)), - std::make_shared(32, Hash::keccak256(payload)), - std::make_shared(gasLimit), - std::make_shared(verificationGasLimit), - std::make_shared(preVerificationGas), - std::make_shared(maxFeePerGas), - std::make_shared(maxInclusionFeePerGas), - std::make_shared(32, Hash::keccak256(paymasterAndData))}); - Data serialized; - params.encode(serialized); - - return serialized; -} - -Data UserOperation::encoded(const Signature& signature, [[maybe_unused]] const uint256_t chainID) const { - Data rawSignature = Signer::simpleStructToSignatureData(signature); - rawSignature[64] += 27; - - const json tx = { - {"sender", hexEncoded(sender)}, - {"nonce", nonce.str()}, - {"initCode", hexEncoded(initCode)}, - {"callData", hexEncoded(payload)}, - {"callGasLimit", gasLimit.str()}, - {"verificationGasLimit", verificationGasLimit.str()}, - {"maxFeePerGas", maxFeePerGas.str()}, - {"maxPriorityFeePerGas", maxInclusionFeePerGas.str()}, - {"paymasterAndData", hexEncoded(paymasterAndData)}, - {"preVerificationGas", preVerificationGas.str()}, - {"signature", hexEncoded(rawSignature)}}; - const auto txString = tx.dump(); - return Data(txString.begin(), txString.end()); -} - -UserOperationPtr -UserOperation::buildNativeTransfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& toAddress, const uint256_t& amount, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData, const Data& initCode, const Data& payload) { - return std::make_shared( - entryPointAddress, - senderAddress, - nonce, - initCode, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - Ethereum::getERC4337ExecuteBytecode(toAddress, amount, payload), - paymasterAndData); -} - -UserOperationPtr -UserOperation::buildBatch(const Data& entryPointAddress, const Data& senderAddress, - const std::vector& toAddresses, const std::vector& amounts, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data paymasterAndData, const Data& initCode, const std::vector& payloads) { - return std::make_shared( - entryPointAddress, - senderAddress, - nonce, - initCode, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - Ethereum::getERC4337ExecuteBatchBytecode(toAddresses, amounts, payloads), - paymasterAndData); -} - -UserOperationPtr -UserOperation::buildERC20Transfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData, const Data& initCode) { - return std::make_shared( - entryPointAddress, - senderAddress, - nonce, - initCode, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - Ethereum::getERC4337ExecuteBytecode(tokenContract, 0, TransactionNonTyped::buildERC20TransferCall(toAddress, amount)), - paymasterAndData); -} - -UserOperationPtr -UserOperation::buildERC20Approve(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData, const Data& initCode) { - return std::make_shared( - entryPointAddress, - senderAddress, - nonce, - initCode, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - Ethereum::getERC4337ExecuteBytecode(tokenContract, 0, TransactionNonTyped::buildERC20ApproveCall(spenderAddress, amount)), - paymasterAndData); -} - -UserOperationPtr -UserOperation::buildERC721Transfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData, const Data& initCode) { - return std::make_shared( - entryPointAddress, - senderAddress, - nonce, - initCode, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - Ethereum::getERC4337ExecuteBytecode(tokenContract, 0, TransactionNonTyped::buildERC721TransferFromCall(from, to, tokenId)), - paymasterAndData); -} - -UserOperationPtr -UserOperation::buildERC1155Transfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData, const Data& initCode) { - return std::make_shared( - entryPointAddress, - senderAddress, - nonce, - initCode, - gasLimit, - verificationGasLimit, - maxFeePerGas, - maxInclusionFeePerGas, - preVerificationGas, - Ethereum::getERC4337ExecuteBytecode(tokenContract, 0, TransactionNonTyped::buildERC1155TransferFromCall(from, to, tokenId, value, data)), - paymasterAndData); -} - -} // namespace TW::Ethereum \ No newline at end of file diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h deleted file mode 100644 index b14e2efd44b..00000000000 --- a/src/Ethereum/Transaction.h +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Address.h" -#include "../uint256.h" - -#include - -namespace TW::Ethereum { - -// Transactions can be: -// - Non-typed (legacy, pre-EIP2718) transactions: -// -- simple ETH transfer -// -- others with payload, function call, e.g. ERC20 transfer -// - Typed transactions (enveloped, EIP2718), with specific type and transaction payload - -/// R-S-V Signature values -struct Signature { -public: - uint256_t r, s, v; -}; - -/// Base class for all transactions. -/// Non-typed and various typed transactions derive from this. -class TransactionBase { -public: - uint256_t nonce; - Data payload; - -public: - TransactionBase(const uint256_t& nonce, const Data& payload): nonce(nonce), payload(payload) {} - virtual ~TransactionBase() {} - // pre-sign hash of the tx, for signing - virtual Data preHash(const uint256_t chainID) const = 0; - // pre-sign image of tx - virtual Data serialize(const uint256_t chainID) const = 0; - // encoded tx (signed) - virtual Data encoded(const Signature& signature, const uint256_t chainID) const = 0; - // Signals wether this tx type uses Eip155-style replay protection in the signature - virtual bool usesReplayProtection() const = 0; - -protected: - TransactionBase() {} -}; - -/// Original transaction format, with no explicit type, legacy as pre-EIP2718 -class TransactionNonTyped: public TransactionBase { -public: - uint256_t gasPrice; - uint256_t gasLimit; - // Public key hash (Address.bytes) - Data to; - uint256_t amount; - - // Factory methods - // Create a native transfer transaction - static std::shared_ptr buildNativeTransfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& toAddress, const uint256_t& amount, const Data& data = {}); - - // Create an ERC20 token transfer transaction - static std::shared_ptr buildERC20Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount); - - // Create an ERC20 approve transaction - static std::shared_ptr buildERC20Approve(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount); - - // Create an ERC721 NFT transfer transaction - static std::shared_ptr buildERC721Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId); - - // Create an ERC1155 NFT transfer transaction - static std::shared_ptr buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); - - // Helpers for building contract calls - static Data buildERC20TransferCall(const Data& to, const uint256_t& amount); - static Data buildERC20ApproveCall(const Data& spender, const uint256_t& amount); - static Data buildERC721TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId); - static Data buildERC1155TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); - - virtual Data preHash(const uint256_t chainID) const; - virtual Data serialize(const uint256_t chainID) const; - virtual Data encoded(const Signature& signature, const uint256_t chainID) const; - virtual bool usesReplayProtection() const { return true; } - -public: - TransactionNonTyped(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& to, const uint256_t& amount, const Data& payload = {}) - : TransactionBase(nonce, payload) - , gasPrice(std::move(gasPrice)) - , gasLimit(std::move(gasLimit)) - , to(std::move(to)) - , amount(std::move(amount)) {} -}; - -enum TransactionType: uint8_t { - TxType_OptionalAccessList = 0x01, - TxType_Eip1559 = 0x02, - TxType_Eip4337 = 0x03, -}; - -/// Base class for various typed transactions. -class TransactionTyped: public TransactionBase { -public: - // transaction type - TransactionType type; - - TransactionTyped(TransactionType type, const uint256_t& nonce, const Data& payload) - : TransactionBase(nonce, payload), type(type) {} - virtual bool usesReplayProtection() const { return false; } -}; - -/// EIP1559 transaction -class TransactionEip1559: public TransactionTyped { -public: - uint256_t maxInclusionFeePerGas; - uint256_t maxFeePerGas; - uint256_t gasLimit; - // Public key hash (Address.bytes) - Data to; - uint256_t amount; - - // Factory methods - // Create a native transfer transaction - static std::shared_ptr buildNativeTransfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& toAddress, const uint256_t& amount, const Data& data = {}); - // Create an ERC20 token transfer transaction - static std::shared_ptr buildERC20Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount); - // Create an ERC20 approve transaction - static std::shared_ptr buildERC20Approve(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount); - // Create an ERC721 NFT transfer transaction - static std::shared_ptr buildERC721Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId); - // Create an ERC1155 NFT transfer transaction - static std::shared_ptr buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); - - virtual Data preHash(const uint256_t chainID) const; - virtual Data serialize(const uint256_t chainID) const; - virtual Data encoded(const Signature& signature, const uint256_t chainID) const; - -public: - TransactionEip1559(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasLimit, - const Data& to, const uint256_t& amount, const Data& payload = {}) - : TransactionTyped(TxType_Eip1559, nonce, payload) - , maxInclusionFeePerGas(std::move(maxInclusionFeePerGas)) - , maxFeePerGas(std::move(maxFeePerGas)) - , gasLimit(std::move(gasLimit)) - , to(std::move(to)) - , amount(std::move(amount)) {} -}; - -/// EIP4337 UserOperation -// https://github.com/ethereum/EIPs/blob/3fd65b1a782912bfc18cb975c62c55f733c7c96e/EIPS/eip-4337.md#specification -class UserOperation: public TransactionTyped { - using UserOperationPtr = std::shared_ptr; -public: - Data entryPoint; - Data sender; - Data initCode; - uint256_t gasLimit; - uint256_t verificationGasLimit; - uint256_t maxFeePerGas; - uint256_t maxInclusionFeePerGas; - uint256_t preVerificationGas; - Data paymasterAndData; - - // Factory methods - // Create a native transfer transaction - static UserOperationPtr buildNativeTransfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& toAddress, const uint256_t& amount, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData = {}, const Data& initCode = {}, const Data& payload = {}); - - // Create a batched transaction for ERC-4337 wallets - static UserOperationPtr buildBatch(const Data& entryPointAddress, const Data& senderAddress, - const std::vector& toAddresses, const std::vector& amounts, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data paymasterAndData = {}, const Data& initCode = {}, const std::vector& payloads = {}); - - // Create an ERC20 token transfer transaction - static UserOperationPtr buildERC20Transfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData = {}, const Data& initCode = {}); - // Create an ERC20 approve transaction - static UserOperationPtr buildERC20Approve(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData = {}, const Data& initCode = {}); - // Create an ERC721 NFT transfer transaction - static UserOperationPtr buildERC721Transfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData = {}, const Data& initCode = {}); - // Create an ERC1155 NFT transfer transaction - static UserOperationPtr buildERC1155Transfer(const Data& entryPointAddress, const Data& senderAddress, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data, const uint256_t& nonce, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& paymasterAndData = {}, const Data& initCode = {}); - - virtual Data preHash(const uint256_t chainID) const; - virtual Data serialize(const uint256_t chainID) const; - virtual Data encoded(const Signature& signature, const uint256_t chainID) const; - -public: - UserOperation(const Data& entryPoint, const Data& sender, const uint256_t& nonce, const Data& initCode, - const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas, - const Data& payload = {}, const Data& paymasterAndData = {}) - : TransactionTyped(TxType_Eip4337, nonce, payload) - , entryPoint(std::move(entryPoint)) - , sender(std::move(sender)) - , initCode(std::move(initCode)) - , gasLimit(std::move(gasLimit)) - , verificationGasLimit(std::move(verificationGasLimit)) - , maxFeePerGas(std::move(maxFeePerGas)) - , maxInclusionFeePerGas(std::move(maxInclusionFeePerGas)) - , preVerificationGas(std::move(preVerificationGas)) - , paymasterAndData(std::move(paymasterAndData)) {} -}; - -} // namespace TW::Ethereum diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index f2f19608ade..c2f2ceafa3e 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -7,11 +7,21 @@ #include #include "AddressConverter.h" -#include "Ethereum/Transaction.h" +#include "Ethereum/Entry.h" +#include "Result.h" #include "Signer.h" +#include "proto/Ethereum.pb.h" +#include "proto/TransactionCompiler.pb.h" namespace TW::Filecoin { +Proto::SigningOutput signingOutputError(Common::Proto::SigningError error) { + Proto::SigningOutput outputErr; + outputErr.set_error(error); + outputErr.set_error_message(Common::Proto::SigningError_Name(error)); + return outputErr; +} + // ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. // As per https://github.com/ethereum-lists/chains static constexpr uint256_t FILECOIN_EIP155_CHAIN_ID = 314; @@ -132,17 +142,38 @@ Proto::SigningOutput Signer::signDelegated(const Proto::SigningInput& input) { } Data toBytes(toEth->bytes.begin(), toEth->bytes.end()); + Ethereum::Proto::SigningInput ethInput; + + auto chainId = store(FILECOIN_EIP155_CHAIN_ID); + auto nonce = store(uint256_t(input.nonce())); + auto gasLimit = store(uint256_t(input.gas_limit())); + + ethInput.set_chain_id(chainId.data(), chainId.size()); + ethInput.set_nonce(nonce.data(), nonce.size()); + ethInput.set_tx_mode(Ethereum::Proto::Enveloped); + ethInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + ethInput.set_max_inclusion_fee_per_gas(input.gas_premium()); + ethInput.set_max_fee_per_gas(input.gas_fee_cap()); + ethInput.set_to_address(toEth->string()); + + auto* transfer = ethInput.mutable_transaction()->mutable_transfer(); + transfer->set_amount(input.value()); + transfer->set_data(params.data(), params.size()); + + // Get an Ethereum EIP1559 native transfer preHash to sign. + auto ethOutputData = Ethereum::Entry().preImageHashes(TWCoinTypeEthereum, data(ethInput.SerializeAsString())); + if (ethOutputData.empty()) { + return signingOutputError(Common::Proto::SigningError::Error_internal); + } + + TxCompiler::Proto::PreSigningOutput ethOutput; + ethOutput.ParseFromArray(ethOutputData.data(), static_cast(ethOutputData.size())); + if (ethOutput.error() != Common::Proto::SigningError::OK) { + return signingOutputError(ethOutput.error()); + } + + auto preHash = data(ethOutput.data_hash()); // Sign transaction as an Ethereum EIP1559 native transfer. - auto ethTransaction = Ethereum::TransactionEip1559::buildNativeTransfer( - /* nonce */ input.nonce(), - /* maxInclusionFeePerGas */ load(input.gas_premium()), - /* maxFeePerGas */ load(input.gas_fee_cap()), - /* gasLimit */ input.gas_limit(), - /* toAddress */ toBytes, - /* amount */ load(input.value()), - /* data */ params - ); - Data preHash = ethTransaction->preHash(FILECOIN_EIP155_CHAIN_ID); Data signature = privateKey.sign(preHash, TWCurveSECP256k1); // Generate a Filecoin signed message. diff --git a/src/Greenfield/ProtobufSerialization.cpp b/src/Greenfield/ProtobufSerialization.cpp index 82da978ccdf..45fdd59647f 100644 --- a/src/Greenfield/ProtobufSerialization.cpp +++ b/src/Greenfield/ProtobufSerialization.cpp @@ -88,7 +88,7 @@ SigningResult ProtobufSerialization::encodeTxProtobuf(const Proto::Signing } const auto serializedTxBody = txBodyResult.payload(); - auto txRaw = cosmos::TxRaw(); + auto txRaw = cosmos::tx::v1beta1::TxRaw(); txRaw.set_body_bytes(serializedTxBody.data(), serializedTxBody.size()); txRaw.set_auth_info_bytes(serializedAuthInfo.data(), serializedAuthInfo.size()); *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); @@ -100,7 +100,7 @@ SigningResult ProtobufSerialization::encodeTxProtobuf(const Proto::Signing } SigningResult ProtobufSerialization::encodeTxBody(const Proto::SigningInput& input) { - cosmos::TxBody txBody; + cosmos::tx::v1beta1::TxBody txBody; // At this moment, we support only one message. if (input.messages_size() != 1) { @@ -120,7 +120,7 @@ SigningResult ProtobufSerialization::encodeTxBody(const Proto::SigningInpu Data ProtobufSerialization::encodeAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey) { // AuthInfo - auto authInfo = cosmos::AuthInfo(); + auto authInfo = cosmos::tx::v1beta1::AuthInfo(); auto* signerInfo = authInfo.add_signer_infos(); // At this moment, we support Eip712 signing mode only. diff --git a/src/Greenfield/SignerEip712.cpp b/src/Greenfield/SignerEip712.cpp index e4a5e91c2c2..ce803cda6f6 100644 --- a/src/Greenfield/SignerEip712.cpp +++ b/src/Greenfield/SignerEip712.cpp @@ -8,7 +8,6 @@ #include "Constants.h" #include "Ethereum/MessageSigner.h" -#include "Ethereum/ABI/ParamStruct.h" #include "HexCoding.h" #include @@ -243,7 +242,7 @@ SigningResult SignerEip712::preImageHash(const Proto::SigningInp } const auto txTypedData = txTypedDataResult.payload(); - const auto txTypedDataHash = Ethereum::ABI::ParamStruct::hashStructJson(txTypedData.dump()); + const auto txTypedDataHash = Ethereum::MessageSigner::typedDataPreImageHash(txTypedData.dump()); return SigningResult::success({.typedData = txTypedData, .typedDataHash = txTypedDataHash}); } diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 74d6e4585bd..b4674f56dcf 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -99,9 +99,9 @@ HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) template HDWallet::~HDWallet() { - std::fill(seed.begin(), seed.end(), 0); - std::fill(mnemonic.begin(), mnemonic.end(), 0); - std::fill(passphrase.begin(), passphrase.end(), 0); + memzero(seed.data(), seed.size()); + memzero(mnemonic.data(), mnemonic.size()); + memzero(passphrase.data(), passphrase.size()); } template diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 6fc570d235e..9b262426187 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -12,6 +12,7 @@ namespace TW::Harmony { using INVALID_ENUM = std::integral_constant; +using RLP = TW::Ethereum::RLP; std::tuple Signer::values(const uint256_t& chainID, const Data& signature) noexcept { @@ -307,159 +308,224 @@ void Signer::sign(const PrivateKey& privateKey, const Data& hash, T& transaction } Data Signer::rlpNoHash(const Transaction& transaction, const bool include_vrs) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); - append(encoded, RLP::encode(transaction.fromShardID)); - append(encoded, RLP::encode(transaction.toShardID)); - append(encoded, RLP::encode(transaction.to.getKeyHash())); - append(encoded, RLP::encode(transaction.amount)); - append(encoded, RLP::encode(transaction.payload)); + auto nonce = store(transaction.nonce); + auto gasPrice = store(transaction.gasPrice); + auto gasLimit = store(transaction.gasLimit); + auto fromShardID = store(transaction.fromShardID); + auto toShardID = store(transaction.toShardID); + auto toKeyHash = transaction.to.getKeyHash(); + auto amount = store(transaction.amount); + + Data v; + Data r; + Data s; if (include_vrs) { - append(encoded, RLP::encode(transaction.v)); - append(encoded, RLP::encode(transaction.r)); - append(encoded, RLP::encode(transaction.s)); + v = store(transaction.v); + r = store(transaction.r); + s = store(transaction.s); } else { - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); + v = store(chainID); + r = store(0); + s = store(0); } - return RLP::encodeList(encoded); -} -template -Data Signer::rlpNoHash(const Staking& transaction, const bool include_vrs) const - noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u256(nonce.data(), nonce.size()); + rlpList->add_items()->set_number_u256(gasPrice.data(), gasPrice.size()); + rlpList->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + rlpList->add_items()->set_number_u256(fromShardID.data(), fromShardID.size()); + rlpList->add_items()->set_number_u256(toShardID.data(), toShardID.size()); + rlpList->add_items()->set_data(toKeyHash.data(), toKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + rlpList->add_items()->set_data(transaction.payload.data(), transaction.payload.size()); + + rlpList->add_items()->set_number_u256(v.data(), v.size()); + rlpList->add_items()->set_number_u256(r.data(), r.size()); + rlpList->add_items()->set_number_u256(s.data(), s.size()); - append(encoded, RLP::encode(transaction.directive)); - append(encoded, rlpNoHashDirective(transaction)); + return RLP::encode(input); +} - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); +template +Data Signer::rlpNoHash(const Staking& transaction, const bool include_vrs) const noexcept { + Data v; + Data r; + Data s; if (include_vrs) { - append(encoded, RLP::encode(transaction.v)); - append(encoded, RLP::encode(transaction.r)); - append(encoded, RLP::encode(transaction.s)); + v = store(transaction.v); + r = store(transaction.r); + s = store(transaction.s); } else { - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); + v = store(chainID); + r = store(0); + s = store(0); } - return RLP::encodeList(encoded); + + auto nonce = store(transaction.nonce); + auto gasPrice = store(transaction.gasPrice); + auto gasLimit = store(transaction.gasLimit); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(transaction.directive); + *rlpList->add_items() = rlpNoHashDirective(transaction); + + rlpList->add_items()->set_number_u256(nonce.data(), nonce.size()); + rlpList->add_items()->set_number_u256(gasPrice.data(), gasPrice.size()); + rlpList->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + + rlpList->add_items()->set_number_u256(v.data(), v.size()); + rlpList->add_items()->set_number_u256(r.data(), r.size()); + rlpList->add_items()->set_number_u256(s.data(), s.size()); + + return RLP::encode(input); } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; +template +EthereumRlp::Proto::RlpItem Signer::rlpPrepareDescription(const Staking& transaction) const noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.name); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.identity); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.website); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.securityContact); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.details); - auto descriptionEncoded = Data(); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.name)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.identity)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.website)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.securityContact)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.details)); - append(encoded, RLP::encodeList(descriptionEncoded)); + return item; +} - auto commissionEncoded = Data(); +EthereumRlp::Proto::RlpItem Signer::rlpPrepareCommissionRates(const Staking &transaction) noexcept { + auto rateValue = store(transaction.stakeMsg.commissionRates.rate.value); + auto maxRateValue = store(transaction.stakeMsg.commissionRates.maxRate.value); + auto maxChangeRateValue = store(transaction.stakeMsg.commissionRates.maxChangeRate.value); - auto rateEncoded = Data(); - append(rateEncoded, RLP::encode(transaction.stakeMsg.commissionRates.rate.value)); - append(commissionEncoded, RLP::encodeList(rateEncoded)); + EthereumRlp::Proto::RlpItem item; + auto* commissionList = item.mutable_list(); - auto maxRateEncoded = Data(); - append(maxRateEncoded, RLP::encode(transaction.stakeMsg.commissionRates.maxRate.value)); - append(commissionEncoded, RLP::encodeList(maxRateEncoded)); + // Append `commission::rate` properties list with a single item. + auto* rateList = commissionList->add_items()->mutable_list(); + rateList->add_items()->set_number_u256(rateValue.data(), rateValue.size()); - auto maxChangeRateEncoded = Data(); - append(maxChangeRateEncoded, - RLP::encode(transaction.stakeMsg.commissionRates.maxChangeRate.value)); - append(commissionEncoded, RLP::encodeList(maxChangeRateEncoded)); + // Append `commission::maxRate` properties list. + auto* maxRateList = commissionList->add_items()->mutable_list(); + maxRateList->add_items()->set_number_u256(maxRateValue.data(), maxRateValue.size()); - append(encoded, RLP::encodeList(commissionEncoded)); + // Append `commission::maxChangeRate` properties list. + auto* maxChangeRateList = commissionList->add_items()->mutable_list(); + maxChangeRateList->add_items()->set_number_u256(maxChangeRateValue.data(), maxChangeRateValue.size()); - append(encoded, RLP::encode(transaction.stakeMsg.minSelfDelegation)); - append(encoded, RLP::encode(transaction.stakeMsg.maxTotalDelegation)); + return item; +} - auto slotPubKeysEncoded = Data(); - for (auto pk : transaction.stakeMsg.slotPubKeys) { - append(slotPubKeysEncoded, RLP::encode(pk)); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto minSelfDelegation = store(transaction.stakeMsg.minSelfDelegation); + auto maxTotalDelegation = store(transaction.stakeMsg.maxTotalDelegation); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + *rlpList->add_items() = rlpPrepareDescription(transaction); + + // Append `commission` properties list of `rate`, `maxRate`, `maxChangeRate` sublists. + *rlpList->add_items() = rlpPrepareCommissionRates(transaction); + + rlpList->add_items()->set_number_u256(minSelfDelegation.data(), minSelfDelegation.size()); + rlpList->add_items()->set_number_u256(maxTotalDelegation.data(), maxTotalDelegation.size()); + + // Append a list of slot public keys. + auto* slotPubkeysList = rlpList->add_items()->mutable_list(); + for (const auto& pk : transaction.stakeMsg.slotPubKeys) { + slotPubkeysList->add_items()->set_data(pk.data(), pk.size()); } - append(encoded, RLP::encodeList(slotPubKeysEncoded)); - auto slotBlsSigsEncoded = Data(); - for (auto sig : transaction.stakeMsg.slotKeySigs) { - append(slotBlsSigsEncoded, RLP::encode(sig)); + // Append a list of slot key signatures. + auto* slotKeySigsList = rlpList->add_items()->mutable_list(); + for (const auto& sign : transaction.stakeMsg.slotKeySigs) { + slotKeySigsList->add_items()->set_data(sign.data(), sign.size()); } - append(encoded, RLP::encodeList(slotBlsSigsEncoded)); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); - return RLP::encodeList(encoded); + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto minSelfDelegation = store(transaction.stakeMsg.minSelfDelegation); + auto maxTotalDelegation = store(transaction.stakeMsg.maxTotalDelegation); + auto active = store(transaction.stakeMsg.active); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); - auto descriptionEncoded = Data(); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.name)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.identity)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.website)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.securityContact)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.details)); - append(encoded, RLP::encodeList(descriptionEncoded)); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + *rlpList->add_items() = rlpPrepareDescription(transaction); - auto decEncoded = Data(); + auto* commissionRateList = rlpList->add_items()->mutable_list(); if (transaction.stakeMsg.commissionRate.has_value()) { // Note: std::optional.value() is not available in XCode with target < iOS 12; using '*' - append(decEncoded, RLP::encode((*transaction.stakeMsg.commissionRate).value)); + auto commissionRateValue = store((*transaction.stakeMsg.commissionRate).value); + commissionRateList->add_items()->set_number_u256(commissionRateValue.data(), commissionRateValue.size()); } - append(encoded, RLP::encodeList(decEncoded)); - append(encoded, RLP::encode(transaction.stakeMsg.minSelfDelegation)); - append(encoded, RLP::encode(transaction.stakeMsg.maxTotalDelegation)); + rlpList->add_items()->set_number_u256(minSelfDelegation.data(), minSelfDelegation.size()); + rlpList->add_items()->set_number_u256(maxTotalDelegation.data(), maxTotalDelegation.size()); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToRemove)); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToAdd)); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToAddSig)); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToRemove.data(), transaction.stakeMsg.slotKeyToRemove.size()); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToAdd.data(), transaction.stakeMsg.slotKeyToAdd.size()); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToAddSig.data(), transaction.stakeMsg.slotKeyToAddSig.size()); - append(encoded, RLP::encode(transaction.stakeMsg.active)); + rlpList->add_items()->set_number_u256(active.data(), active.size()); - return RLP::encodeList(encoded); + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + + return item; } std::string Signer::txnAsRLPHex(Transaction& transaction) const noexcept { diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index 71a45b162b1..9a24142279e 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -12,6 +12,7 @@ #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Harmony.pb.h" +#include "../proto/EthereumRlp.pb.h" #include #include @@ -96,11 +97,16 @@ class Signer { template Data rlpNoHash(const Staking &transaction, const bool) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + + template + EthereumRlp::Proto::RlpItem rlpPrepareDescription(const Staking& transaction) const noexcept; + + static EthereumRlp::Proto::RlpItem rlpPrepareCommissionRates(const Staking &transaction) noexcept; }; } // namespace TW::Harmony diff --git a/src/Hash.cpp b/src/Hash.cpp index 9556cca78f2..389854338ec 100644 --- a/src/Hash.cpp +++ b/src/Hash.cpp @@ -94,16 +94,27 @@ Data Hash::blake256(const byte* data, size_t size) { } Data Hash::blake2b(const byte* data, size_t dataSize) { - return Rust::CByteArrayWrapper(Rust::blake2_b(data, dataSize, 32)).data; + Rust::CByteArrayResultWrapper res = Rust::blake2_b(data, dataSize, 32); + if (res.isErr()) { + throw std::runtime_error("Error 'blake2_b' hashing"); + } + return res.unwrap().data; } Data Hash::blake2b(const byte* data, size_t dataSize, size_t hashSize) { - return Rust::CByteArrayWrapper(Rust::blake2_b(data, dataSize, hashSize)).data; + Rust::CByteArrayResultWrapper res = Rust::blake2_b(data, dataSize, hashSize); + if (res.isErr()) { + throw std::runtime_error("Error 'blake2_b' hashing"); + } + return res.unwrap().data; } Data Hash::blake2b(const byte* data, size_t dataSize, size_t hashSize, const Data& personal) { - Rust::CByteArrayWrapper res = Rust::blake2_b_personal(data, dataSize, hashSize, personal.data(), personal.size()); - return res.data; + Rust::CByteArrayResultWrapper res = Rust::blake2_b_personal(data, dataSize, hashSize, personal.data(), personal.size()); + if (res.isErr()) { + throw std::runtime_error("Error 'blake2_b_personal' hashing"); + } + return res.unwrap().data; } Data Hash::groestl512(const byte* data, size_t size) { diff --git a/src/ImmutableX/StarkKey.cpp b/src/ImmutableX/StarkKey.cpp index 2f2604ccadd..f550b7ab14d 100644 --- a/src/ImmutableX/StarkKey.cpp +++ b/src/ImmutableX/StarkKey.cpp @@ -4,8 +4,6 @@ // 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 @@ -47,44 +45,9 @@ PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey) { PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath) { using namespace internal; - //auto data = parse_hex(signature); - auto ethSignature = Ethereum::Signer::signatureDataToStructSimple(signature); - auto seed = store(ethSignature.s); + // The signature is `rsv`, where `s` starts at 32 and is 32 long. + auto seed = subData(signature, 32, 32); return getPrivateKeyFromSeed(seed, derivationPath); } -Data getPublicKeyFromPrivateKey(const Data& privateKey) { - auto pubKey = Rust::starknet_pubkey_from_private(hex(privateKey).c_str()); - if (pubKey.code != Rust::OK_CODE) { - return {}; - } - const auto toReturn = parse_hex(pubKey.result, true); - Rust::free_string(pubKey.result); - return toReturn; -} - -Data sign(const Data& privateKey, const Data& digest) { - auto privKeyStr = hex(privateKey); - auto hexDigest = hex(digest); - auto resultSignature = Rust::starknet_sign(privKeyStr.c_str(), hexDigest.c_str()); - if (resultSignature.code != Rust::OK_CODE) { - return {}; - } - auto toReturn = parse_hex(resultSignature.result); - Rust::free_string(resultSignature.result); - return toReturn; -} - -bool verify(const Data& pubKey, const Data& signature, const Data& digest) { - if (signature.size() != 64) { - return false; - } - auto r = hex(subData(signature, 0, 32)); - auto s = hex(subData(signature, 32)); - auto pubKeyStr = hex(pubKey); - auto digestStr = hex(digest); - const auto res = Rust::starknet_verify(pubKeyStr.c_str(), digestStr.c_str(), r.c_str(), s.c_str()); - return res.code == Rust::OK_CODE && res.result; -} - } // namespace TW::ImmutableX diff --git a/src/ImmutableX/StarkKey.h b/src/ImmutableX/StarkKey.h index 82e3c4eae49..5c2643a56da 100644 --- a/src/ImmutableX/StarkKey.h +++ b/src/ImmutableX/StarkKey.h @@ -23,10 +23,4 @@ PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey); PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath); -Data getPublicKeyFromPrivateKey(const Data& privateKey); - -Data sign(const Data &privateKey, const Data& digest); - -bool verify(const Data &pubKey, const Data& signature, const Data& digest); - } // namespace TW::ImmutableX diff --git a/src/InternetComputer/Entry.h b/src/InternetComputer/Entry.h new file mode 100644 index 00000000000..0d9081d60d1 --- /dev/null +++ b/src/InternetComputer/Entry.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "rust/RustCoinEntry.h" + +namespace TW::InternetComputer { + +/// Entry point for implementation of InternetComputer coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Rust::RustCoinEntry { +}; + +} // namespace TW::InternetComputer diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 3e35bc7aa9b..a3fca3bb91f 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -32,7 +33,9 @@ StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& pas } Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); + StoredKey key(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); + memzero(mnemonicData.data(), mnemonic.size()); + return key; } StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) { @@ -40,7 +43,9 @@ StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Dat const auto& mnemonic = wallet.getMnemonic(); assert(Mnemonic::isValid(mnemonic)); Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); + StoredKey key(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); + memzero(mnemonicData.data(), mnemonic.size()); + return key; } StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin, TWStoredKeyEncryption encryption) { diff --git a/src/LiquidStaking/LiquidStaking.cpp b/src/LiquidStaking/LiquidStaking.cpp index a50c26d725e..b635e5c268c 100644 --- a/src/LiquidStaking/LiquidStaking.cpp +++ b/src/LiquidStaking/LiquidStaking.cpp @@ -15,8 +15,6 @@ // ETH #include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamAddress.h" -#include "Ethereum/ABI/ParamBase.h" #include "Ethereum/Address.h" #include "proto/Ethereum.pb.h" #include "uint256.h" @@ -35,7 +33,6 @@ struct PairHash { using EVMLiquidStakingFunctionRegistry = std::unordered_map; using EVMLiquidStakingParamsRegistry = std::unordered_map; using EVMLiquidStakingRegistry = std::unordered_map; -using Params = std::vector>; static const EVMLiquidStakingFunctionRegistry gStraderFunctionRegistry = {{std::make_pair(Proto::POLYGON, Action::Stake), "swapMaticForMaticXViaInstantPool"}, @@ -62,29 +59,35 @@ namespace internal { } void handleStake(const Proto::Stake& stake, const Proto::Blockchain& blockchain, Data& payload, uint256_t& amount, const Proto::Protocol protocol) { - Params params; + Ethereum::ABI::BaseParams params; if (protocol == Proto::Lido) { - params.emplace_back(std::make_shared()); + params.emplace_back(std::make_shared()); + } + auto funcData = Ethereum::ABI::Function::encodeFunctionCall(gEVMLiquidStakingRegistry.at(protocol).at({blockchain, Action::Stake}), params); + if (funcData.has_value()) { + payload = funcData.value(); } - auto func = Ethereum::ABI::Function(gEVMLiquidStakingRegistry.at(protocol).at({blockchain, Action::Stake}), params); - func.encode(payload); amount = uint256_t(stake.amount()); } void handleUnstake(const Proto::Unstake& unstake, const Proto::Blockchain& blockchain, Data& payload) { - Params params; - params.emplace_back(std::make_shared(uint256_t(unstake.amount()))); + Ethereum::ABI::BaseParams params; + params.emplace_back(std::make_shared(uint256_t(unstake.amount()))); auto functionName = gStraderFunctionRegistry.at({blockchain, Action::Unstake}); - auto func = Ethereum::ABI::Function(functionName, params); - func.encode(payload); + auto funcData = Ethereum::ABI::Function::encodeFunctionCall(functionName, params); + if (funcData.has_value()) { + payload = funcData.value(); + } } void handleWithdraw(const Proto::Withdraw& withdraw, const Proto::Blockchain& blockchain, Data& payload) { - Params params; - params.emplace_back(std::make_shared(uint256_t(withdraw.idx()))); + Ethereum::ABI::BaseParams params; + params.emplace_back(std::make_shared(uint256_t(withdraw.idx()))); auto functionName = gStraderFunctionRegistry.at({blockchain, Action::Withdraw}); - auto func = Ethereum::ABI::Function(functionName, params); - func.encode(payload); + auto funcData = Ethereum::ABI::Function::encodeFunctionCall(functionName, params); + if (funcData.has_value()) { + payload = funcData.value(); + } } } diff --git a/src/NativeEvmos/Entry.h b/src/NativeEvmos/Entry.h new file mode 100644 index 00000000000..bc44a9620e2 --- /dev/null +++ b/src/NativeEvmos/Entry.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "Cosmos/Entry.h" + +namespace TW::NativeEvmos { + +/// Entry point for implementation of NativeEvmos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Cosmos::Entry { +}; + +} // namespace TW::NativeEvmos diff --git a/src/NativeInjective/Entry.h b/src/NativeInjective/Entry.h new file mode 100644 index 00000000000..1a55e88540b --- /dev/null +++ b/src/NativeInjective/Entry.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "Cosmos/Entry.h" + +namespace TW::NativeInjective { + +/// Entry point for implementation of NativeEvmos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Cosmos::Entry { +}; + +} // namespace TW::NativeInjective diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 373c021c3e6..5707103c8d1 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -25,6 +25,36 @@ using namespace TW; +Data rust_get_public_from_private(const Data& key, TWPublicKeyType public_type) { + auto* privkey = Rust::tw_private_key_create_with_data(key.data(), key.size()); + if (privkey == nullptr) { + return {}; + } + Data toReturn; + + auto* pubkey = Rust::tw_private_key_get_public_key_by_type(privkey, static_cast(public_type)); + if (pubkey == nullptr) { + Rust::tw_private_key_delete(privkey); + return {}; + } + + Rust::CByteArrayWrapper res = Rust::tw_public_key_data(pubkey); + + Rust::tw_public_key_delete(pubkey); + Rust::tw_private_key_delete(privkey); + return res.data; +} + +Data rust_private_key_sign(const Data& key, const Data& hash, TWCurve curve) { + auto* priv = Rust::tw_private_key_create_with_data(key.data(), key.size()); + if (priv == nullptr) { + return {}; + } + Rust::CByteArrayWrapper res = Rust::tw_private_key_sign(priv, hash.data(), hash.size(), static_cast(curve)); + Rust::tw_private_key_delete(priv); + return res.data; +} + bool PrivateKey::isValid(const Data& data) { // Check length if (data.size() != _size && data.size() != cardanoKeySize) { @@ -162,34 +192,13 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { } case TWPublicKeyTypeStarkex: { - result = ImmutableX::getPublicKeyFromPrivateKey(this->bytes); - if (result.size() == PublicKey::starkexSize - 1) { - result.insert(result.begin(), 0); - } + result = rust_get_public_from_private(this->bytes, type); break; } } return PublicKey(result, type); } -Data PrivateKey::getSharedKey(const PublicKey& pubKey, TWCurve curve) const { - if (curve != TWCurveSECP256k1) { - return {}; - } - - Data result(PublicKey::secp256k1ExtendedSize); - bool success = ecdh_multiply(&secp256k1, key().data(), - pubKey.bytes.data(), result.data()) == 0; - - if (success) { - PublicKey sharedKey(result, TWPublicKeyTypeSECP256k1Extended); - auto hash = Hash::sha256(sharedKey.compressed().bytes); - return hash; - } - - return {}; -} - int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, const uint8_t* digest, size_t digest_size, uint8_t* sig, uint8_t* pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { if (digest_size < 32) { return -1; @@ -202,43 +211,42 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { Data result; bool success = false; switch (curve) { - case TWCurveSECP256k1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; - } break; - case TWCurveED25519: { - result.resize(64); - ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); - success = true; - } break; - case TWCurveED25519Blake2bNano: { - result.resize(64); - ed25519_sign_blake2b(digest.data(), digest.size(), key().data(), result.data()); - success = true; - } break; - case TWCurveED25519ExtendedCardano: { - result.resize(64); - ed25519_sign_ext(digest.data(), digest.size(), key().data(), extension().data(), result.data()); - success = true; - } break; - case TWCurveCurve25519: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); - const auto sign_bit = publicKey.bytes[31] & 0x80; - result[63] = result[63] & 127; - result[63] |= sign_bit; - success = true; - } break; - case TWCurveNIST256p1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; - } break; + case TWCurveSECP256k1: { + result.resize(65); + success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; + } break; + case TWCurveED25519: { + result.resize(64); + ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); + success = true; + } break; + case TWCurveED25519Blake2bNano: { + result.resize(64); + ed25519_sign_blake2b(digest.data(), digest.size(), key().data(), result.data()); + success = true; + } break; + case TWCurveED25519ExtendedCardano: { + result.resize(64); + ed25519_sign_ext(digest.data(), digest.size(), key().data(), extension().data(), result.data()); + success = true; + } break; + case TWCurveCurve25519: { + result.resize(64); + const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); + ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); + const auto sign_bit = publicKey.bytes[31] & 0x80; + result[63] = result[63] & 127; + result[63] |= sign_bit; + success = true; + } break; + case TWCurveNIST256p1: { + result.resize(65); + success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; + } break; case TWCurveStarkex: { - result = ImmutableX::sign(this->bytes, digest); - success = true; - break; - } + result = rust_private_key_sign(key(), digest, curve); + success = result.size() == 64; + } break; case TWCurveNone: default: break; @@ -308,5 +316,5 @@ Data PrivateKey::signZilliqa(const Data& message) const { } void PrivateKey::cleanup() { - std::fill(bytes.begin(), bytes.end(), 0); + memzero(bytes.data(), bytes.size()); } diff --git a/src/PrivateKey.h b/src/PrivateKey.h index 26290e238d4..e314c8350a0 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -65,10 +65,6 @@ class PrivateKey { /// Returns the public key for this private key. PublicKey getPublicKey(enum TWPublicKeyType type) const; - /// Computes an EC Diffie-Hellman secret in constant time - /// Supported curves: secp256k1 - Data getSharedKey(const PublicKey& publicKey, TWCurve curve) const; - /// Signs a digest using the given ECDSA curve. Data sign(const Data& digest, TWCurve curve) const; diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 8c6fd35c569..2609b4216da 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -7,6 +7,7 @@ #include "PublicKey.h" #include "PrivateKey.h" #include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" #include #include @@ -132,6 +133,16 @@ PublicKey PublicKey::extended() const { } } +bool rust_public_key_verify(const Data& key, TWPublicKeyType type, const Data& sig, const Data& msgHash) { + auto* pubkey = Rust::tw_public_key_create_with_data(key.data(), key.size(), static_cast(type)); + if (pubkey == nullptr) { + return {}; + } + bool verified = Rust::tw_public_key_verify(pubkey, sig.data(), sig.size(), msgHash.data(), msgHash.size()); + Rust::tw_public_key_delete(pubkey); + return verified; +} + bool PublicKey::verify(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: @@ -163,7 +174,7 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), verifyBuffer.data()) == 0; } case TWPublicKeyTypeStarkex: - return ImmutableX::verify(this->bytes, signature, message); + return rust_public_key_verify(bytes, type, signature, message); default: throw std::logic_error("Not yet implemented"); } diff --git a/src/Ronin/Address.cpp b/src/Ronin/Address.cpp deleted file mode 100644 index a64e9d0383c..00000000000 --- a/src/Ronin/Address.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" -#include "Ethereum/Address.h" -#include "Ethereum/AddressChecksum.h" -#include "../Hash.h" -#include "../HexCoding.h" - -const std::string prefix = "ronin:"; - -namespace TW::Ronin { - -bool Address::isValid(const std::string& string) { - // check prefix - if (string.compare(0, prefix.length(), prefix) == 0) { - const auto suffix = string.substr(prefix.length()); - const auto data = parse_hex(suffix); - return Ethereum::Address::isValid(data); - } - // accept Ethereum format as well - if (Ethereum::Address::isValid(string)) { - return true; - } - return false; -} - -Address::Address(const std::string& string) { - // check prefix - if (string.compare(0, prefix.length(), prefix) == 0) { - const auto suffix = string.substr(prefix.length()); - const auto data = parse_hex(suffix); - std::copy(data.begin(), data.end(), bytes.begin()); - } else if (Ethereum::Address::isValid(string)) { - // accept Ethereum format as well - Ethereum::Address ethereumAddress(string); - bytes = ethereumAddress.bytes; - } else { - throw std::invalid_argument("Invalid address data"); - } -} - -// Normalized: with ronin prefix, checksummed hex address, no 0x prefix -std::string Address::string() const { - std::string address = Ethereum::checksumed(*this); - if (address.size() >= 2 && address.substr(0, 2) == "0x") { - address = address.substr(2); - } // skip 0x - return prefix + address; -} - -} // namespace TW::Ronin diff --git a/src/Ronin/Address.h b/src/Ronin/Address.h deleted file mode 100644 index 24369e09d87..00000000000 --- a/src/Ronin/Address.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../PublicKey.h" -#include "../Ethereum/Address.h" -#include -#include - -namespace TW::Ronin { - -class Address: public Ethereum::Address { - public: - /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); - - /// Initializes an address with a string representation. - explicit Address(const std::string& string); - - /// Initializes an address with a public key. - explicit Address(const PublicKey& publicKey): Ethereum::Address(publicKey) {} - - /// Returns a string representation of the address. - std::string string() const; -}; - -} // namespace TW::Ronin diff --git a/src/Ronin/Entry.cpp b/src/Ronin/Entry.cpp deleted file mode 100644 index c70d2cca7b4..00000000000 --- a/src/Ronin/Entry.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Address.h" -#include "../Ethereum/Signer.h" - -using namespace TW; -using namespace std; - -namespace TW::Ronin { - -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address::isValid(address); -} - -string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { - return Address(address).string(); -} - -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address(publicKey).string(); -} - -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { - const auto addr = Address(address); - return {addr.bytes.begin(), addr.bytes.end()}; -} - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { - return Ethereum::Signer::signJSON(json, key); -} - -} // namespace TW::Ronin diff --git a/src/Ronin/Entry.h b/src/Ronin/Entry.h index e8ea769ff6e..b9b5ab2c2d9 100644 --- a/src/Ronin/Entry.h +++ b/src/Ronin/Entry.h @@ -6,20 +6,12 @@ #pragma once -#include "../CoinEntry.h" +#include "Ethereum/Entry.h" namespace TW::Ronin { /// Entry point for Ronin (EVM side chain) -class Entry final : public CoinEntry { -public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +class Entry final : public Ethereum::Entry { }; } // namespace TW::Ronin diff --git a/src/THORChain/Entry.cpp b/src/THORChain/Entry.cpp deleted file mode 100644 index ed96e9e3d9d..00000000000 --- a/src/THORChain/Entry.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Signer.h" -#include "../proto/Cosmos.pb.h" - -using namespace std; - -namespace TW::THORChain { - -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Cosmos::Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - auto serializedOut = Signer::sign(input).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} - -string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} - -} // namespace TW::THORChain diff --git a/src/THORChain/Entry.h b/src/THORChain/Entry.h index aeb3707cd1f..551b26be00b 100644 --- a/src/THORChain/Entry.h +++ b/src/THORChain/Entry.h @@ -14,9 +14,6 @@ namespace TW::THORChain { /// Entry point for implementation of THORChain coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file class Entry final : public Cosmos::Entry { -public: - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::THORChain diff --git a/src/THORChain/Signer.cpp b/src/THORChain/Signer.cpp deleted file mode 100644 index 7554e242013..00000000000 --- a/src/THORChain/Signer.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "../Cosmos/Signer.h" -#include "../proto/Cosmos.pb.h" - -#include -#include - -using namespace TW; - -namespace TW::THORChain { -const std::string TYPE_PREFIX_MSG_SEND = "thorchain/MsgSend"; - -Cosmos::Proto::SigningOutput Signer::sign(Cosmos::Proto::SigningInput& input) noexcept { - for (auto i = 0; i < input.messages_size(); ++i) { - if (input.messages(i).has_send_coins_message()) { - input.mutable_messages(i)->mutable_send_coins_message()->set_type_prefix(TYPE_PREFIX_MSG_SEND); - } - } - return Cosmos::Signer::sign(input, TWCoinTypeTHORChain); -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Cosmos::Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); - return output.json(); -} - -} // namespace TW::THORChain diff --git a/src/THORChain/Signer.h b/src/THORChain/Signer.h deleted file mode 100644 index 10a5214c784..00000000000 --- a/src/THORChain/Signer.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "../proto/Cosmos.pb.h" -#include - -namespace TW::THORChain { - -/// Helper class that performs THORChain transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Cosmos::Proto::SigningOutput sign(Cosmos::Proto::SigningInput& input) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); -}; - -} // namespace TW::THORChain diff --git a/src/THORChain/Swap.cpp b/src/THORChain/Swap.cpp index 89e53e4a221..92ef7a79c95 100644 --- a/src/THORChain/Swap.cpp +++ b/src/THORChain/Swap.cpp @@ -19,8 +19,6 @@ #include "../proto/Bitcoin.pb.h" // ETH #include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamAddress.h" -#include "Ethereum/ABI/ParamBase.h" #include "Ethereum/Address.h" #include "uint256.h" #include "../proto/Ethereum.pb.h" @@ -65,6 +63,8 @@ TWCoinType chainCoinType(Chain chain) { return TWCoinTypeLitecoin; case Chain::ATOM: return TWCoinTypeCosmos; + case Chain::BSC: + return TWCoinTypeSmartChain; case Chain::THOR: default: return TWCoinTypeTHORChain; @@ -79,6 +79,8 @@ std::string chainName(Chain chain) { return "ETH"; case Chain::BNB: return "BNB"; + case Chain::BSC: + return "BSC"; case Chain::BTC: return "BTC"; case Chain::DOGE: @@ -127,6 +129,7 @@ SwapBundled SwapBuilder::build(bool shortened) { return buildAtom(fromAmountNum, memo); case Chain::ETH: case Chain::AVAX: + case Chain::BSC: return buildEth(fromAmountNum, memo); } default: @@ -237,7 +240,7 @@ SwapBundled SwapBuilder::buildEth(const uint256_t& amount, const std::string& me Data out; auto input = Ethereum::Proto::SigningInput(); // EIP-1559 - input.set_tx_mode(Ethereum::Proto::Enveloped); + input.set_tx_mode(this->mFromAsset.chain() == Proto::Chain::BSC ? Ethereum::Proto::Legacy : Ethereum::Proto::Enveloped); const auto& toTokenId = mFromAsset.token_id(); // some sanity check / address conversion Data vaultAddressBin = ethAddressStringToData(mVaultAddress); @@ -269,15 +272,19 @@ SwapBundled SwapBuilder::buildEth(const uint256_t& amount, const std::string& me mExpirationPolicy = std::chrono::duration_cast(in_15_minutes.time_since_epoch()).count(); } auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); - auto func = Ethereum::ABI::Function("depositWithExpiry", std::vector>{ - std::make_shared(vaultAddressBin), - std::make_shared(toAssetAddressBin), - std::make_shared(uint256_t(amount)), - std::make_shared(memo), - std::make_shared(uint256_t(*mExpirationPolicy))}); - Data payload; - func.encode(payload); - transfer.set_data(payload.data(), payload.size()); + + // Ethereum::ABI::AbiProto::NamedParam + auto payload = Ethereum::ABI::Function::encodeFunctionCall("depositWithExpiry", { + std::make_shared(mVaultAddress), + std::make_shared(toTokenId), + std::make_shared(amount), + std::make_shared(memo), + std::make_shared(uint256_t(*mExpirationPolicy)), + }); + if (payload.has_value()) { + transfer.set_data(payload.value().data(), payload.value().size()); + } + Data amountData = store(uint256_t(0)); // if tokenId is set to 0x0000000000000000000000000000000000000000 this means we are sending ethereum and transfer amount also need to be set if (toTokenId == "0x0000000000000000000000000000000000000000") { diff --git a/src/THORChain/Swap.h b/src/THORChain/Swap.h index 7f98df6e5d9..e6aa32a3001 100644 --- a/src/THORChain/Swap.h +++ b/src/THORChain/Swap.h @@ -27,7 +27,8 @@ enum Chain { BCH = 5, LTC = 6, ATOM = 7, - AVAX = 8 + AVAX = 8, + BSC = 9, }; using SwapErrorCode = int; diff --git a/src/THORChain/TWSwap.cpp b/src/THORChain/TWSwap.cpp index f1d4b203dc3..22915577761 100644 --- a/src/THORChain/TWSwap.cpp +++ b/src/THORChain/TWSwap.cpp @@ -72,6 +72,7 @@ TWData* _Nonnull TWTHORChainSwapBuildSwap(TWData* _Nonnull input) { } break; case THORChainSwap::Proto::ETH: + case THORChainSwap::Proto::BSC: case THORChainSwap::Proto::AVAX: { Ethereum::Proto::SigningInput ethInput; if (!ethInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { diff --git a/src/Theta/Entry.cpp b/src/Theta/Entry.cpp index 7590fba906c..803877dc1e3 100644 --- a/src/Theta/Entry.cpp +++ b/src/Theta/Entry.cpp @@ -12,6 +12,24 @@ namespace TW::Theta { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address::isValid(address); +} + +std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { + // normalized with EIP55 checksum + return Ethereum::Address(address).string(); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address(publicKey).string(); +} + +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Ethereum::Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } diff --git a/src/Theta/Entry.h b/src/Theta/Entry.h index 7c6e2eeabfb..bcb79ac69fe 100644 --- a/src/Theta/Entry.h +++ b/src/Theta/Entry.h @@ -6,19 +6,21 @@ #pragma once -#include "Ethereum/Entry.h" -#include "../CoinEntry.h" -#include "Ethereum/Entry.h" +#include "CoinEntry.h" namespace TW::Theta { /// Entry point for Theta. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public Ethereum::Entry { +class Entry final : public CoinEntry { public: - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - Data preImageHashes(TWCoinType coin, const Data& txInputData) const; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Theta diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index 01d46f037f6..050deff969b 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -31,7 +31,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); transaction.setSignature(from, signature); - auto encoded = transaction.encode(); + auto encoded = transaction.encodePayload(); output.set_encoded(encoded.data(), encoded.size()); output.set_signature(signature.data(), signature.size()); return output; @@ -39,25 +39,25 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::encode(const Transaction& transaction) const { const uint64_t nonce = 0; - const uint256_t gasPrice = 0; + const uint64_t gasPrice = 0; const uint64_t gasLimit = 0; - const Ethereum::Address to = Ethereum::Address("0x0000000000000000000000000000000000000000"); - const uint256_t amount = 0; + const auto* to = "0x0000000000000000000000000000000000000000"; + const uint64_t amount = 0; + auto txData = transaction.encode(chainID); + + EthereumRlp::Proto::EncodingInput encodingInput; + auto* rlpList = encodingInput.mutable_item()->mutable_list(); - auto encoded = Data(); /// Need to add the following prefix to the tx signbytes to be compatible with /// the Ethereum tx format - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to.bytes)); - append(encoded, RLP::encode(amount)); - /// Chain ID - auto payload = Data(); - append(payload, RLP::encode(chainID)); - append(payload, transaction.encode()); - append(encoded, RLP::encode(payload)); - return RLP::encodeList(encoded); + rlpList->add_items()->set_number_u64(nonce); + rlpList->add_items()->set_number_u64(gasPrice); + rlpList->add_items()->set_number_u64(gasLimit); + rlpList->add_items()->set_address(to); + rlpList->add_items()->set_number_u64(amount); + rlpList->add_items()->set_data(txData.data(), txData.size()); + + return RLP::encode(encodingInput); } Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept { @@ -102,7 +102,7 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub auto protoOutput = Proto::SigningOutput(); auto transaction = buildTransaction(); transaction.setSignature(from, signature); - auto encoded = transaction.encode(); + auto encoded = transaction.encodePayload(); protoOutput.set_encoded(encoded.data(), encoded.size()); protoOutput.set_signature(signature.data(), signature.size()); diff --git a/src/Theta/Transaction.cpp b/src/Theta/Transaction.cpp index b9a70c0611e..939c54aa7b3 100644 --- a/src/Theta/Transaction.cpp +++ b/src/Theta/Transaction.cpp @@ -12,43 +12,61 @@ namespace TW::Theta { using RLP = Ethereum::RLP; -Data encode(const Coins& coins) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(coins.thetaWei)); - append(encoded, RLP::encode(coins.tfuelWei)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const Coins& coins) noexcept { + auto thetaWei = store(coins.thetaWei); + auto tfuelWei = store(coins.tfuelWei); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_number_u256(thetaWei.data(), thetaWei.size()); + rlpList->add_items()->set_number_u256(tfuelWei.data(), tfuelWei.size()); + + return item; } -Data encode(const TxInput& input) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(input.address.bytes)); - append(encoded, encode(input.coins)); - append(encoded, RLP::encode(input.sequence)); - append(encoded, RLP::encode(input.signature)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const TxInput& input) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(input.address.bytes.data(), input.address.bytes.size()); + *rlpList->add_items() = prepare(input.coins); + rlpList->add_items()->set_number_u64(input.sequence); + rlpList->add_items()->set_data(input.signature.data(), input.signature.size()); + + return item; } -Data encode(const std::vector& inputs) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepare(const std::vector& inputs) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + for (const auto& input : inputs) { - append(encoded, encode(input)); + *rlpList->add_items() = prepare(input); } - return RLP::encodeList(encoded); + + return item; } -Data encode(const TxOutput& output) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(output.address.bytes)); - append(encoded, encode(output.coins)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const TxOutput& output) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(output.address.bytes.data(), output.address.bytes.size()); + *rlpList->add_items() = prepare(output.coins); + + return item; } -Data encode(const std::vector& outputs) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepare(const std::vector& outputs) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + for (const auto& output : outputs) { - append(encoded, encode(output)); + *rlpList->add_items() = prepare(output); } - return RLP::encodeList(encoded); + + return item; } Transaction::Transaction(Ethereum::Address from, Ethereum::Address to, @@ -65,15 +83,29 @@ Transaction::Transaction(Ethereum::Address from, Ethereum::Address to, this->outputs.push_back(output); } -Data Transaction::encode() const noexcept { - auto encoded = Data(); - uint16_t txType = 2; // TxSend - append(encoded, RLP::encode(txType)); - auto encodedData = Data(); - append(encodedData, Theta::encode(_fee)); - append(encodedData, Theta::encode(inputs)); - append(encodedData, Theta::encode(outputs)); - append(encoded, RLP::encodeList(encodedData)); +Data Transaction::encodePayload() const noexcept { + const uint64_t txType = 2; // TxSend + + EthereumRlp::Proto::EncodingInput txInput; + auto* txPropertiesList = txInput.mutable_item()->mutable_list(); + + *txPropertiesList->add_items() = prepare(_fee); + *txPropertiesList->add_items() = prepare(inputs); + *txPropertiesList->add_items() = prepare(outputs); + + auto txPropertiesEncoded = RLP::encode(txInput); + + Data payload; + append(payload, RLP::encodeU256(static_cast(txType))); + append(payload, txPropertiesEncoded); + + return payload; +} + +Data Transaction::encode(const std::string& chainId) const noexcept { + Data encoded; + append(encoded, RLP::encodeString(chainId)); + append(encoded, encodePayload()); return encoded; } diff --git a/src/Theta/Transaction.h b/src/Theta/Transaction.h index 770bcc1cf75..5e1a1973d12 100644 --- a/src/Theta/Transaction.h +++ b/src/Theta/Transaction.h @@ -11,7 +11,7 @@ #include "Coins.h" #include "Data.h" -#include "../Ethereum/Address.h" +#include "Ethereum/Address.h" namespace TW::Theta { @@ -51,8 +51,11 @@ class Transaction { const uint256_t& thetaAmount, const uint256_t& tfuelAmount, uint64_t sequence, const uint256_t& feeAmount = 1000000000000); - /// Encodes the transaction - Data encode() const noexcept; + /// Encodes the essential part of the transaction without a Chain ID. + Data encodePayload() const noexcept; + + /// Encodes the transaction with the given `chainId`. + Data encode(const std::string& chainId) const noexcept; /// Sets signature bool setSignature(const Ethereum::Address& address, const Data& signature) noexcept; diff --git a/src/VeChain/Entry.cpp b/src/VeChain/Entry.cpp index 1d15dcfa22b..c9724dfae75 100644 --- a/src/VeChain/Entry.cpp +++ b/src/VeChain/Entry.cpp @@ -6,13 +6,29 @@ #include "Entry.h" #include -#include "../proto/Common.pb.h" -#include "../Hash.h" #include "Ethereum/Address.h" #include "Signer.h" namespace TW::VeChain { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address::isValid(address); +} + +std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { + // normalized with EIP55 checksum + return Ethereum::Address(address).string(); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address(publicKey).string(); +} + +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Ethereum::Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); diff --git a/src/VeChain/Entry.h b/src/VeChain/Entry.h index f1977330557..8a06ae5434c 100644 --- a/src/VeChain/Entry.h +++ b/src/VeChain/Entry.h @@ -6,18 +6,21 @@ #pragma once -#include "Ethereum/Entry.h" +#include "CoinEntry.h" namespace TW::VeChain { /// Entry point for VeChain. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public Ethereum::Entry { +class Entry final : public CoinEntry { public: - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - - Data preImageHashes(TWCoinType coin, const Data& txInputData) const; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::VeChain diff --git a/src/VeChain/Transaction.cpp b/src/VeChain/Transaction.cpp index 8c28ea8becf..48030e74f77 100644 --- a/src/VeChain/Transaction.cpp +++ b/src/VeChain/Transaction.cpp @@ -12,38 +12,50 @@ namespace TW::VeChain { using RLP = Ethereum::RLP; -Data encode(const Clause& clause) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(clause.to.bytes)); - append(encoded, RLP::encode(clause.value)); - append(encoded, RLP::encode(clause.data)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepareClause(const Clause& clause) noexcept { + auto value = store(clause.value); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(clause.to.bytes.data(), clause.to.bytes.size()); + rlpList->add_items()->set_number_u256(value.data(), value.size()); + rlpList->add_items()->set_data(clause.data.data(), clause.data.size()); + + return item; } -Data encodeClauses(std::vector clauses) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepareClauses(const std::vector& clauses) noexcept { + EthereumRlp::Proto::RlpItem item; + + auto* rlpList = item.mutable_list(); for (const auto& clause : clauses) { - auto encodedClause = encode(clause); - append(encoded, encodedClause); + *rlpList->add_items() = prepareClause(clause); } - return RLP::encodeList(encoded); + + return item; } Data Transaction::encode() const noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(chainTag)); - append(encoded, RLP::encode(blockRef)); - append(encoded, RLP::encode(expiration)); - append(encoded, encodeClauses(clauses)); - append(encoded, RLP::encode(gasPriceCoef)); - append(encoded, RLP::encode(gas)); - append(encoded, RLP::encode(dependsOn)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encodeList(reserved)); + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(chainTag); + rlpList->add_items()->set_number_u64(blockRef); + rlpList->add_items()->set_number_u64(expiration); + *rlpList->add_items() = prepareClauses(clauses); + rlpList->add_items()->set_number_u64(gasPriceCoef); + rlpList->add_items()->set_number_u64(gas); + rlpList->add_items()->set_data(dependsOn.data(), dependsOn.size()); + rlpList->add_items()->set_number_u64(nonce); + // Put an empty list - reserved field for backward compatibility. + rlpList->add_items()->mutable_list(); + if (!signature.empty()) { - append(encoded, RLP::encode(signature)); + rlpList->add_items()->set_data(signature.data(), signature.size()); } - return RLP::encodeList(encoded); + + return RLP::encode(input); } } // namespace TW::VeChain diff --git a/src/XRP/Signer.cpp b/src/XRP/Signer.cpp index c8b3e018585..c60afac81eb 100644 --- a/src/XRP/Signer.cpp +++ b/src/XRP/Signer.cpp @@ -26,6 +26,30 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { signPayment(input, output, transaction); break; + case Proto::SigningInput::kOpEscrowCreate: + transaction.createEscrowCreate( + input.op_escrow_create().amount(), + input.op_escrow_create().destination(), + input.op_escrow_create().destination_tag(), + input.op_escrow_create().cancel_after(), + input.op_escrow_create().finish_after(), + input.op_escrow_create().condition()); + break; + + case Proto::SigningInput::kOpEscrowCancel: + transaction.createEscrowCancel( + input.op_escrow_cancel().owner(), + input.op_escrow_cancel().offer_sequence()); + break; + + case Proto::SigningInput::kOpEscrowFinish: + transaction.createEscrowFinish( + input.op_escrow_finish().owner(), + input.op_escrow_finish().offer_sequence(), + input.op_escrow_finish().condition(), + input.op_escrow_finish().fulfillment()); + break; + case Proto::SigningInput::kOpNftokenBurn: transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); break; @@ -94,6 +118,30 @@ TW::Data Signer::preImage() const { signPayment(input, output, transaction); break; + case Proto::SigningInput::kOpEscrowCreate: + transaction.createEscrowCreate( + input.op_escrow_create().amount(), + input.op_escrow_create().destination(), + input.op_escrow_create().destination_tag(), + input.op_escrow_create().cancel_after(), + input.op_escrow_create().finish_after(), + input.op_escrow_create().condition()); + break; + + case Proto::SigningInput::kOpEscrowCancel: + transaction.createEscrowCancel( + input.op_escrow_cancel().owner(), + input.op_escrow_cancel().offer_sequence()); + break; + + case Proto::SigningInput::kOpEscrowFinish: + transaction.createEscrowFinish( + input.op_escrow_finish().owner(), + input.op_escrow_finish().offer_sequence(), + input.op_escrow_finish().condition(), + input.op_escrow_finish().fulfillment()); + break; + case Proto::SigningInput::kOpNftokenBurn: transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); break; @@ -149,6 +197,30 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub signPayment(input, output, transaction); break; + case Proto::SigningInput::kOpEscrowCreate: + transaction.createEscrowCreate( + input.op_escrow_create().amount(), + input.op_escrow_create().destination(), + input.op_escrow_create().destination_tag(), + input.op_escrow_create().cancel_after(), + input.op_escrow_create().finish_after(), + input.op_escrow_create().condition()); + break; + + case Proto::SigningInput::kOpEscrowCancel: + transaction.createEscrowCancel( + input.op_escrow_cancel().owner(), + input.op_escrow_cancel().offer_sequence()); + break; + + case Proto::SigningInput::kOpEscrowFinish: + transaction.createEscrowFinish( + input.op_escrow_finish().owner(), + input.op_escrow_finish().offer_sequence(), + input.op_escrow_finish().condition(), + input.op_escrow_finish().fulfillment()); + break; + case Proto::SigningInput::kOpNftokenBurn: transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); break; diff --git a/src/XRP/Transaction.cpp b/src/XRP/Transaction.cpp index f608fa962e0..52dbc3b0cff 100644 --- a/src/XRP/Transaction.cpp +++ b/src/XRP/Transaction.cpp @@ -22,7 +22,9 @@ Data Transaction::serialize() const { auto data = Data(); - /// field must be sorted by field type then by field name + /// fields must be sorted by field type code then by field code (key) + // https://xrpl.org/serialization.html#canonical-field-order + /// "type" encodeType(FieldType::int16, 2, data); encode16BE(uint16_t(transaction_type), data); @@ -36,17 +38,37 @@ Data Transaction::serialize() const { encode32BE(sequence, data); /// "destinationTag" - if ((transaction_type == TransactionType::payment) && encode_tag) { + if (((transaction_type == TransactionType::payment) || + (transaction_type == TransactionType::EscrowCreate)) && encode_tag) { encodeType(FieldType::int32, 14, data); encode32BE(static_cast(destination_tag), data); } + /// "OfferSequence" + if ((transaction_type == TransactionType::EscrowCancel) || + (transaction_type == TransactionType::EscrowFinish)) { + encodeType(FieldType::int32, 25, data); + encode32BE(offer_sequence, data); + } + /// "lastLedgerSequence" if (last_ledger_sequence > 0) { encodeType(FieldType::int32, 27, data); encode32BE(last_ledger_sequence, data); } + /// "CancelAfter" + if ((transaction_type == TransactionType::EscrowCreate) && cancel_after > 0) { + encodeType(FieldType::int32, 36, data); + encode32BE(static_cast(cancel_after), data); + } + + /// "FinishAfter" + if ((transaction_type == TransactionType::EscrowCreate) && finish_after > 0) { + encodeType(FieldType::int32, 37, data); + encode32BE(static_cast(finish_after), data); + } + /// "NFTokenId" if ((transaction_type == TransactionType::NFTokenCreateOffer) || (transaction_type == TransactionType::NFTokenBurn)) { @@ -71,6 +93,9 @@ Data Transaction::serialize() const { } else if (transaction_type == TransactionType::TrustSet) { encodeType(FieldType::amount, 3, data); append(data, serializeCurrencyAmount(limit_amount)); + } else if (transaction_type == TransactionType::EscrowCreate) { + encodeType(FieldType::amount, 1, data); + append(data, serializeAmount(amount)); } /// "fee" @@ -82,23 +107,45 @@ Data Transaction::serialize() const { encodeType(FieldType::vl, 3, data); encodeBytes(pub_key, data); } + /// "txnSignature" if (!signature.empty()) { encodeType(FieldType::vl, 4, data); encodeBytes(signature, data); } + /// "Fulfillment" + if ((transaction_type == TransactionType::EscrowFinish) && !fulfillment.empty()) { + encodeType(FieldType::vl, 16, data); + encodeBytes(fulfillment, data); + } + + /// "Condition" + if (((transaction_type == TransactionType::EscrowCreate) || + (transaction_type == TransactionType::EscrowFinish)) && !condition.empty()) { + encodeType(FieldType::vl, 17, data); + encodeBytes(condition, data); + } + /// "account" encodeType(FieldType::account, 1, data); encodeBytes(serializeAddress(account), data); /// "destination" if ((transaction_type == TransactionType::payment) || - (transaction_type == TransactionType::NFTokenCreateOffer)) { + (transaction_type == TransactionType::NFTokenCreateOffer) || + (transaction_type == TransactionType::EscrowCreate)) { encodeType(FieldType::account, 3, data); encodeBytes(destination, data); } + /// "Owner" + if ((transaction_type == TransactionType::EscrowCancel) || + (transaction_type == TransactionType::EscrowFinish)) { + encodeType(FieldType::account, 2, data); + encodeBytes(owner, data); + } + /// "NFTokenOffers" if (transaction_type == TransactionType::NFTokenCancelOffer) { // only support one offer diff --git a/src/XRP/Transaction.h b/src/XRP/Transaction.h index 563fc9fd78d..3058a8c856d 100644 --- a/src/XRP/Transaction.h +++ b/src/XRP/Transaction.h @@ -29,6 +29,9 @@ enum class FieldType: int { enum class TransactionType { no_type = -1, payment = 0, + EscrowCreate = 1, + EscrowFinish = 2, + EscrowCancel = 4, TrustSet = 20, NFTokenBurn = 26, NFTokenCreateOffer = 27, @@ -64,6 +67,12 @@ class Transaction { int64_t destination_tag; Data pub_key; Data signature; + int64_t cancel_after; + int64_t finish_after; + Data owner; + int32_t offer_sequence; + Data condition; + Data fulfillment; Data nftoken_id; Data sell_offer; Data token_offers; @@ -78,6 +87,9 @@ class Transaction { , account(p_account) , encode_tag(false) , destination_tag(0) + , cancel_after(0) + , finish_after(0) + , offer_sequence(0) , nftoken_id(0) , sell_offer(0) , token_offers(0) @@ -108,6 +120,36 @@ class Transaction { setCurrencyAmount(currency_amount, currency, value, issuer); } + void createEscrowCreate(int64_t amount, const std::string& destination, int64_t destination_tag, + int64_t cancel_after, int64_t finish_after, const std::string& condition) { + transaction_type = TransactionType::EscrowCreate; + if (cancel_after == 0 && finish_after == 0) { + throw std::invalid_argument("Either CancelAfter or FinishAfter must be specified"); + } else if (finish_after == 0 && condition.length() == 0) { + throw std::invalid_argument("Either Condition or FinishAfter must be specified"); + } + this->amount = amount; + setDestination(destination, destination_tag); + this->cancel_after = cancel_after; + this->finish_after = finish_after; + this->condition = parse_hex(condition); + } + + void createEscrowCancel(const std::string& owner, int32_t offer_sequence) { + transaction_type = TransactionType::EscrowCancel; + setAccount(owner, this->owner); + this->offer_sequence = offer_sequence; + } + + void createEscrowFinish(const std::string& owner, int32_t offer_sequence, + const std::string& condition, const std::string& fulfillment) { + transaction_type = TransactionType::EscrowFinish; + setAccount(owner, this->owner); + this->offer_sequence = offer_sequence; + this->condition = parse_hex(condition); + this->fulfillment = parse_hex(fulfillment); + } + void createNFTokenBurn(const std::string& p_nftoken_id) { transaction_type = TransactionType::NFTokenBurn; nftoken_id = parse_hex(p_nftoken_id); diff --git a/src/interface/TWEthereumAbi.cpp b/src/interface/TWEthereumAbi.cpp index 24ed89d8138..e8eceb53344 100644 --- a/src/interface/TWEthereumAbi.cpp +++ b/src/interface/TWEthereumAbi.cpp @@ -8,44 +8,69 @@ #include #include "Data.h" -#include "Ethereum/ABI.h" +#include "Ethereum/ABI/Function.h" #include "Ethereum/ContractCall.h" +#include "Ethereum/MessageSigner.h" #include "HexCoding.h" -#include "uint256.h" #include -#include #include using namespace TW; namespace EthAbi = TW::Ethereum::ABI; +template +static TWData* _Nonnull ethereumAbiForwardToRust(F rustFunction, enum TWCoinType coin, TWData* _Nonnull input) { + const Data& inputData = *(reinterpret_cast(input)); + + const Rust::TWDataWrapper dataInPtr(inputData); + Rust::TWDataWrapper dataOutPtr = rustFunction(static_cast(coin), dataInPtr.get()); + + auto dataOut = dataOutPtr.toDataOrDefault(); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + +TWData* _Nonnull TWEthereumAbiDecodeContractCall(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_decode_contract_call, coin, input); +} + +TWData* _Nonnull TWEthereumAbiDecodeParams(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_decode_params, coin, input); +} + +TWData* _Nonnull TWEthereumAbiDecodeValue(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_decode_value, coin, input); +} + +TWData* _Nonnull TWEthereumAbiEncodeFunction(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_encode_function, coin, input); +} + TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull func_in) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - Data encoded; - function.encode(encoded); - return TWDataCreateWithData(&encoded); + Data encodedData; + auto encoded = func_in->impl.encodeInput(); + if (encoded.has_value()) { + encodedData = encoded.value(); + } + return TWDataCreateWithData(&encodedData); } bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull func_in, TWData* _Nonnull encoded) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; assert(encoded != nullptr); - Data encData = data(TWDataBytes(encoded), TWDataSize(encoded)); + const Data& encData = *(reinterpret_cast(encoded)); - size_t offset = 0; - return function.decodeOutput(encData, offset); + bool isOutput = true; + return func_in->impl.decode(encData, isOutput); } TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* _Nonnull abiString) { const Data& call = *(reinterpret_cast(callData)); const auto& jsonString = *reinterpret_cast(abiString); try { - auto abi = nlohmann::json::parse(jsonString); - auto string = EthAbi::decodeCall(call, abi); + auto string = EthAbi::decodeCall(call, jsonString); if (!string.has_value()) { return nullptr; } @@ -59,7 +84,7 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson) { Data data; try { - data = EthAbi::ParamStruct::hashStructJson(TWStringUTF8Bytes(messageJson)); + data = Ethereum::MessageSigner::typedDataPreImageHash(TWStringUTF8Bytes(messageJson)); } catch (...) {} // return empty return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWEthereumAbiFunction.cpp b/src/interface/TWEthereumAbiFunction.cpp index 01fe3ef7816..9a544c533e8 100644 --- a/src/interface/TWEthereumAbiFunction.cpp +++ b/src/interface/TWEthereumAbiFunction.cpp @@ -6,12 +6,10 @@ #include -#include "Ethereum/ABI.h" +#include "Ethereum/ABI/Function.h" #include "Data.h" #include "HexCoding.h" -#include "../uint256.h" -#include #include #include @@ -30,8 +28,7 @@ void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull func_in) TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull func_in) { assert(func_in != nullptr); - auto function = func_in->impl; - std::string sign = function.getType(); + std::string sign = func_in->impl.getType(); return TWStringCreateWithUTF8Bytes(sign.c_str()); } @@ -39,418 +36,369 @@ TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_N int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, uint8_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 8; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, uint16_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 16; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, uint32_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 32; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, uint64_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 64; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - auto param = std::make_shared(val2); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 256; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - auto param = std::make_shared(bits, val2); - auto idx = function.addParam(param, isOutput); - return idx; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addUintParam(static_cast(bits), encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int8_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 8; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int16_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 16; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int32_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 32; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int64_t val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 64; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); - auto param = std::make_shared(val2); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 256; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); - auto param = std::make_shared(bits, val2); - auto idx = function.addParam(param, isOutput); - return idx; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addIntParam(static_cast(bits), encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, bool val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `boolean` type. + paramType.mutable_param()->mutable_boolean(); + + EthereumAbi::Proto::Token token; + token.set_boolean(val); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull func_in, TWString *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - assert(val != nullptr); - auto param = std::make_shared(TWStringUTF8Bytes(val)); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `string` type. + paramType.mutable_param()->mutable_string_param(); + + EthereumAbi::Proto::Token token; + auto* s = reinterpret_cast(val); + token.set_string_value(*s); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - assert(val != nullptr); - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(data); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `address` type. + paramType.mutable_param()->mutable_address(); + + EthereumAbi::Proto::Token token; + const Data& addressData = *(reinterpret_cast(val)); + bool prefixed = true; + auto addressStr = hex(addressData, prefixed); + token.set_address(addressStr); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(data); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `byte_array` type. + paramType.mutable_param()->mutable_byte_array(); + + EthereumAbi::Proto::Token token; + const Data& bytesData = *(reinterpret_cast(val)); + token.set_byte_array(bytesData.data(), bytesData.size()); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, size_t count, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(count, data); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `byte_array_fix` type. + paramType.mutable_param()->mutable_byte_array_fix()->set_size(static_cast(count)); + + EthereumAbi::Proto::Token token; + Data bytesData = *(reinterpret_cast(val)); + bytesData.resize(count); + token.set_byte_array_fix(bytesData.data(), bytesData.size()); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull func_in, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - auto param = std::make_shared(); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `array` type. + paramType.mutable_param()->mutable_array(); + + EthereumAbi::Proto::Token token; + // Declare the `array` empty value. + token.mutable_array(); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } ///// GetParam uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return 0; - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return 0; - } - return param2->getVal(); + return func_in->impl.getUintParam(idx, 8, isOutput); } uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return 0; - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return 0; - } - return param2->getVal(); + return func_in->impl.getUintParam(idx, 64, isOutput); } TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - uint256_t val256 = 0; - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - TW::Data valData = TW::store(val256); - return TWDataCreateWithData(&valData); - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - TW::Data valData = TW::store(val256); - return TWDataCreateWithData(&valData); - } - val256 = param2->getVal(); - TW::Data valData = TW::store(val256); + auto valData = func_in->impl.getUintParamData(idx, 256, isOutput); return TWDataCreateWithData(&valData); } bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { + auto param = func_in->impl.getParam(idx, isOutput); + if (!param.has_value() || !param->has_boolean()) { return false; } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return false; - } - return param2->getVal(); + return param->boolean(); } TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - std::string valStr; - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return TWStringCreateWithUTF8Bytes(valStr.c_str()); - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { + + auto param = func_in->impl.getParam(idx, isOutput); + if (!param.has_value() || !param->has_string_value()) { return TWStringCreateWithUTF8Bytes(valStr.c_str()); } - valStr = param2->getVal(); + valStr = param->string_value(); return TWStringCreateWithUTF8Bytes(valStr.c_str()); } TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; + Data addressData; - Data valData; - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return TWDataCreateWithData(&valData); + auto param = func_in->impl.getParam(idx, isOutput); + if (!param.has_value() || !param->has_address()) { + return TWDataCreateWithData(&addressData); } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return TWDataCreateWithData(&valData); + auto addressStr = param->address(); + try { + addressData = parse_hex(addressStr); + return TWDataCreateWithData(&addressData); + } catch (...) { + return TWDataCreateWithData(&addressData); } - valData = param2->getData(); - return TWDataCreateWithData(&valData); } ///// AddInArrayParam -int addInArrayParam(EthAbi::Function& function, int arrayIdx, const std::shared_ptr& childParam) { - std::shared_ptr param; - if (!function.getInParam(arrayIdx, param)) { - return -1; - } - std::shared_ptr paramArr = std::dynamic_pointer_cast(param); - if (paramArr == nullptr) { - return -1; // not an array - } - return paramArr->addParam(childParam); -} - int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint8_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 8, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint16_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 16, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint32_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 32, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint64_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 64, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayUintParam(arrayIdx, 256, bytesData); } int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayUintParam(arrayIdx, bits, bytesData); } int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int8_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 8, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int16_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 16, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int32_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 32, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int64_t val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 64, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); - return addInArrayParam(function, arrayIdx, std::make_shared(val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayIntParam(arrayIdx, 256, bytesData); } int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = EthAbi::ValueEncoder::int256FromUint256(load(*static_cast(val))); - return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayIntParam(arrayIdx, bits, bytesData); } int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, bool val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_boolean(); + + EthereumAbi::Proto::Token token; + token.set_boolean(val); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWString *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - assert(val != nullptr); - return addInArrayParam(function, arrayIdx, std::make_shared(TWStringUTF8Bytes(val))); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_string_param(); + + EthereumAbi::Proto::Token token; + token.set_string_value(TWStringUTF8Bytes(val)); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - assert(val != nullptr); - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(data)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_address(); + + EthereumAbi::Proto::Token token; + const Data& addressData = *(reinterpret_cast(val)); + bool prefixed = true; + auto addressStr = hex(addressData, prefixed); + token.set_address(addressStr); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(data)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_byte_array(); + + EthereumAbi::Proto::Token token; + const Data& bytesData = *(reinterpret_cast(val)); + token.set_byte_array(bytesData.data(), bytesData.size()); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, size_t count, TWData *_Nonnull val) { assert(func_in != nullptr); - EthAbi::Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(count, data)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_byte_array_fix()->set_size(static_cast(count)); + + EthereumAbi::Proto::Token token; + Data bytesData = *(reinterpret_cast(val)); + bytesData.resize(count); + token.set_byte_array_fix(bytesData.data(), bytesData.size()); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } diff --git a/src/interface/TWEthereumRlp.cpp b/src/interface/TWEthereumRlp.cpp new file mode 100644 index 00000000000..4c405d54882 --- /dev/null +++ b/src/interface/TWEthereumRlp.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "rust/Wrapper.h" +#include "Data.h" + +using namespace TW; + +TWData* _Nonnull TWEthereumRlpEncode(enum TWCoinType coin, TWData* _Nonnull input) { + const Data& dataIn = *(reinterpret_cast(input)); + + const Rust::TWDataWrapper dataInPtr(dataIn); + Rust::TWDataWrapper dataOutPtr = Rust::tw_ethereum_rlp_encode(static_cast(coin), dataInPtr.get()); + + auto dataOut = dataOutPtr.toDataOrDefault(); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index 701541096f3..72b28ea7714 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -90,15 +90,6 @@ struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivate return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeCURVE25519); } -TWData *_Nullable TWPrivateKeyGetSharedKey(const struct TWPrivateKey *_Nonnull pk, const struct TWPublicKey *_Nonnull publicKey, enum TWCurve curve) { - auto result = pk->impl.getSharedKey(publicKey->impl, curve); - if (result.empty()) { - return nullptr; - } else { - return TWDataCreateWithBytes(result.data(), result.size()); - } -} - TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { const auto& d = *reinterpret_cast(digest); auto result = pk->impl.sign(d, curve); diff --git a/src/interface/TWString.cpp b/src/interface/TWString.cpp index ccd0f267dfd..ac118e581a0 100644 --- a/src/interface/TWString.cpp +++ b/src/interface/TWString.cpp @@ -6,6 +6,7 @@ #include +#include #include TWString *_Nonnull TWStringCreateWithUTF8Bytes(const char *_Nonnull bytes) { @@ -34,8 +35,12 @@ const char *_Nonnull TWStringUTF8Bytes(TWString *_Nonnull string) { } void TWStringDelete(TWString *_Nonnull string) { - auto* s = reinterpret_cast(string); - delete s; + auto *sConst = reinterpret_cast(string); + // `const_cast` is safe here despite that the pointer to the string is const + // but `std::string` is not a constant value. + auto *s = const_cast(sConst); + memzero(s->data(), s->size()); + delete sConst; } bool TWStringEqual(TWString *_Nonnull lhs, TWString *_Nonnull rhs) { diff --git a/src/interface/TWWebAuthn.cpp b/src/interface/TWWebAuthn.cpp index ce7cd514f36..ad1829e0c14 100644 --- a/src/interface/TWWebAuthn.cpp +++ b/src/interface/TWWebAuthn.cpp @@ -30,4 +30,4 @@ TWData *_Nonnull TWWebAuthnReconstructOriginalMessage(TWData* _Nonnull authentic const auto& clientDataJSONConverted = *reinterpret_cast(clientDataJSON); const auto& message = TW::WebAuthn::reconstructSignedMessage(authenticatorDataConverted, clientDataJSONConverted); return TWDataCreateWithData(&message); -} \ No newline at end of file +} diff --git a/src/proto/BitcoinV2.proto b/src/proto/BitcoinV2.proto new file mode 100644 index 00000000000..c55810557bf --- /dev/null +++ b/src/proto/BitcoinV2.proto @@ -0,0 +1,430 @@ +syntax = "proto3"; + +package TW.BitcoinV2.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Utxo.proto"; + +enum Error { + OK = 0; + // `tx_utxo` related errors. + Error_utxo_invalid_leaf_hash = 2; + Error_utxo_invalid_sighash_type = 3; + Error_utxo_invalid_lock_time = 4; + Error_utxo_invalid_txid = 5; + Error_utxo_sighash_failed = 6; + Error_utxo_missing_sighash_method = 7; + Error_utxo_failed_encoding = 8; + Error_utxo_insufficient_inputs = 9; + Error_utxo_missing_change_script_pubkey = 10; + // `tw_bitcoin` related errors. + Error_zero_sequence_not_enabled = 11; + Error_unmatched_input_signature_count = 12; + Error_missing_input_builder = 13; + Error_missing_output_builder = 14; + Error_missing_recipient = 15; + Error_missing_inscription = 41; + Error_missing_tagged_output = 42; + Error_legacy_p2tr_invalid_variant = 16; + Error_legacy_no_spending_script_provided = 17; + Error_legacy_expected_redeem_script = 18; + Error_legacy_outpoint_not_set = 19; + Error_legacy_no_private_key = 36; + Error_legacy_no_plan_provided = 37; + Error_invalid_private_key = 20; + Error_invalid_public_key = 21; + Error_invalid_sighash = 22; + Error_invalid_witness_pubkey_hash = 23; + Error_invalid_brc20_ticker = 24; + Error_invalid_ecdsa_signature = 25; + Error_invalid_schnorr_signature = 26; + Error_invalid_control_block = 27; + Error_invalid_pubkey_hash = 28; + Error_invalid_taproot_root = 29; + Error_invalid_redeem_script = 30; + Error_invalid_wpkh_script_code = 1; + Error_invalid_witness_redeem_script_hash = 31; + Error_invalid_witness_encoding = 39; + Error_invalid_taproot_tweaked_pubkey = 32; + Error_invalid_change_output = 33; + Error_unsupported_address_recipient = 34; + Error_bad_address_recipient = 35; + Error_ordinal_mime_type_too_large = 38; + Error_ordinal_payload_too_large = 40; +} + +message SigningInput { + // (optional) The protocol version, is currently expected to be 1 or 2. + // Version 2 by default. + int32 version = 1; + + // Only required if the `sign` method is called. + bytes private_key = 2; + + // (optional) Block height or timestamp indicating at what point transactions can be + // included in a block. None by default (zero value). + Utxo.Proto.LockTime lock_time = 3; + + // The inputs to spend. + repeated Input inputs = 5; + + // The output of the transaction. Note that the change output is specified + // in the `change_output` field. + repeated Output outputs = 6; + + // How the inputs should be selected. + Utxo.Proto.InputSelector input_selector = 7; + + // (optional) The amount of satoshis per vbyte ("satVb"), used for fee calculation. + uint64 fee_per_vb = 8; + + // The change output to be added (return to sender). + // The `value` can be left at 0. + Output change_output = 9; + + // Explicility disable change output creation. + bool disable_change_output = 10; + + bool dangerous_use_fixed_schnorr_rng = 11; +} + +message Input { + // Use an individual private key for this input. Only required if the `sign` + // method is called. + bytes private_key = 1; + + // The referenced transaction ID in REVERSED order. + bytes txid = 2; + + // The position in the previous transactions output that this input + // references. + uint32 vout = 3; + + // The sequence number, used for timelocks, replace-by-fee, etc. Normally + // this number is simply 4294967295 (0xFFFFFFFF) . + uint32 sequence = 4; + + // If the sequence is a zero value, this field must be set to `true`. + bool sequence_enable_zero = 5; + + // The amount of satoshis of this input. Required for producing + // Segwit/Taproot transactions. + uint64 value = 6; + + // The sighash type, normally `SighashType::UseDefault` (All). + Utxo.Proto.SighashType sighash_type = 7; + + // The reciepient of this input (the spender) + oneof to_recipient { + // Construct input with a buildler pattern. + InputBuilder builder = 8; + // Construct input by providing raw spending information directly. + InputScriptWitness custom_script = 9; + } + + message InputBuilder { + oneof variant { + // Pay-to-Script-Hash, specify the redeem script. + bytes p2sh = 1; + // Pay-to-Public-Key-Hash, specify the public key. + bytes p2pkh = 2; + // Pay-to-Witness-Script-Hash, specify the redeem script. + bytes p2wsh = 3; + // Pay-to-Public-Key-Hash, specify the public key. + bytes p2wpkh = 6; + // Pay-to-Taproot-key-path (balance transfers). + InputTaprootKeyPath p2tr_key_path = 7; + // Pay-to-Taproot-script-path (complex transfers). + InputTaprootScriptPath p2tr_script_path = 8; + // Create a BRC20 inscription. + InputBrc20Inscription brc20_inscribe = 9; + // Create an Ordinal (NFT) inscriptiohn. + InputOrdinalInscription ordinal_inscribe = 10; + } + } + + message InputScriptWitness { + // The spending condition of this input. + bytes script_pubkey = 1; + // The claiming script for this input (non-Segwit/non-Taproot) + bytes script_sig = 2; + // The claiming script for this input (Segwit/Taproot) + repeated bytes witness_items = 3; + // The signing method. + Utxo.Proto.SigningMethod signing_method = 5; + } + + message InputTaprootKeyPath { + // Whether only one prevout should be used to calculate the Sighash. + // Normally this is `false`. + bool one_prevout = 1; + // The recipient. + bytes public_key = 2; + } + + message InputTaprootScriptPath { + // Whether only one prevout should be used to calculate the Sighash. + // Normally this is `false`. + bool one_prevout = 1; + // The payload of the Taproot transaction. + bytes payload = 2; + // The control block of the Taproot transaction required for claiming. + bytes control_block = 3; + } + + message InputOrdinalInscription { + // Whether only one prevout should be used to calculate the Sighash. + // Normally this is `false`. + bool one_prevout = 1; + // The recipient of the inscription, usually the sender. + bytes inscribe_to = 2; + // The MIME type of the inscription, such as `image/png`, etc. + string mime_type = 3; + // The actual inscription content. + bytes payload = 4; + } + + message InputBrc20Inscription { + bool one_prevout = 1; + // The recipient of the inscription, usually the sender. + bytes inscribe_to = 2; + // The ticker of the BRC20 inscription. + string ticker = 3; + // The BRC20 token transfer amount. + uint64 transfer_amount = 4; + } +} + +message Output { + // The amount of satoshis to spend. + uint64 value = 1; + + oneof to_recipient { + // Construct output with builder pattern. + OutputBuilder builder = 2; + // Construct output by providing the scriptPubkey directly. + bytes custom_script_pubkey = 3; + // Derive the expected output from the provided address. + string from_address = 4; + } + + message OutputBuilder { + oneof variant { + // Pay-to-Script-Hash, specify the hash. + OutputRedeemScriptOrHash p2sh = 1; + // Pay-to-Public-Key-Hash + ToPublicKeyOrHash p2pkh = 2; + // Pay-to-Witness-Script-Hash, specify the hash. + OutputRedeemScriptOrHash p2wsh = 3; + // Pay-to-Public-Key-Hash + ToPublicKeyOrHash p2wpkh = 4; + // Pay-to-Taproot-key-path (balance transfers), specify the public key. + bytes p2tr_key_path = 5; + // Pay-to-Taproot-script-path (complex transfers) + OutputTaprootScriptPath p2tr_script_path = 6; + bytes p2tr_dangerous_assume_tweaked = 7; + OutputBrc20Inscription brc20_inscribe = 8; + OutputOrdinalInscription ordinal_inscribe = 9; + } + } + + message OutputRedeemScriptOrHash { + oneof variant { + bytes redeem_script = 1; + bytes hash = 2; + } + } + + message OutputTaprootScriptPath { + // The internal key, usually the public key of the recipient. + bytes internal_key = 1; + // The merkle root of the Taproot script(s), required to compute the sighash. + bytes merkle_root = 2; + } + + message OutputOrdinalInscription { + // The recipient of the inscription, usually the sender. + bytes inscribe_to = 1; + // The MIME type of the inscription, such as `image/png`, etc. + string mime_type = 2; + // The actual inscription content. + bytes payload = 3; + } + + message OutputBrc20Inscription { + // The recipient of the inscription, usually the sender. + bytes inscribe_to = 1; + // The ticker of the BRC20 inscription. + string ticker = 2; + // The BRC20 token transfer amount. + uint64 transfer_amount = 3; + } +} + +message ToPublicKeyOrHash { + oneof to_address { + bytes pubkey = 1; + bytes hash = 2; + } +} + +message PreSigningOutput { + // A possible error, `OK` if none. + Error error = 1; + + string error_message = 2; + + // The transaction ID in NON-reversed order. Note that this must be reversed + // when referencing in future transactions. + bytes txid = 3; + + /// The sighashes to be signed; ECDSA for legacy and Segwit, Schnorr for Taproot. + repeated Utxo.Proto.Sighash sighashes = 4; + + // The raw inputs. + repeated Utxo.Proto.TxIn utxo_inputs = 5; + + // The raw outputs. + repeated TxOut utxo_outputs = 6; + + // The estimated weight of the transaction. + uint64 weight_estimate = 7; + + // The estimated fees of the transaction in satoshis. + uint64 fee_estimate = 8; + + // The output of a transaction. + message TxOut { + // The value of the output (in satoshis). + uint64 value = 1; + // The spending condition of the output. + bytes script_pubkey = 2; + // The payload of the Taproot script. + bytes taproot_payload = 3; + // The optional control block for a Taproot output (P2TR script-path). + bytes control_block = 4; + } +} + +message SigningOutput { + // A possible error, `OK` if none. + Error error = 1; + + string error_message = 2; + + Transaction transaction = 3; + + // The encoded transaction that submitted to the network. + bytes encoded = 4; + + // The transaction ID in NON-reversed order. Note that this must be reversed + // when referencing in future transactions. + bytes txid = 5; + + // The total and final weight of the transaction. + uint64 weight = 6; + + // The total and final fee of the transaction in satoshis. + uint64 fee = 7; +} + +message Transaction { + // The protocol version, is currently expected to be 1 or 2 (BIP68) + int32 version = 1; + + // Block height or timestamp indicating at what point transactions can be + // included in a block. None by default (zero value). + Utxo.Proto.LockTime lock_time = 2; + + // The transaction inputs. + repeated TransactionInput inputs = 3; + + // The transaction outputs. + repeated TransactionOutput outputs = 4; +} + +message TransactionInput { + // The referenced transaction ID in REVERSED order. + bytes txid = 1; + + // The position in the previous transactions output that this input + // references. + uint32 vout = 3; + + // The sequence number, used for timelocks, replace-by-fee, etc. Normally + // this number is simply 4294967295 (0xFFFFFFFF) . + uint32 sequence = 4; + + // The script for claiming the input (non-Segwit/non-Taproot). + bytes script_sig = 5; + + // The script for claiming the input (Segit/Taproot). + repeated bytes witness_items = 6; +} + +message TransactionOutput { + // The condition for claiming the output. + bytes script_pubkey = 1; + + // The amount of satoshis to spend. + uint64 value = 2; + + // In case of P2TR script-path (complex scripts), this is the payload that + // must later be revealed and is required for claiming. + bytes taproot_payload = 3; + + // In case of P2TR script-path (complex scripts), this is the control block + // required for claiming. + bytes control_block = 4; +} + +message ComposePlan { + oneof compose { + ComposeBrc20Plan brc20 = 1; + } + + message ComposeBrc20Plan { + // (optional) Sets the private key in the composed transactions. Can + // also be added manually. + bytes private_key = 1; + + // The inputs for the commit transaction. + repeated Input inputs = 2; + + // How the inputs for the commit transaction should be selected. + Utxo.Proto.InputSelector input_selector = 3; + + // The tagged output of the inscription. Commonly a P2WPKH transaction + // with the value of 546 (dust limit). + Output tagged_output = 4; + + // The BRC20 payload to inscribe. + Input.InputBrc20Inscription inscription = 5; + + // The amount of satoshis per vbyte ("satVb"), used for fee calculation. + uint64 fee_per_vb = 6; + + // The change output to be added (return to sender). + // The `value` can be left at 0. + Output change_output = 7; + + // Explicility disable change output creation. + bool disable_change_output = 8; + } +} + +message TransactionPlan { + // A possible error, `OK` if none. + Error error = 1; + + string error_message = 2; + + oneof plan { + Brc20Plan brc20 = 3; + } + + message Brc20Plan { + SigningInput commit = 1; + SigningInput reveal = 2; + } +} diff --git a/src/proto/Common.proto b/src/proto/Common.proto index cedf89feee4..0a9944bff49 100644 --- a/src/proto/Common.proto +++ b/src/proto/Common.proto @@ -67,4 +67,6 @@ enum SigningError { Error_invalid_params = 22; // Invalid input token amount Error_invalid_requested_token_amount = 23; + // Operation not supported for the chain. + Error_not_supported = 24; } diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index 264e811a8d9..323ac93bb08 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -117,14 +117,6 @@ message Message { string type_prefix = 3; } - message ExecuteContract { - string sender = 1; - string contract = 2; - string execute_msg = 3; - repeated Amount coins = 4; - string type_prefix = 5; - } - // transfer within wasm/MsgExecuteContract, used by Terra Classic message WasmTerraExecuteContractTransfer { // sender address @@ -224,6 +216,7 @@ message Message { } // execute within wasm/MsgExecuteContract + // TODO replaces `ExecuteContract`. message WasmExecuteContractGeneric { // sender address string sender_address = 1; @@ -353,7 +346,6 @@ message Message { WasmTerraExecuteContractTransfer wasm_terra_execute_contract_transfer_message = 8; WasmTerraExecuteContractSend wasm_terra_execute_contract_send_message = 9; THORChainSend thorchain_send_message = 10; - ExecuteContract execute_contract_message = 11; WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 12; WasmExecuteContractTransfer wasm_execute_contract_transfer_message = 13; WasmExecuteContractSend wasm_execute_contract_send_message = 14; diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 504e229fa53..52255fc6b0b 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -197,3 +197,59 @@ message SigningOutput { // Encoded transaction bytes. bytes pre_hash = 8; } + +enum MessageType { + // Sign a message following EIP-191. + MessageType_legacy = 0; + // Sign a message following EIP-191 with EIP-155 replay attack protection. + MessageType_eip155 = 1; + // Sign a typed message EIP-712 V4. + MessageType_typed = 2; + // Sign a typed message EIP-712 V4 with EIP-155 replay attack protection. + MessageType_typed_eip155 = 3; + // Sign a message with Immutable X msg type. + MessageType_immutable_x = 4; +} + +message MaybeChainId { + // Chain ID. + uint64 chain_id = 3; +} + +message MessageSigningInput { + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // Message to sign. Either a regular message or a typed data structured message in JSON format. + // Message type should be declared at `message_type`. + string message = 2; + + // Optional. Used in replay protection and to check Typed Structured Data input. + // Eg. should be set if `message_type` is `MessageType_eip155`, or MessageType_typed, or `MessageType_typed_eip155`. + MaybeChainId chain_id = 3; + + // Message type. + MessageType message_type = 4; +} + +message MessageSigningOutput { + // The signature, Hex-encoded. + string signature = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; +} + +message MessageVerifyingInput { + // The message signed. + string message = 1; + + // Public key that will verify and recover the message from the signature. + bytes public_key = 2; + + // The signature, Hex-encoded. + string signature = 3; +} diff --git a/src/proto/EthereumAbi.proto b/src/proto/EthereumAbi.proto new file mode 100644 index 00000000000..8ee2c0692a1 --- /dev/null +++ b/src/proto/EthereumAbi.proto @@ -0,0 +1,318 @@ +syntax = "proto3"; + +package TW.EthereumAbi.Proto; +option java_package = "wallet.core.jni.proto"; + +enum AbiError { + // This is the OK case, with value=0 + OK = 0; + + // Internal error. + Error_internal = 1; + + // Unexpected function signature or ABI mismatch. + Error_abi_mismatch = 2; + // Invalid ABI. + Error_invalid_abi = 3; + // Invalid parameter type. + Error_invalid_param_type = 4; + // Invalid address value. + Error_invalid_address_value = 5; + // Invalid UInt value. + Error_invalid_uint_value = 6; + // Missing parameter type. + Error_missing_param_type = 7; + // Missing parameter value. + Error_missing_param_value = 8; + // Invalid encoded data. + Error_decoding_data = 9; + // Invalid empty type. + // For example, bytes0, address[0]. + Error_empty_type = 10; +} + +// ABI type parameters excluding values. + +// Indicates a boolean type. +message BoolType {} + +// Generic number type for all bit sizes, like UInt24, 40, 48, ... 248. +message NumberNType { + // The number of bits of an integer. + uint32 bits = 1; +} + +// Indicates a string type. +message StringType {} + +// Indicates an address type. +message AddressType {} + +// Indicates an array type with an inner `element_type`. +message ArrayType { + // The type of array elements. + ParamType element_type = 1; +} + +// Indicates a fixed-size array type with an inner `element_type`. +message FixedArrayType { + // The fixed-size of the array. + uint64 size = 1; + // The type of array elements. + ParamType element_type = 2; +} + +// Indicates a byte array type. +message ByteArrayType {} + +// Indicates a fixed-size byte array type. +message ByteArrayFixType { + // The fixed-size of the array. + uint64 size = 1; +} + +// Indicates a tuple with inner type parameters. +message TupleType { + // Tuple named parameters. + repeated Param params = 1; +} + +// Named parameter with type. +message Param { + // Name of the parameter. + string name = 1; + + // Type of the parameter. + ParamType param = 2; +} + +message ParamType { + oneof param { + BoolType boolean = 1; + NumberNType number_int = 2; + NumberNType number_uint = 3; + // Nested values. Gap in field numbering is intentional. + StringType string_param = 7; + AddressType address = 8; + ByteArrayType byte_array = 9; + ByteArrayFixType byte_array_fix = 10; + + // Nested values. Gap in field numbering is intentional. + ArrayType array = 14; + FixedArrayType fixed_array = 15; + + // Nested values. Gap in field numbering is intentional. + TupleType tuple = 19; + } +} + +// ABI parameters including values. + +// Generic number parameter for all other bit sizes, like UInt24, 40, 48, ... 248. +message NumberNParam { + // Count of bits of the number. + // 0 < bits <= 256, bits % 8 == 0 + uint32 bits = 1; + + // Serialized big endian. + bytes value = 2; +} + +// A byte array of arbitrary size. +message ArrayParam { + // The type of array elements. + ParamType element_type = 1; + + // Array elements. + repeated Token elements = 2; +} + +// A tuple with various parameters similar to a structure. +message TupleParam { + // Tokens (values) of the tuple parameters. + repeated Token params = 1; +} + +// A value of an ABI parameter. +message Token { + // Optional. Name of a corresponding parameter. + string name = 1; + + oneof token { + // Integer values. + bool boolean = 2; + NumberNParam number_int = 3; + NumberNParam number_uint = 4; + + // Simple values. Gap in field numbering is intentional. + string string_value = 7; + string address = 8; + bytes byte_array = 9; + bytes byte_array_fix = 10; + + // Nested values. Gap in field numbering is intentional. + ArrayParam array = 14; + ArrayParam fixed_array = 15; + + // Nested values. Gap in field numbering is intentional. + TupleParam tuple = 19; + } +} + +//// TWEthereumAbiDecodeContractCall + +// Decode a contract call (function input) according to the given ABI json. +message ContractCallDecodingInput { + // An encoded smart contract call with a prefixed function signature (4 bytes). + bytes encoded = 1; + + // A smart contract ABI in JSON. + // Each ABI function must be mapped to a short signature. + // Expected to be a set of functions mapped to corresponding short signatures. + // Example: + // ``` + // { + // "1896f70a": { + // "name": "setResolver", + // "inputs": [...], + // ... + // }, + // "ac9650d8": { + // "name": "multicall", + // "inputs": [...], + // ... + // } + // } + // ``` + string smart_contract_abi_json = 2; +} + +message ContractCallDecodingOutput { + // Human readable json format, according to the input `ContractCallDecodingInput::smart_contract_abi_json`. + string decoded_json = 1; + + // Decoded parameters. + repeated Token tokens = 2; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 3; + + // error code description + string error_message = 4; +} + +//// TWEthereumAbiDecodeParams + +// A set of ABI type parameters. +message AbiParams { + // ABI type parameters. + repeated Param params = 1; +} + +// Decode a function input or output data according to the given ABI json. +message ParamsDecodingInput { + // An encoded ABI. + bytes encoded = 1; + + oneof abi { + // A set of ABI parameters in JSON. + // Expected to be a JSON array at the entry level. + // Example: + // ``` + // [ + // { + // "name": "_to', + // "type": "address" + // }, + // { + // "name": "_value", + // "type": "uint256" + // } + // ] + // ``` + string abi_json = 2; + + // A set of ABI type parameters. + AbiParams abi_params = 3; + } +} + +message ParamsDecodingOutput { + // Decoded parameters. + repeated Token tokens = 1; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 2; + + // error code description + string error_message = 3; +} + +//// TWEthereumAbiDecodeValue + +// Decode an Eth ABI value. +message ValueDecodingInput { + // An encoded value to be decoded. + bytes encoded = 1; + + // A type of the parameter. + // Example: "bytes[32]". + // Please note `tuple` is not supported. + string param_type = 2; +} + +message ValueDecodingOutput { + // Decoded parameter. + Token token = 1; + + // Decoded parameter as a string. + string param_str = 2; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 3; + + // error code description + string error_message = 4; +} + +//// TWEthereumAbiEncodeFunction + +// Encode a function call to Eth ABI binary. +message FunctionEncodingInput { + // Function name. + string function_name = 1; + + // Parameters to be encoded. + repeated Token tokens = 2; +} + +message FunctionEncodingOutput { + // The function type signature. + // Example: "baz(int32,uint256)" + string function_type = 1; + + // An encoded smart contract call with a prefixed function signature (4 bytes). + bytes encoded = 2; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 3; + + // error code description + string error_message = 4; +} + +//// TWEthereumAbiFunctionGetType + +// Return the function type signature, of the form "baz(int32,uint256)". +message FunctionGetTypeInput { + // Function signature. Includes function inputs if they are. + // Examples: + // - `functionName()` + // - `functionName()` + // - `functionName(bool)` + // - `functionName(uint256,bytes32)` + string function_name = 1; + + // A set of ABI type parameters. + repeated Param inputs = 2; +} diff --git a/src/proto/EthereumRlp.proto b/src/proto/EthereumRlp.proto new file mode 100644 index 00000000000..0655ff37064 --- /dev/null +++ b/src/proto/EthereumRlp.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package TW.EthereumRlp.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// List of elements. +message RlpList { + repeated RlpItem items = 1; +} + +// RLP item. +message RlpItem { + oneof item { + // A string to be encoded. + string string_item = 1; + // A U64 number to be encoded. + uint64 number_u64 = 2; + // A U256 number to be encoded. + bytes number_u256 = 3; + // An address to be encoded. + string address = 4; + // A data to be encoded. + bytes data = 5; + // A list of items to be encoded. + RlpList list = 6; + // An RLP encoded item to be appended as it is. + bytes raw_encoded = 7; + } +} + +// RLP encoding input. +message EncodingInput { + // An item or a list to encode. + RlpItem item = 1; +} + +/// RLP encoding output. +message EncodingOutput { + // An item RLP encoded. + bytes encoded = 1; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 2; + + // Error code description. + string error_message = 3; +} diff --git a/src/proto/InternetComputer.proto b/src/proto/InternetComputer.proto new file mode 100644 index 00000000000..00cd513baf0 --- /dev/null +++ b/src/proto/InternetComputer.proto @@ -0,0 +1,46 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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.InternetComputer.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Internet Computer Transactions +message Transaction { + + // ICP ledger transfer arguments + message Transfer { + string to_account_identifier = 1; + uint64 amount = 2; + uint64 memo = 3; + uint64 current_timestamp_nanos = 4; + } + + // Payload transfer + oneof transaction_oneof { + Transfer transfer = 1; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + Transaction transaction = 2; +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + // NOTE: Before sending to the Rosetta node, this value should be hex-encoded before using with the JSON structure. + bytes signed_transaction = 1; + + Common.Proto.SigningError error = 2; + + string error_message = 3; +} \ No newline at end of file diff --git a/src/proto/Ripple.proto b/src/proto/Ripple.proto index 8423e56fc97..d32b58e25c9 100644 --- a/src/proto/Ripple.proto +++ b/src/proto/Ripple.proto @@ -40,6 +40,52 @@ message OperationPayment { int64 destination_tag = 4; } +// https://xrpl.org/escrowcreate.html +message OperationEscrowCreate { + // Escrow amount + int64 amount = 1; + + // Beneficiary account + string destination = 2; + + // Destination Tag + int64 destination_tag = 3; + + // Escrow expire time + int64 cancel_after = 4; + + // Escrow release time + int64 finish_after = 5; + + // Crypto condition + // https://datatracker.ietf.org/doc/html/draft-thomas-crypto-conditions-02#section-8.1 + string condition = 6; +} + +// https://xrpl.org/escrowcancel.html +message OperationEscrowCancel { + // Funding account + string owner = 1; + + // Escrow transaction sequence + int32 offer_sequence = 2; +} + +// https://xrpl.org/escrowfinish.html +message OperationEscrowFinish { + // Funding account + string owner = 1; + + // Escrow transaction sequence + int32 offer_sequence = 2; + + // Crypto condition + string condition = 3; + + // Fulfillment matching condition + string fulfillment = 4; +} + // https://xrpl.org/nftokenburn.html message OperationNFTokenBurn { // Hash256 NFTokenId @@ -99,6 +145,12 @@ message SigningInput { OperationNFTokenAcceptOffer op_nftoken_accept_offer = 11; OperationNFTokenCancelOffer op_nftoken_cancel_offer = 12; + + OperationEscrowCreate op_escrow_create = 16; + + OperationEscrowCancel op_escrow_cancel = 17; + + OperationEscrowFinish op_escrow_finish = 18; } // Only used by tss chain-integration. diff --git a/src/proto/THORChainSwap.proto b/src/proto/THORChainSwap.proto index 382c2e25f9b..69b6d257e16 100644 --- a/src/proto/THORChainSwap.proto +++ b/src/proto/THORChainSwap.proto @@ -19,6 +19,7 @@ enum Chain { LTC = 6; ATOM = 7; AVAX = 8; + BSC = 9; } // Predefined error codes diff --git a/src/proto/Utxo.proto b/src/proto/Utxo.proto new file mode 100644 index 00000000000..ada5c6bad9a --- /dev/null +++ b/src/proto/Utxo.proto @@ -0,0 +1,221 @@ +syntax = "proto3"; + +package TW.Utxo.Proto; +option java_package = "wallet.core.jni.proto"; + +enum Error { + OK = 0; + Error_invalid_leaf_hash = 1; + Error_invalid_sighash_type = 2; + Error_invalid_lock_time = 3; + Error_invalid_txid = 4; + Error_sighash_failed = 5; + Error_missing_sighash_method = 6; + Error_failed_encoding = 7; + Error_insufficient_inputs = 8; + Error_missing_change_script_pubkey = 9; +} + +message SigningInput { + // The protocol version. + int32 version = 1; + + // Block height or timestamp indicating at what point transactions can be + // included in a block. + LockTime lock_time = 2; + + // The inputs of the transaction. + repeated TxIn inputs = 3; + + // The outputs of the transaction. + repeated TxOut outputs = 4; + + // How inputs should be selected. + InputSelector input_selector = 5; + + // The base unit per weight. In the case of Bitcoin, that would refer to + // satoshis by vbyte ("satVb"). + uint64 weight_base = 6; + + // The change output where to send left-over funds to (usually the sender). + bytes change_script_pubkey = 7; + + // Explicility disable change output creation. + bool disable_change_output = 8; +} + +enum InputSelector { + // Use all the inputs provided in the given order. + UseAll = 0; + // Automatically select enough inputs in the given order to cover the + // outputs of the transaction. + SelectInOrder = 1; + // Automatically select enough inputs in an ascending order to cover the + // outputs of the transaction. + SelectAscending = 2; +} + +message LockTime { + oneof variant { + uint32 blocks = 1; + uint32 seconds = 2; + } +} + +message TxIn { + // The referenced transaction ID in REVERSED order. + bytes txid = 1; + + // The position in the previous transactions output that this input + // references. + uint32 vout = 2; + + // The value of this input, such as satoshis. Required for producing + // Segwit/Taproot transactions. + uint64 value = 3; + + // The sequence number, used for timelocks, replace-by-fee, etc. Normally + // this number is simply 4294967295 (0xFFFFFFFF) . + uint32 sequence = 4; + + // The spending condition of the referenced output. + bytes script_pubkey = 7; + + // The sighash type, normally `SighashType::UseDefault` (All). + SighashType sighash_type = 8; + + // The signing method. + SigningMethod signing_method = 9; + + // The estimated weight of the input, required for estimating fees. + uint64 weight_estimate = 10; + + // If this input is a Taproot script-path (complex transaction), then this + // leaf hash is required in order to compute the sighash. + bytes leaf_hash = 11; +} + +enum SigningMethod { + // Used for P2SH and P2PKH + Legacy = 0; + // Used for P2WSH and P2WPKH + Segwit = 1; + // Used for P2TR key-path and P2TR script-paty + TaprootAll = 2; + // Used for P2TR key-path and P2TR script-paty if only one prevout should be + // used to calculate the Sighash. Normally this is not used. + TaprootOnePrevout = 3; +} + +enum SighashType { + // Use default (All) + UseDefault = 0; // 0x00 + // Sign all outputs (default). + All = 1; // 0x01 + // Sign no outputs, anyone can choose the destination. + None = 2; // 0x02 + // Sign the output whose index matches this inputs index. + Single = 3; // 0x03 + //Sign all outputs but only this input. + AllPlusAnyoneCanPay = 129; // 0x81 + // Sign no outputs and only this input. + NonePlusAnyoneCanPay = 130; // 0x82 + // Sign one output and only this input. + SinglePlusAnyoneCanPay = 131; // 0x83 +} + +// The output of a transaction. +message TxOut { + // The value of the output. + uint64 value = 1; + // The spending condition of the output. + bytes script_pubkey = 2; +} + +message PreSigningOutput { + // error code, 0 is ok, other codes will be treated as errors + Error error = 1; + + // The transaction ID in NON-reversed order. Note that this must be reversed + // when referencing in future transactions. + bytes txid = 2; + + /// Sighashes to be signed; ECDSA for legacy and Segwit, Schnorr for Taproot. + repeated Sighash sighashes = 3; + + // The raw inputs. + repeated TxIn inputs = 4; + + // The raw outputs. + repeated TxOut outputs = 5; + + // The estimated weight of the transaction. + uint64 weight_estimate = 6; + + // The estimated fee of the transaction denominated in the base unit (such + // as satoshis). + uint64 fee_estimate = 7; +} + +message Sighash { + // The sighash to be signed. + bytes sighash = 1; + // The used signing method for this sighash. + SigningMethod signing_method = 2; + // The used sighash type for this sighash. + SighashType sighash_type = 3; +} + +message PreSerialization { + // The protocol version, is currently expected to be 1 or 2 (BIP68) + int32 version = 1; + + // Block height or timestamp indicating at what point transactions can be + // included in a block. + LockTime lock_time = 2; + + // The transaction inputs containing the serialized claim scripts. + repeated TxInClaim inputs = 3; + + // The transaction outputs. + repeated TxOut outputs = 4; + + // The base unit per weight. In the case of Bitcoin, that would refer to + // satoshis ("satVb"). + uint64 weight_base = 5; +} + +message TxInClaim { + // The referenced transaction hash. + bytes txid = 1; + + // The index of the referenced output. + uint32 vout = 2; + + // The sequence number (TODO). + uint32 sequence = 3; + + // The script used for claiming an input. + bytes script_sig = 4; + + // The script used for claiming an input. + repeated bytes witness_items = 5; +} + +message SerializedTransaction { + // error code, 0 is ok, other codes will be treated as errors + Error error = 1; + + // The encoded transaction, ready to be submitted to the network. + bytes encoded = 2; + + // The transaction ID. + bytes txid = 3; + + // The total and final weight of the transaction. + uint64 weight = 4; + + // The total and final fee of the transaction denominated in the base unit + // (such as satoshis). + uint64 fee = 5; +} diff --git a/src/rust/RustCoinEntry.cpp b/src/rust/RustCoinEntry.cpp new file mode 100644 index 00000000000..497edc57644 --- /dev/null +++ b/src/rust/RustCoinEntry.cpp @@ -0,0 +1,114 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "RustCoinEntry.h" +#include "Wrapper.h" + +namespace TW::Rust { + +bool RustCoinEntry::validateAddress(TWCoinType coin, const std::string &address, const PrefixVariant &addressPrefix) const { + Rust::TWStringWrapper addressStr = address; + + if (std::holds_alternative(addressPrefix)) { + return Rust::tw_any_address_is_valid(addressStr.get(), static_cast(coin)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + return Rust::tw_any_address_is_valid_bech32(addressStr.get(), static_cast(coin), hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } +} + +std::string RustCoinEntry::normalizeAddress(TWCoinType coin, const std::string& address) const { + Rust::TWStringWrapper addressStr = address; + + // `CoinEntry::normalizeAddress` is used when a `TWAnyAddress` has been created already, therefore validated. + auto anyAddress = Rust::wrapTWAnyAddress( + Rust::tw_any_address_create_with_string_unchecked(addressStr.get(), static_cast(coin))); + if (!anyAddress) { + return {}; + } + + Rust::TWStringWrapper normalized = Rust::tw_any_address_description(anyAddress.get()); + return normalized.toStringOrDefault(); +} + +std::string RustCoinEntry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + auto *twPublicKeyRaw = Rust::tw_public_key_create_with_data(publicKey.bytes.data(), + publicKey.bytes.size(), + static_cast(publicKey.type)); + auto twPublicKey = Rust::wrapTWPublicKey(twPublicKeyRaw); + if (!twPublicKey) { + return {}; + } + + Rust::TWAnyAddress* anyAddressRaw = nullptr; + if (std::holds_alternative(addressPrefix)) { + anyAddressRaw = Rust::tw_any_address_create_with_public_key_derivation(twPublicKey.get(), + static_cast(coin), + static_cast(derivation)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + anyAddressRaw = Rust::tw_any_address_create_bech32_with_public_key(twPublicKey.get(), + static_cast(coin), + hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } + + auto anyAddress = Rust::wrapTWAnyAddress(anyAddressRaw); + if (!anyAddress) { + return {}; + } + + Rust::TWStringWrapper derivedAddress = Rust::tw_any_address_description(anyAddress.get()); + return derivedAddress.toStringOrDefault(); +} + +Data RustCoinEntry::addressToData(TWCoinType coin, const std::string& address) const { + Rust::TWStringWrapper addressStr = address; + + // `CoinEntry::normalizeAddress` is used when a `TWAnyAddress` has been created already, therefore validated. + auto anyAddress = Rust::wrapTWAnyAddress( + Rust::tw_any_address_create_with_string_unchecked(addressStr.get(), static_cast(coin))); + if (!anyAddress) { + return {}; + } + + Rust::TWDataWrapper data = Rust::tw_any_address_data(anyAddress.get()); + return data.toDataOrDefault(); +} + +void RustCoinEntry::sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const { + Rust::TWDataWrapper input = Rust::tw_data_create_with_bytes(dataIn.data(), dataIn.size()); + Rust::TWDataWrapper output = Rust::tw_any_signer_sign(input.get(), static_cast(coin)); + + dataOut = output.toDataOrDefault(); +} + +Data RustCoinEntry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + Rust::TWDataWrapper input = txInputData; + Rust::TWDataWrapper output = Rust::tw_transaction_compiler_pre_image_hashes(static_cast(coin), input.get()); + + return output.toDataOrDefault(); +} + +void RustCoinEntry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + Rust::TWDataWrapper input = txInputData; + + std::vector publicKeysData; + for (const auto& publicKey : publicKeys) { + publicKeysData.push_back(publicKey.bytes); + } + + Rust::TWDataVectorWrapper signaturesVec = signatures; + Rust::TWDataVectorWrapper publicKeysVec = publicKeysData; + + Rust::TWDataWrapper output = Rust::tw_transaction_compiler_compile(static_cast(coin), input.get(), signaturesVec.get(), publicKeysVec.get()); + dataOut = output.toDataOrDefault(); +} + +} // namespace TW::Rust diff --git a/src/rust/RustCoinEntry.h b/src/rust/RustCoinEntry.h new file mode 100644 index 00000000000..edcf926d9e2 --- /dev/null +++ b/src/rust/RustCoinEntry.h @@ -0,0 +1,26 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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::Rust { + +class RustCoinEntry : public CoinEntry { +public: + ~RustCoinEntry() noexcept override = default; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::Rust diff --git a/src/rust/Wrapper.h b/src/rust/Wrapper.h index 906b7343dce..3b4f9436f39 100644 --- a/src/rust/Wrapper.h +++ b/src/rust/Wrapper.h @@ -6,13 +6,102 @@ #pragma once +#include #include -#include "Data.h" -#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "../Data.h" +#include "bindgen/WalletCoreRSBindgen.h" namespace TW::Rust { +inline std::shared_ptr wrapTWAnyAddress(TWAnyAddress* anyAddress) { + return std::shared_ptr(anyAddress, tw_any_address_delete); +} + +inline std::shared_ptr wrapTWPublicKey(TWPublicKey* publicKey) { + return std::shared_ptr(publicKey, tw_public_key_delete); +} + +struct TWDataVectorWrapper { + /// Implicit constructor. + TWDataVectorWrapper(const std::vector& vec) { + ptr = std::shared_ptr(tw_data_vector_create(), Rust::tw_data_vector_delete); + + for (const auto& item : vec) { + auto* itemData = tw_data_create_with_bytes(item.data(), item.size()); + Rust::tw_data_vector_add(ptr.get(), itemData); + Rust::tw_data_delete(itemData); + } + } + + ~TWDataVectorWrapper() = default; + + TWDataVector* get() const { + return ptr.get(); + } + + std::shared_ptr ptr; +}; + +struct TWDataWrapper { + /// Implicit constructor. + TWDataWrapper(const Data& bytes) { + auto* dataRaw = tw_data_create_with_bytes(bytes.data(), bytes.size()); + ptr = std::shared_ptr(dataRaw, tw_data_delete); + } + + /// Implicit constructor. + TWDataWrapper(TWData *ptr): ptr(std::shared_ptr(ptr, tw_data_delete)) { + } + + ~TWDataWrapper() = default; + + TWData* get() const { + return ptr.get(); + } + + Data toDataOrDefault() const { + if (!ptr) { + return {}; + } + + auto* bytes = tw_data_bytes(ptr.get()); + Data out(bytes, bytes + tw_data_size(ptr.get())); + return out; + } + + std::shared_ptr ptr; +}; + +struct TWStringWrapper { + /// Implicit constructor. + TWStringWrapper(const std::string& string) { + auto* stringRaw = tw_string_create_with_utf8_bytes(string.c_str()); + ptr = std::shared_ptr(stringRaw, tw_string_delete); + } + + /// Implicit constructor. + TWStringWrapper(TWString *ptr): ptr(std::shared_ptr(ptr, tw_string_delete)) { + } + + ~TWStringWrapper() = default; + + TWString* get() const { + return ptr.get(); + } + + std::string toStringOrDefault() const { + if (!ptr) { + return {}; + } + + auto* bytes = tw_string_utf8_bytes(ptr.get()); + return {bytes}; + } + + std::shared_ptr ptr; +}; + struct CByteArrayWrapper { CByteArrayWrapper() = default; diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift index 8cfd526b459..d86ae0b1aba 100644 --- a/swift/Tests/BarzTests.swift +++ b/swift/Tests/BarzTests.swift @@ -96,7 +96,7 @@ class BarzTests: XCTestCase { } let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) XCTAssertEqual(output.preHash.hexString, "2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae") - XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16db98b365b1f89191996942612b14f1da4bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}") + XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}") } // https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 @@ -135,7 +135,7 @@ class BarzTests: XCTestCase { } let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) XCTAssertEqual(output.preHash.hexString, "548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937") - XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392ae041bfbdbaa0cff9234a0c8f64df97b7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"); + XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"); } // https://testnet.bscscan.com/tx/0x872f709815a9f79623a349f2f16d93b52c4d5136967bab53a586f045edbe9203 @@ -185,7 +185,7 @@ class BarzTests: XCTestCase { } let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) XCTAssertEqual(output.preHash.hexString, "84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab") - XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1e6c542ebc7c960c6a155a9094db838cef842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") + XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } let factory = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" diff --git a/swift/Tests/Blockchains/CosmosTests.swift b/swift/Tests/Blockchains/CosmosTests.swift index 6903c9487ee..7a7c906d5d0 100644 --- a/swift/Tests/Blockchains/CosmosTests.swift +++ b/swift/Tests/Blockchains/CosmosTests.swift @@ -102,7 +102,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") XCTAssertEqual(output.errorMessage, "") } diff --git a/swift/Tests/Blockchains/EthereumAbiTests.swift b/swift/Tests/Blockchains/EthereumAbiTests.swift index a6a18430e5b..ef7e9f413df 100644 --- a/swift/Tests/Blockchains/EthereumAbiTests.swift +++ b/swift/Tests/Blockchains/EthereumAbiTests.swift @@ -57,7 +57,7 @@ class EthereumAbiTests: XCTestCase { func testValueDecoderValue() { XCTAssertEqual("42", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000000000000000000000000000000000000000002a")!, type: "uint")) XCTAssertEqual("24", EthereumAbiValue.decodeValue(input: Data(hexString: "0000000000000000000000000000000000000000000000000000000000000018")!, type: "uint8")) - XCTAssertEqual("0xf784682c82526e245f50975190ef0fff4e4fc077", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077")!, type: "address")) + XCTAssertEqual("0xF784682C82526e245F50975190EF0fff4E4fC077", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077")!, type: "address")) XCTAssertEqual("Hello World! Hello World! Hello World!", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000" )!, type: "string")) @@ -71,7 +71,7 @@ class EthereumAbiTests: XCTestCase { func testValueDecoderArray_address() { let input = Data(hexString: "0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3") - XCTAssertEqual("[\"0xf784682c82526e245f50975190ef0fff4e4fc077\",\"0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3\"]", EthereumAbiValue.decodeArray(input: input!, type: "address[]")) + XCTAssertEqual("[\"0xF784682C82526e245F50975190EF0fff4E4fC077\",\"0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3\"]", EthereumAbiValue.decodeArray(input: input!, type: "address[]")) } func testValueDecoderArray_bytes() { @@ -90,7 +90,7 @@ class EthereumAbiTests: XCTestCase { "inputs": [{ "name": "_spender", "type": "address", - "value": "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + "value": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }, { "name": "_value", "type": "uint256", @@ -113,24 +113,24 @@ class EthereumAbiTests: XCTestCase { "inputs": [{ "name": "caller", "type": "address", - "value": "0x27239549dd40e1d60f5b80b0c4196923745b1fd2" + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" }, { "components": [{ "name": "srcToken", "type": "address", - "value": "0x2b591e99afe9f32eaa6214f7b7629768c40eeb39" + "value": "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39" }, { "name": "dstToken", "type": "address", - "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }, { "name": "srcReceiver", "type": "address", - "value": "0x27239549dd40e1d60f5b80b0c4196923745b1fd2" + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" }, { "name": "dstReceiver", "type": "address", - "value": "0x1611c227725c5e420ef058275ae772b41775e261" + "value": "0x1611C227725c5E420Ef058275AE772b41775e261" }, { "name": "amount", "type": "uint256", @@ -221,4 +221,135 @@ class EthereumAbiTests: XCTestCase { XCTAssertEqual(hash.hexString, "54140d99a864932cbc40fd8a2d1d1706c3923a79c183a3b151e929ac468064db") } + + func testEthereumAbiEncodeFunction() throws { + let amountIn = EthereumAbiNumberNParam.with { + $0.bits = 256 + $0.value = Data(hexString: "0x0de0b6b3a7640000")! // 1000000000000000000 + } + let amountOutMin = EthereumAbiNumberNParam.with { + $0.bits = 256 + $0.value = Data(hexString: "0x229f7e501ad62bdb")! // 2494851601099271131 + } + let deadline = EthereumAbiNumberNParam.with { + $0.bits = 256 + $0.value = Data(hexString: "0x5f0ed070")! // 1594806384 + } + + let arr = EthereumAbiArrayParam.with { + $0.elementType = EthereumAbiParamType.with { + $0.address = EthereumAbiAddressType.init() + } + $0.elements = [ + EthereumAbiToken.with { $0.address = "0x6B175474E89094C44Da98b954EedeAC495271d0F" }, + EthereumAbiToken.with { $0.address = "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2" }, + EthereumAbiToken.with { $0.address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" }, + EthereumAbiToken.with { $0.address = "0xE41d2489571d322189246DaFA5ebDe1F4699F498" }, + ] + } + let encodingInput = EthereumAbiFunctionEncodingInput.with { + $0.functionName = "swapExactTokensForTokens" + $0.tokens = [ + EthereumAbiToken.with { $0.numberUint = amountIn }, + EthereumAbiToken.with { $0.numberUint = amountOutMin }, + EthereumAbiToken.with { $0.array = arr }, + EthereumAbiToken.with { $0.address = "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1" }, + EthereumAbiToken.with { $0.numberUint = deadline }, + ] + } + + let inputData = try encodingInput.serializedData() + let outputData = EthereumAbi.encodeFunction(coin: .ethereum, input: inputData) + + let encodingOutput = try EthereumAbiFunctionEncodingOutput(serializedData: outputData) + XCTAssertEqual(encodingOutput.error, EthereumAbiAbiError.ok) + XCTAssertEqual(encodingOutput.functionType, "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)") + + let expectedEncoded = Data(hexString: "0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000229f7e501ad62bdb00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f1000000000000000000000000000000000000000000000000000000005f0ed07000000000000000000000000000000000000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498")! + XCTAssertEqual(encodingOutput.encoded, expectedEncoded) + } + + func testEthereumAbiDecodeParams() throws { + let encoded = Data(hexString: "00000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b0c0000000000000000000000000000000000000000000000000000000000000001")! + + let abiParams = [ + EthereumAbiParam.with { + $0.name = "to" + $0.param = EthereumAbiParamType.with { $0.address = EthereumAbiAddressType.init() } + }, + EthereumAbiParam.with { + $0.name = "approved" + $0.param = EthereumAbiParamType.with { $0.boolean = EthereumAbiBoolType.init() } + } + ] + let decodingInput = EthereumAbiParamsDecodingInput.with { + $0.encoded = encoded + $0.abiParams = EthereumAbiAbiParams.with { $0.params = abiParams } + } + + let inputData = try decodingInput.serializedData() + let outputData = EthereumAbi.decodeParams(coin: .ethereum, input: inputData) + + let decodingOutput = try EthereumAbiParamsDecodingOutput(serializedData: outputData) + XCTAssertEqual(decodingOutput.error, EthereumAbiAbiError.ok) + + XCTAssertEqual(decodingOutput.tokens[0].name, "to") + XCTAssertEqual(decodingOutput.tokens[0].address, "0x88341d1a8F672D2780C8dC725902AAe72F143B0c") + XCTAssertEqual(decodingOutput.tokens[1].name, "approved") + XCTAssertEqual(decodingOutput.tokens[1].boolean, true) + } + + func testEthereumAbiDecodeValue() throws { + let encoded = Data(hexString: "000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000")! + + let decodingInput = EthereumAbiValueDecodingInput.with { + $0.encoded = encoded + $0.paramType = "string" + } + + let inputData = try decodingInput.serializedData() + let outputData = EthereumAbi.decodeValue(coin: .ethereum, input: inputData) + + let decodingOutput = try EthereumAbiValueDecodingOutput(serializedData: outputData) + XCTAssertEqual(decodingOutput.error, EthereumAbiAbiError.ok) + XCTAssertEqual(decodingOutput.token.stringValue, "Hello World! Hello World! Hello World!") + } + + func testEthereumAbiDecodeContractCall() throws { + let encoded = Data(hexString: "c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000")! + let abiJson = """ + { + "c47f0027": { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + } + """ + + let decodingInput = EthereumAbiContractCallDecodingInput.with { + $0.encoded = encoded + $0.smartContractAbiJson = abiJson + } + + let inputData = try decodingInput.serializedData() + let outputData = EthereumAbi.decodeContractCall(coin: .ethereum, input: inputData) + + let decodingOutput = try EthereumAbiContractCallDecodingOutput(serializedData: outputData) + XCTAssertEqual(decodingOutput.error, EthereumAbiAbiError.ok) + + let expectedJson = #"{"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]}"# + XCTAssertEqual(decodingOutput.decodedJson, expectedJson) + XCTAssertEqual(decodingOutput.tokens[0].name, "name") + XCTAssertEqual(decodingOutput.tokens[0].stringValue, "deadbeef") + } } diff --git a/swift/Tests/Blockchains/EthereumRlpTests.swift b/swift/Tests/Blockchains/EthereumRlpTests.swift new file mode 100644 index 00000000000..3690657bab9 --- /dev/null +++ b/swift/Tests/Blockchains/EthereumRlpTests.swift @@ -0,0 +1,47 @@ +// Copyright © 2017-2020 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 WalletCore + +class EthereumRlpTests: XCTestCase { + func testRlpEncodeEip1559() throws { + let chainId = Data(hexString: "0x0a")! + let nonce = Data(hexString: "0x06")! + let maxInclusionFeePerGas = Data(hexString: "0x77359400")! // 2000000000 + let maxFeePerGas = Data(hexString: "0xb2d05e00")! // 3000000000 + let gasLimit = Data(hexString: "0x526c")! // 21100 + let to = "0x6b175474e89094c44da98b954eedeac495271d0f" + let amount = Data(hexString: "0x00")! + let payload = Data(hexString: "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1")! + // Empty `access_list`. + let accessList = EthereumRlpRlpList() + + let rlpList = EthereumRlpRlpList.with { + $0.items = [ + EthereumRlpRlpItem.with { $0.numberU256 = chainId }, + EthereumRlpRlpItem.with { $0.numberU256 = nonce }, + EthereumRlpRlpItem.with { $0.numberU256 = maxInclusionFeePerGas }, + EthereumRlpRlpItem.with { $0.numberU256 = maxFeePerGas }, + EthereumRlpRlpItem.with { $0.numberU256 = gasLimit }, + EthereumRlpRlpItem.with { $0.address = to }, + EthereumRlpRlpItem.with { $0.numberU256 = amount }, + EthereumRlpRlpItem.with { $0.data = payload }, + EthereumRlpRlpItem.with { $0.list = accessList }, + ] + } + + let encodingInput = EthereumRlpEncodingInput.with { + $0.item.list = rlpList + } + let inputData = try encodingInput.serializedData() + let outputData = EthereumRlp.encode(coin: .ethereum, input: inputData) + + let encodingOutput = try EthereumRlpEncodingOutput(serializedData: outputData) + XCTAssertEqual(encodingOutput.error, CommonSigningError.ok) + XCTAssertEqual(encodingOutput.encoded.hexString, "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0") + } +} diff --git a/swift/Tests/Blockchains/InternetComputerTests.swift b/swift/Tests/Blockchains/InternetComputerTests.swift new file mode 100644 index 00000000000..0ea025dd0c9 --- /dev/null +++ b/swift/Tests/Blockchains/InternetComputerTests.swift @@ -0,0 +1,78 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import WalletCore +import XCTest + +class InternetComputerTests: XCTestCase { + // TODO: Check and finalize implementation + + func testAddress() { + // TODO: Check and finalize implementation + + let key = PrivateKey(data: Data(hexString: "ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .internetComputer) + let addressFromString = AnyAddress(string: "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", coin: .internetComputer)! + + XCTAssertEqual(pubkey.data.hexString, "048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + $0.amount = 100000000 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.signedTransaction.hexString, "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2") + } + + func testSignWithInvalidToAccountIdentifier() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b" + $0.amount = 100000000 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.error.rawValue, 16) + } + + func testSignWithInvalidAmount() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + $0.amount = 0 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.error.rawValue, 23) + } +} diff --git a/swift/Tests/Blockchains/NEOTests.swift b/swift/Tests/Blockchains/NEOTests.swift index 89754e3e28e..74f87b6102e 100644 --- a/swift/Tests/Blockchains/NEOTests.swift +++ b/swift/Tests/Blockchains/NEOTests.swift @@ -95,7 +95,12 @@ class NEOTests: XCTestCase { let result = signedTx.encoded.hexString // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf + XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", result) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", + // result) } } diff --git a/swift/Tests/Blockchains/OntologyTests.swift b/swift/Tests/Blockchains/OntologyTests.swift index 3779918b0f7..4daec3a7403 100644 --- a/swift/Tests/Blockchains/OntologyTests.swift +++ b/swift/Tests/Blockchains/OntologyTests.swift @@ -53,7 +53,11 @@ class OntologyTests: XCTestCase { let output: OntologySigningOutput = AnySigner.sign(input: input, coin: .ontology) let result = output.encoded.hexString + XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) } func testSignOngTransfer() { @@ -72,7 +76,11 @@ class OntologyTests: XCTestCase { let output: OntologySigningOutput = AnySigner.sign(input: input, coin: .ontology) let result = output.encoded.hexString + XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8aefaa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d743b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) } } diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 81e0bdc55f7..fdc68d10b43 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -112,7 +112,9 @@ class CoinAddressDerivationTests: XCTestCase { .neon, .base, .linea, - .greenfield: + .greenfield, + .mantle, + .zenEON: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ronin: @@ -222,7 +224,7 @@ class CoinAddressDerivationTests: XCTestCase { case .thunderCore: let expectedResult = "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .tomoChain: + case .viction: let expectedResult = "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tron: @@ -304,7 +306,7 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .aptos: - let expectedResult = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; + let expectedResult = "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nebl: let expectedResult = "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7"; @@ -381,6 +383,12 @@ class CoinAddressDerivationTests: XCTestCase { case .sei: let expectedResult = "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .internetComputer: + let expectedResult = "b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .tia: + let expectedResult = "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } diff --git a/swift/Tests/CoinTypeTests.swift b/swift/Tests/CoinTypeTests.swift index 7ef5b5e2260..5021dcf0f32 100644 --- a/swift/Tests/CoinTypeTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -23,7 +23,7 @@ class CoinTypeTests: XCTestCase { XCTAssertEqual(CoinType.poanetwork.rawValue, 178) XCTAssertEqual(CoinType.veChain.rawValue, 818) XCTAssertEqual(CoinType.icon.rawValue, 74) - XCTAssertEqual(CoinType.tomoChain.rawValue, 889) + XCTAssertEqual(CoinType.viction.rawValue, 889) XCTAssertEqual(CoinType.tezos.rawValue, 1729) XCTAssertEqual(CoinType.qtum.rawValue, 2301) XCTAssertEqual(CoinType.nebulas.rawValue, 2718) diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 62d907ccfaf..3017d10c304 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -234,7 +234,6 @@ class KeyStoreTests: XCTestCase { } func testImportJSON() throws { - let expected = """ { "activeAccounts": [{ diff --git a/swift/Tests/PrivateKeyTests.swift b/swift/Tests/PrivateKeyTests.swift index bc9ae7e13ff..82be1a5e7a5 100644 --- a/swift/Tests/PrivateKeyTests.swift +++ b/swift/Tests/PrivateKeyTests.swift @@ -51,44 +51,6 @@ class PrivateKeyTests: XCTestCase { XCTAssertEqual(publicKey.data.hexString, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91") } - func testGetSharedKey() { - let privateKey = PrivateKey(data: Data(hexString: "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0")!)! - let publicKey = PublicKey(data: Data(hexString: "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992")!, type: .secp256k1)! - - let derivedData = privateKey.getSharedKey(publicKey: publicKey, curve: .secp256k1)! - - XCTAssertEqual(derivedData.hexString, "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a") - } - - func testGetSharedKeyWycherproof() { - let privateKey = PrivateKey(data: Data(hexString: "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254")!)! - let publicKey = PublicKey(data: Data(hexString: "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b")!, type: .secp256k1)! - - let derivedData = privateKey.getSharedKey(publicKey: publicKey, curve: .secp256k1)! - - XCTAssertEqual(derivedData.hexString, "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a") - } - - func testGetSharedKeyBidirectional() { - let privateKey1 = PrivateKey(data: Data(hexString: "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0")!)! - let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: false) - let privateKey2 = PrivateKey(data: Data(hexString: "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a")!)! - let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: false) - - let derivedData1 = privateKey1.getSharedKey(publicKey: publicKey2, curve: .secp256k1)! - let derivedData2 = privateKey2.getSharedKey(publicKey: publicKey1, curve: .secp256k1)! - - XCTAssertEqual(derivedData1.hexString, derivedData2.hexString) - } - - func testGetSharedKeyError() { - let privateKey = PrivateKey(data: Data(hexString: "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0")!)! - let publicKey = PublicKey(data: Data(hexString: "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992")!, type: .secp256k1)! - - let derivedData = privateKey.getSharedKey(publicKey: publicKey, curve: .ed25519) - XCTAssertNil(derivedData) - } - func testSignSchnorr() { let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) diff --git a/tests/chains/Aion/RLPTests.cpp b/tests/chains/Aion/RLPTests.cpp index cda4a23fbbd..7ad104a9bdf 100644 --- a/tests/chains/Aion/RLPTests.cpp +++ b/tests/chains/Aion/RLPTests.cpp @@ -12,16 +12,23 @@ namespace TW::Aion::tests { using boost::multiprecision::uint128_t; +// Function helper over `RLP::prepareLong`. +Data encodeLong(const uint128_t& l) { + EthereumRlp::Proto::EncodingInput input; + *input.mutable_item() = RLP::prepareLong(l); + return Ethereum::RLP::encode(input); +} + TEST(AionRLP, EncodeLong) { - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(1))), "01"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(21000))), "825208"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(1000000))), "830f4240"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(20000000000))), "8800000004a817c800"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(9007199254740991))), "88001fffffffffffff"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(9007199254740990))), "88001ffffffffffffe"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4294967296L))), "880000000100000000"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); + EXPECT_EQ(hex(encodeLong(uint128_t(1))), "01"); + EXPECT_EQ(hex(encodeLong(uint128_t(21000))), "825208"); + EXPECT_EQ(hex(encodeLong(uint128_t(1000000))), "830f4240"); + EXPECT_EQ(hex(encodeLong(uint128_t(20000000000))), "8800000004a817c800"); + EXPECT_EQ(hex(encodeLong(uint128_t(9007199254740991))), "88001fffffffffffff"); + EXPECT_EQ(hex(encodeLong(uint128_t(9007199254740990))), "88001ffffffffffffe"); + EXPECT_EQ(hex(encodeLong(uint128_t(4294967296L))), "880000000100000000"); + EXPECT_EQ(hex(encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); + EXPECT_EQ(hex(encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); } } // namespace TW::Aion::tests diff --git a/tests/chains/Aptos/AddressTests.cpp b/tests/chains/Aptos/AddressTests.cpp index 8c758428154..fc68f647a88 100644 --- a/tests/chains/Aptos/AddressTests.cpp +++ b/tests/chains/Aptos/AddressTests.cpp @@ -6,7 +6,7 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" -#include "Aptos/Address.h" +#include "Aptos/Entry.h" #include "PublicKey.h" #include "PrivateKey.h" #include @@ -15,49 +15,38 @@ namespace TW::Aptos::tests { TEST(AptosAddress, Valid) { - ASSERT_TRUE(Address::isValid("0x1")); - ASSERT_TRUE(Address::isValid(Address::one().string())); - ASSERT_TRUE(Address::isValid(Address::zero().string())); - ASSERT_TRUE(Address::isValid("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); - ASSERT_TRUE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); - ASSERT_TRUE(Address::isValid("19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c")); - ASSERT_TRUE(Address::isValid("777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb")); - ASSERT_TRUE(Address::isValid("0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb")); - ASSERT_TRUE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175")); // too short -> automatically padded + Entry entry; + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x1", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x0", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", std::monostate{})); } TEST(AptosAddress, Invalid) { - ASSERT_FALSE(Address::isValid("Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); // Invalid hex character - ASSERT_FALSE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb")); // Invalid length: too long + Entry entry; + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); } TEST(AptosAddress, FromPrivateKey) { auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(address.string(), "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); + auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypeAptos, pubkey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); } TEST(AptosAddress, FromPublicKey) { auto publicKey = PublicKey(parse_hex("ad0e293a56c9fc648d1872a00521d97e6b65724519a2676c2c47cb95d131cf5a"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); -} - -TEST(AptosAddress, FromString) { - auto address = Address("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); - ASSERT_EQ(address.string(), "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); - - - address = Address("0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); - ASSERT_EQ(address.string(), "0x0000777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); - address = Address("777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); - ASSERT_EQ(address.string(), "0x0000777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); -} - -TEST(AptosAddress, ShortString) { - ASSERT_EQ(Address::one().string(), "0x0000000000000000000000000000000000000000000000000000000000000001"); - ASSERT_EQ(Address::one().shortString(), "1"); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypeAptos, publicKey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); } } // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/CompilerTests.cpp b/tests/chains/Aptos/CompilerTests.cpp index 6f731cfbfe5..fd00034931a 100644 --- a/tests/chains/Aptos/CompilerTests.cpp +++ b/tests/chains/Aptos/CompilerTests.cpp @@ -4,7 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Aptos/Signer.h" +#include "proto/Aptos.pb.h" +#include "proto/TransactionCompiler.pb.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -66,12 +67,12 @@ TEST(AptosCompiler, StandardTransaction) { "gas_unit_price": "100", "max_gas_amount": "3296766", "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], "function": "0x1::aptos_account::transfer", "type": "entry_function_payload", "type_arguments": [] }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", "sequence_number": "99", "signature": { "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", @@ -160,7 +161,7 @@ TEST(AptosCompiler, BlindTransactionJson) { ], "type": "entry_function_payload" }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", "sequence_number": "42", "signature": { "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", @@ -173,52 +174,4 @@ TEST(AptosCompiler, BlindTransactionJson) { assertJSONEqual(expectedJson, parsedJson); } -TEST(AptosCompiler, BlindTransactionHex) { - // successfully broadcasted https://explorer.aptoslabs.com/txn/0xd95857a9e644528708778a3a0a6e13986751944fca30eaac98853c1655de0422?network=Devnet - auto anyEncoded = "0xb5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"; - - Proto::SigningInput input; - input.set_any_encoded(anyEncoded); - - auto inputString = input.SerializeAsString(); - auto inputStrData = TW::Data(inputString.begin(), inputString.end()); - - // Pre-hash the transaction. - - auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); - TxCompiler::Proto::PreSigningOutput preSigningOutput; - preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); - auto actualDataToSign = data(preSigningOutput.data()); - - EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); - EXPECT_EQ(actualDataToSign, parse_hex(anyEncoded)); - - // Sign the pre-hash data. - - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); - EXPECT_EQ(hex(signature), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - - // Compile the transaction. - - auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); - Proto::SigningOutput output; - output.ParseFromArray(outputData.data(), static_cast(outputData.size())); - - EXPECT_EQ(output.error(), Common::Proto::OK); - ASSERT_EQ(hex(output.raw_txn()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(output.authenticator().signature()), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - ASSERT_EQ(hex(output.encoded()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c409e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - nlohmann::json expectedJson = R"( - { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b", - "type": "ed25519_signature" - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(output.json()); - assertJSONEqual(expectedJson, parsedJson); -} - } diff --git a/tests/chains/Aptos/MoveTypesTests.cpp b/tests/chains/Aptos/MoveTypesTests.cpp deleted file mode 100644 index 06ef076991c..00000000000 --- a/tests/chains/Aptos/MoveTypesTests.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright 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 - -namespace TW::Aptos::tests { - -TEST(AptosMoveTypes, ModuleId) { - ModuleId module(Address::one(), "coin"); - ASSERT_EQ(module.address(), Address::one()); - ASSERT_EQ(module.name(), "coin"); - ASSERT_EQ(hex(module.accessVector()), "00000000000000000000000000000000000000000000000000000000000000000104636f696e"); - ASSERT_EQ(module.string(), "0x0000000000000000000000000000000000000000000000000000000000000001::coin"); - ASSERT_EQ(module.shortString(), "0x1::coin"); -} - -TEST(AptosMoveTypes, StructTag) { - auto functorTest = [](T value, const std::string expectedHex) { - TypeTag t{.tags = value}; - StructTag st(Address::one(), "abc", "abc", std::vector{{t}}); - ASSERT_EQ(st.moduleID().name(), "abc"); - ASSERT_EQ(st.moduleID().address(), Address::one()); - ASSERT_EQ(hex(st.serialize()), expectedHex); - }; - functorTest(Bool{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630100"); - functorTest(U8{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630101"); - functorTest(U64{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630102"); - functorTest(U128{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630103"); - functorTest(TAddress{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630104"); - functorTest(TSigner{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630105"); - functorTest(Vector{.tags = std::vector{{TypeTag{.tags = U8{}}}}}, "0100000000000000000000000000000000000000000000000000000000000000010361626303616263010601"); - StructTag stInner(Address::one(), "foo", "bar", std::vector{{U8{}}}); - functorTest(TStructTag{stInner}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630107000000000000000000000000000000000000000000000000000000000000000103666f6f036261720101"); -} - -TEST(AptosMoveTypes, TypeTagDisplay) { - auto functorTest = [](const TypeTag &value, const std::string& expected) { - ASSERT_EQ(TypeTagToString(value), expected); - }; - functorTest(TypeTag{.tags = Bool{}}, "bool"); - functorTest(TypeTag{.tags = U8{}}, "u8"); - functorTest(TypeTag{.tags = U64{}}, "u64"); - functorTest(TypeTag{.tags = U128{}}, "u128"); - functorTest(TypeTag{.tags = TAddress{}}, "address"); - functorTest(TypeTag{.tags = TSigner{}}, "signer"); - TypeTag t{.tags = TypeTag::TypeTagVariant(Vector{.tags = {{U8{}}}})}; - functorTest(t, "vector"); - StructTag st(Address::one(), "foo", "bar", std::vector{{U8{}}}); - TypeTag anotherT{.tags = TypeTag::TypeTagVariant(st)}; - functorTest(anotherT, "0x1::foo::bar"); - functorTest(gTransferTag, "0x1::aptos_coin::AptosCoin"); -} - -} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/SignerTests.cpp b/tests/chains/Aptos/SignerTests.cpp deleted file mode 100644 index 65c0bc87089..00000000000 --- a/tests/chains/Aptos/SignerTests.cpp +++ /dev/null @@ -1,730 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aptos/Address.h" -#include "Aptos/Signer.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "TestUtilities.h" -#include - -#include - -namespace TW::Aptos::tests { - -TEST(AptosSigner, ClaimNftTxSign) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x60b51e15140ec0b7650334e948fb447ce3cb13ae63492260461ebfa9d02e85c4?network=testnet - Proto::SigningInput input; - input.set_sender("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(19); - auto& tf = *input.mutable_nft_message()->mutable_claim_nft(); - tf.set_sender("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); - tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); - tf.set_collectionname("Topaz Troopers"); - tf.set_name("Topaz Trooper #20068"); - tf.set_property_version(0); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": [ - "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", - "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", - "Topaz Troopers", "Topaz Trooper #20068", "0"], - "function": "0x3::token_transfers::claim_script", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "19", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, NftOfferTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x514e473618bd3cb89a2b110b7c473db9a2e10532f98eb42d02d86fb31c00525d?network=testnet - Proto::SigningInput input; - input.set_sender("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); - input.set_sequence_number(1); - auto& tf = *input.mutable_nft_message()->mutable_offer_nft(); - tf.set_receiver("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); - tf.set_collectionname("Topaz Troopers"); - tf.set_name("Topaz Trooper #20068"); - tf.set_property_version(0); - tf.set_amount(1); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("7bebb6d543d17f6fe4e685cfab239fa37896edd594ff859f1df32f244fb707e2")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f"); - ASSERT_EQ(hex(result.encoded()), "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada00000000020020d1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a411340af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": [ - "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", - "Topaz Troopers", "Topaz Trooper #20068", "0", "1"], - "function": "0x3::token_transfers::offer_script", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", - "sequence_number": "1", - "signature": { - "public_key": "0xd1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a4113", - "signature": "0xaf5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, CancelNftOfferTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x0b8c64e6847c368e4c6bd2cce0e9eab378971b0ef2e3bc40cbd292910a80201d?network=testnet - Proto::SigningInput input; - input.set_sender("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(21); - auto& tf = *input.mutable_nft_message()->mutable_cancel_offer_nft(); - tf.set_receiver("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); - tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); - tf.set_collectionname("Topaz Troopers"); - tf.set_name("Topaz Trooper #20068"); - tf.set_property_version(0); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": [ - "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", - "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", - "Topaz Troopers", "Topaz Trooper #20068", "0"], - "function": "0x3::token_transfers::cancel_offer_script", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "21", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(99); - auto& tf = *input.mutable_transfer(); - tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - tf.set_amount(1000); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(33); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(result.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], - "function": "0x1::aptos_account::transfer", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "99", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, CreateAccount) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x477141736de6b0936a6c3734e4d6fd018c7d21f1f28f99028ef0bc6881168602?network=Devnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(0); - auto& tf = *input.mutable_create_account(); - tf.set_auth_key("0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(33); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(result.authenticator().signature()), "fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": ["0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"], - "function": "0x1::aptos_account::create_account", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "0", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xfcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, BlindSignFromJson) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet - auto payloadJson = R"( - { - "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", - "type_arguments": [ - "0x1::aptos_coin::AptosCoin", - "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" - ], - "arguments": [ - "1000000", - "49329" - ], - "type": "entry_function_payload" - })"_json; - Proto::SigningInput input; - input.set_sequence_number(42); - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_gas_unit_price(100); - input.set_max_gas_amount(100011); - input.set_expiration_timestamp_secs(3664390082); - input.set_any_encoded(payloadJson.dump()); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_chain_id(1); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); - nlohmann::json expectedJson = R"( -{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "100011", - "payload": { - "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", - "type_arguments": [ - "0x1::aptos_coin::AptosCoin", - "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" - ], - "arguments": [ - "1000000", - "49329" - ], - "type": "entry_function_payload" - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "42", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", - "type": "ed25519_signature" - } -} - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TortugaLiquidStakingStake) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet - Proto::SigningInput input; - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_sequence_number(19); - auto& ls = *input.mutable_liquid_staking_message(); - ls.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); - auto& ls_stake = *ls.mutable_stake(); - ls_stake.set_amount(100000000); - input.set_max_gas_amount(5554); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(1670240203); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - EXPECT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001"); - EXPECT_EQ(hex(result.authenticator().signature()), "22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d"); - EXPECT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc44022d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d"); - nlohmann::json expectedJson = R"( - { - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "19", - "max_gas_amount": "5554", - "gas_unit_price": "100", - "expiration_timestamp_secs": "1670240203", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", - "type_arguments": [], - "arguments": [ - "100000000" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", - "signature": "0x22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TortugaLiquidStakingUnstake) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968?network=mainnet - Proto::SigningInput input; - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_sequence_number(20); - auto& ls = *input.mutable_liquid_staking_message(); - ls.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); - auto& ls_unstake = *ls.mutable_unstake(); - ls_unstake.set_amount(99178100); - input.set_max_gas_amount(2371); - input.set_gas_unit_price(120); - input.set_expiration_timestamp_secs(1670304949); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - EXPECT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001"); - EXPECT_EQ(hex(result.authenticator().signature()), "6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c"); - EXPECT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4406994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c"); - nlohmann::json expectedJson = R"( - { - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "20", - "max_gas_amount": "2371", - "gas_unit_price": "120", - "expiration_timestamp_secs": "1670304949", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", - "type_arguments": [], - "arguments": [ - "99178100" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", - "signature": "0x6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TortugaLiquidStakingClaim) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x9fc874de7a7d3e813d9a1658d896023de270a0096a5e258c196005656ace7d54?network=mainnet - Proto::SigningInput input; - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_sequence_number(28); - auto& ls = *input.mutable_liquid_staking_message(); - ls.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); - auto& ls_claim = *ls.mutable_claim(); - ls_claim.set_idx(0); - input.set_max_gas_amount(10); - input.set_gas_unit_price(148); - input.set_expiration_timestamp_secs(1682066783); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - EXPECT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001"); - EXPECT_EQ(hex(result.authenticator().signature()), "c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03"); - EXPECT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc440c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03"); - nlohmann::json expectedJson = R"( - { - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "28", - "max_gas_amount": "10", - "gas_unit_price": "148", - "expiration_timestamp_secs": "1682066783", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::claim", - "type_arguments": [], - "arguments": [ - "0" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", - "signature": "0xc936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, BlindSignStaking) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/payload - auto payloadJson = R"( - { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", - "type_arguments": [], - "arguments": [ - "100000000" - ], - "type": "entry_function_payload" - })"_json; - - Proto::SigningInput input; - input.set_sequence_number(43); - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_gas_unit_price(100); - input.set_max_gas_amount(100011); - input.set_expiration_timestamp_secs(3664390082); - input.set_any_encoded(payloadJson.dump()); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_chain_id(1); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b"); - ASSERT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b"); - nlohmann::json expectedJson = R"( -{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "100011", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", - "type_arguments": [], - "arguments": [ - "100000000" - ], - "type": "entry_function_payload" - }, - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "43", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xa41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", - "type": "ed25519_signature" - } -} - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, BlindSignUnStaking) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968/payload - auto payloadJson = R"( - { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", - "type_arguments": [], - "arguments": [ - "99178100" - ], - "type": "entry_function_payload" - })"_json; - Proto::SigningInput input; - input.set_sequence_number(44); - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_gas_unit_price(100); - input.set_max_gas_amount(100011); - input.set_expiration_timestamp_secs(3664390082); - input.set_any_encoded(payloadJson.dump()); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_chain_id(1); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a"); - ASSERT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a"); - nlohmann::json expectedJson = R"( -{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "100011", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", - "type_arguments": [], - "arguments": [ - "99178100" - ], - "type": "entry_function_payload" - }, - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "44", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xa58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", - "type": "ed25519_signature" - } -} - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - - -TEST(AptosSigner, BlindSign) { - // successfully broadcasted https://explorer.aptoslabs.com/txn/0xd95857a9e644528708778a3a0a6e13986751944fca30eaac98853c1655de0422?network=Devnet - // encoded submission with: - // curl --location --request POST 'https://fullnode.devnet.aptoslabs.com/v1/transactions/encode_submission' \ - //--header 'Content-Type: application/json' \ - //--header 'Cookie: AWSALB=0zI2zWypvEr0I3sGM6vnyHSxYO1D0aaMXfyA/2VwhA291aJJ80Yz67Fur50sXPFBI8dKKID4p8DShj1KkEXPY/NGAylpOj1EG2M2Qjuu1B38Q5C+dZW2CHT+IAZ5; AWSALBCORS=0zI2zWypvEr0I3sGM6vnyHSxYO1D0aaMXfyA/2VwhA291aJJ80Yz67Fur50sXPFBI8dKKID4p8DShj1KkEXPY/NGAylpOj1EG2M2Qjuu1B38Q5C+dZW2CHT+IAZ5' \ - //--data-raw '{ - // "expiration_timestamp_secs": "3664390082", - // "gas_unit_price": "100", - // "max_gas_amount": "3296766", - // "payload": { - // "function": "0x4633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b298837::pool::swap_y_to_x", - // "type_arguments": [ - // "0xdeae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e01::devnet_coins::DevnetUSDT", - // "0x1::aptos_coin::AptosCoin" - // ], - // "arguments": [ - // "100000000", - // "0" - // ], - // "type": "entry_function_payload" - // }, - // "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - // "sequence_number": "2" - //}' - Proto::SigningInput input; - input.set_any_encoded("0xb5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(result.authenticator().signature()), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - ASSERT_EQ(hex(result.encoded()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c409e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - nlohmann::json expectedJson = R"( - { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b", - "type": "ed25519_signature" - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TokenRegisterTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(23); - auto& tf = *input.mutable_register_token(); - tf.mutable_function()->set_account_address("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379"); - tf.mutable_function()->set_module("move_coin"); - tf.mutable_function()->set_name("MoveCoin"); - input.set_max_gas_amount(2000000); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "2000000", - "payload": { - "arguments": [], - "function": "0x1::managed_coin::register", - "type": "entry_function_payload", - "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "23", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TokenTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb5b383a5c7f99b2edb3bed9533f8169a89051b149d65876a82f4c0b9bf78a15b?network=Devnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(24); - auto& tf = *input.mutable_token_transfer(); - tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - tf.set_amount(100000); - tf.mutable_function()->set_account_address("0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9"); - tf.mutable_function()->set_module("coins"); - tf.mutable_function()->set_name("BTC"); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(32); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada0000000020"); - ASSERT_EQ(hex(result.authenticator().signature()), "7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada00000000200020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c407643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","100000"], - "function": "0x1::coin::transfer", - "type": "entry_function_payload", - "type_arguments": ["0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC"] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "24", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TokenTransferCoins) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet - Proto::SigningInput input; - input.set_sender("0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25"); - input.set_sequence_number(2); - auto& tf = *input.mutable_token_transfer_coins(); - tf.set_to("0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c"); - tf.set_amount(10000); - tf.mutable_function()->set_account_address("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9"); - tf.mutable_function()->set_module("mee_coin"); - tf.mutable_function()->set_name("MeeCoin"); - input.set_max_gas_amount(2000); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("e7f56c77189e03699a75d8ec5c090e41f3d9d4783bc49c33df8a93d915e10de8")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - ASSERT_EQ(hex(result.raw_txn()), "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d"); - ASSERT_EQ(hex(result.encoded()), "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "2000", - "payload": { - "arguments": ["0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c","10000"], - "function": "0x1::aptos_account::transfer_coins", - "type": "entry_function_payload", - "type_arguments": ["0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin"] - }, - "sender": "0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", - "sequence_number": "2", - "signature": { - "public_key": "0x62e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca8369", - "signature": "0x30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWAnySignerTests.cpp b/tests/chains/Aptos/TWAnySignerTests.cpp index 28f260970b6..44885c9f872 100644 --- a/tests/chains/Aptos/TWAnySignerTests.cpp +++ b/tests/chains/Aptos/TWAnySignerTests.cpp @@ -4,11 +4,10 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Aptos/Address.h" -#include "Aptos/Signer.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" +#include "proto/Aptos.pb.h" #include #include #include "TestUtilities.h" @@ -42,12 +41,12 @@ TEST(TWAnySignerAptos, TxSign) { "gas_unit_price": "100", "max_gas_amount": "3296766", "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], "function": "0x1::aptos_account::transfer", "type": "entry_function_payload", "type_arguments": [] }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", "sequence_number": "99", "signature": { "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", diff --git a/tests/chains/Aptos/TWAptosAddressTests.cpp b/tests/chains/Aptos/TWAptosAddressTests.cpp index 2b9e3fd8047..ec6612d0960 100644 --- a/tests/chains/Aptos/TWAptosAddressTests.cpp +++ b/tests/chains/Aptos/TWAptosAddressTests.cpp @@ -28,7 +28,7 @@ TEST(TWAptosAddress, HDWallet) { auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeAptos)); auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressStr, "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + assertStringsEqual(addressStr, "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); } } // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TransactionPayloadTests.cpp b/tests/chains/Aptos/TransactionPayloadTests.cpp deleted file mode 100644 index 45b501230c3..00000000000 --- a/tests/chains/Aptos/TransactionPayloadTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright 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 - -namespace TW::Aptos::tests { - -TEST(AptosTransactionPayload, PancakeSwapPayload) { - auto pancakeSwapPayload=R"( - { -"arguments": [ - "0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6" -], -"function": "0xc23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d50::IFO::release", -"type": "entry_function_payload", -"type_arguments": [ - "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::BUSD", - "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::DAI", - "0x9936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a::uints::U1" -] -} -)"_json; - - TransactionPayload payload = EntryFunction::from_json(pancakeSwapPayload); - BCS::Serializer serializer; - Address sender("0x2ce519d8cd60e0870e874e8000e8cbc87c8172e6acdbec83662b4c8cc3fc3de9"); - std::uint64_t sequenceNumber{75}; - std::uint64_t gasAmount{488130}; - std::uint64_t gasPrice{100}; - std::uint64_t expirationTime{199940521552}; - std::uint8_t chainId{1}; - serializer << sender << sequenceNumber << payload << gasAmount << gasPrice << expirationTime << chainId; - ASSERT_EQ(hex(serializer.bytes), "2ce519d8cd60e0870e874e8000e8cbc87c8172e6acdbec83662b4c8cc3fc3de94b0000000000000002c23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d500349464f0772656c65617365030748e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced9051705636f696e730442555344000748e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced9051705636f696e730344414900079936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a0575696e747302553100012120c95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6c2720700000000006400000000000000503e628d2e00000001"); -} - -TEST(AptosTransactionPayload, PayLoadBasis) { - ModuleId module(Address::one(), "coin"); - std::uint64_t amount{1000}; - Address to("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); - BCS::Serializer serializer; - serializer << to; - std::vector args; - args.emplace_back(serializer.bytes); - serializer.clear(); - serializer << amount; - args.emplace_back(serializer.bytes); - TransactionPayload payload = EntryFunction(module, "transfer", {gTransferTag}, args); - ASSERT_EQ(std::get(payload).module().name(), "coin"); - ASSERT_EQ(std::get(payload).module().shortString(), "0x1::coin"); - serializer.clear(); - serializer << payload; - ASSERT_EQ(hex(serializer.bytes), "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"); -} - -} // namespace TW::Aptos::tests diff --git a/tests/chains/BinanceSmartChain/SignerTests.cpp b/tests/chains/BinanceSmartChain/SignerTests.cpp index 1ce61b7fd27..c970d687488 100644 --- a/tests/chains/BinanceSmartChain/SignerTests.cpp +++ b/tests/chains/BinanceSmartChain/SignerTests.cpp @@ -5,14 +5,13 @@ // file LICENSE at the root of the source code distribution tree. #include -#include "Ethereum/Signer.h" -#include "Ethereum/Transaction.h" #include "Ethereum/Address.h" -#include "Ethereum/ABI.h" +#include "Ethereum/ABI/Function.h" #include "proto/Ethereum.pb.h" #include "HexCoding.h" #include "uint256.h" #include "TestUtilities.h" +#include "PrivateKey.h" #include @@ -21,33 +20,39 @@ namespace TW::Binance { TEST(BinanceSmartChain, SignNativeTransfer) { // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e - auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto transaction = Ethereum::TransactionNonTyped::buildNativeTransfer( - /* nonce: */ 0, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ toAddress, - /* amount: */ 10000000000000000 // 0.01 - ); + Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(97)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(20000000000)); + auto gasLimit = store(uint256_t(21000)); + auto toAddress = "0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"; + auto amount = store(uint256_t(10000000000000000)); // 0.01 // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note - auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - uint256_t chainID = 97; - auto signature = Ethereum::Signer::sign(privateKey, chainID, transaction); + auto privateKey = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(toAddress); + input.set_private_key(privateKey.data(), privateKey.size()); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(amount.data(), amount.size()); + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSmartChain); - auto encoded = transaction->encoded(signature, chainID); - ASSERT_EQ(hex(encoded), "f86c808504a817c8008252089431be00eb1fc8e14a696dbc72f746ec3e95f49683872386f26fc100008081e5a057806b486844c5d0b7b5ce34b289f4e8776aa1fe24a3311cef5053995c51050ca07697aa0695de27da817625df0e7e4c64b0ab22d9df30aec92299a7b380be8db7"); + ASSERT_EQ(hex(output.encoded()), "f86c808504a817c8008252089431be00eb1fc8e14a696dbc72f746ec3e95f49683872386f26fc100008081e5a057806b486844c5d0b7b5ce34b289f4e8776aa1fe24a3311cef5053995c51050ca07697aa0695de27da817625df0e7e4c64b0ab22d9df30aec92299a7b380be8db7"); } TEST(BinanceSmartChain, SignTokenTransfer) { - auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto func = Ethereum::ABI::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 toAddress = "0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"; + auto funcData = Ethereum::ABI::Function::encodeFunctionCall("transfer", Ethereum::ABI::BaseParams{ + std::make_shared(toAddress), + std::make_shared(uint256_t(10000000000000000)) + }).value(); + EXPECT_EQ(hex(funcData), "a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc10000"); auto input = Ethereum::Proto::SigningInput(); auto chainId = store(uint256_t(97)); @@ -66,7 +71,7 @@ TEST(BinanceSmartChain, SignTokenTransfer) { input.set_to_address(tokenContractAddress); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); - transfer.set_data(payloadFunction.data(), payloadFunction.size()); + transfer.set_data(funcData.data(), funcData.size()); const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; diff --git a/tests/chains/Bitcoin/TWSegwitAddressTests.cpp b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp index 6b21a066205..9723244292c 100644 --- a/tests/chains/Bitcoin/TWSegwitAddressTests.cpp +++ b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp @@ -60,7 +60,7 @@ TEST(TWSegwitAddress, InitWithAddress) { ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); - auto witness = WRAPS(TWSegwitAddressWitnessProgram(address.get())); + auto witness = WRAPD(TWSegwitAddressWitnessProgram(address.get())); ASSERT_EQ(TW::hex(TW::data(TWDataBytes(witness.get()), TWDataSize(witness.get()))), "751e76e8199196d454941c45d1b3a323f1433bd6"); } diff --git a/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp b/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp index fa6793727c9..f71f90c5cff 100644 --- a/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp +++ b/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp @@ -5,11 +5,11 @@ // file LICENSE at the root of the source code distribution tree. #include "proto/Cosmos.pb.h" -#include "Cosmos/Signer.h" #include "Cosmos/Address.h" #include "HexCoding.h" #include "PublicKey.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" #include #include @@ -72,7 +72,8 @@ TEST(CryptoorgSigner, SignTx_DDCCE4) { auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Cosmos::Signer::sign(input, TWCoinTypeCryptoOrg); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCryptoOrg); assertJSONEqual(output.json(), R"( { @@ -125,7 +126,7 @@ TEST(CryptoorgSigner, SignJson) { auto inputJson = R"({"accountNumber":"125798","chainId":"crypto-org-chain-mainnet-1","fee":{"amounts":[{"denom":"basecro","amount":"5000"}],"gas":"200000"},"messages":[{"sendCoinsMessage":{"fromAddress":"cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0","toAddress":"cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus","amounts":[{"denom":"basecro","amount":"100000000"}]}}]})"; auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); - auto outputJson = Cosmos::Signer::signJSON(inputJson, privateKey, TWCoinTypeCryptoOrg); + auto outputJson = TW::anySignJSON(TWCoinTypeCryptoOrg, inputJson, privateKey); assertJSONEqual(outputJson, R"( { diff --git a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp index 13444945f39..dafa5aee10a 100644 --- a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp @@ -39,7 +39,9 @@ TEST(TWAnySignerJuno, Sign) { amountOfFee->set_amount("1000"); Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeJuno); + + EXPECT_EQ(output.error(), Common::Proto::OK); // https://www.mintscan.io/juno/txs/3DCE6AAF19657BCF11D44FD6BE124D57B44E04CA34851DE0ECCE619F70ECC46F auto expectedJson = R"( diff --git a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp index 822897296e8..2713fcbba7e 100644 --- a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp @@ -20,7 +20,7 @@ TEST(TWEvmosCoinType, TWCoinTypeNativeEvmos) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeEvmos)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeEvmos), 18); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); + ASSERT_EQ(TWBlockchainNativeEvmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); assertStringsEqual(symbol, "EVMOS"); assertStringsEqual(txUrl, "https://mintscan.io/evmos/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); diff --git a/tests/chains/Cosmos/NativeInjective/SignerTests.cpp b/tests/chains/Cosmos/NativeInjective/SignerTests.cpp index 46ed18809ba..a103ff8d09a 100644 --- a/tests/chains/Cosmos/NativeInjective/SignerTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/SignerTests.cpp @@ -8,12 +8,10 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "TestUtilities.h" -#include +#include "TrustWalletCore/TWAnySigner.h" #include -#include namespace TW::Cosmos::nativeInjective::tests { @@ -46,7 +44,8 @@ TEST(NativeInjectiveSigner, Sign) { auto privateKey = parse_hex("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeNativeInjective); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeInjective); // https://www.mintscan.io/injective/txs/135DD2C4A1910E4334A9C0F15125DA992E724EBF23FEB9638FCB71218BB064A5 assertJSONEqual(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw==\"}"); diff --git a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp index 0b152ca68be..f92a2f9ca7e 100644 --- a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp @@ -28,7 +28,7 @@ TEST(TWNativeInjectiveCoinType, TWCoinType) { assertStringsEqual(name, "Native Injective"); assertStringsEqual(symbol, "INJ"); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); - ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainNativeInjective); ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); assertStringsEqual(chainId, "injective-1"); diff --git a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp index a7591f1b786..163b19dfaf1 100644 --- a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Base64.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" #include "proto/TransactionCompiler.pb.h" @@ -95,8 +94,7 @@ TEST(NativeInjectiveCompiler, CompileWithSignatures) { EXPECT_EQ(output.error(), Common::Proto::OK); EXPECT_EQ(output.serialized(), expectedTx); - EXPECT_EQ(output.signature(), ""); - EXPECT_EQ(hex(output.signature()), ""); + EXPECT_EQ(hex(output.signature()), "f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421"); } } diff --git a/tests/chains/Cosmos/Osmosis/SignerTests.cpp b/tests/chains/Cosmos/Osmosis/SignerTests.cpp index d7935f190de..b0ae1832083 100644 --- a/tests/chains/Cosmos/Osmosis/SignerTests.cpp +++ b/tests/chains/Cosmos/Osmosis/SignerTests.cpp @@ -5,10 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "PublicKey.h" #include "proto/Cosmos.pb.h" +#include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" #include @@ -45,7 +45,8 @@ TEST(OsmosisSigner, SignTransfer_81B4) { auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeOsmosis); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeOsmosis); // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs diff --git a/tests/chains/Cosmos/ProtobufTests.cpp b/tests/chains/Cosmos/ProtobufTests.cpp deleted file mode 100644 index 009bf227afd..00000000000 --- a/tests/chains/Cosmos/ProtobufTests.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base64.h" -#include "Cosmos/Address.h" -#include "Cosmos/Protobuf/authz_tx.pb.h" -#include "Cosmos/Protobuf/bank_tx.pb.h" -#include "Cosmos/Protobuf/tx.pb.h" -#include "Cosmos/ProtobufSerialization.h" -#include "Data.h" -#include "HexCoding.h" - -#include "Protobuf/Article.pb.h" -#include "TestUtilities.h" - -#include -#include - -#include - -namespace TW::Cosmos::tests { - -using json = nlohmann::json; - -TEST(CosmosProtobuf, SendMsg) { - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - msgSend.set_to_address("cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"); - auto coin = msgSend.add_amount(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto txBody = cosmos::TxBody(); - txBody.add_messages()->PackFrom(msgSend); - txBody.set_memo(""); - txBody.set_timeout_height(0); - - const auto serialized = data(txBody.SerializeAsString()); - EXPECT_EQ(hex(serialized), "0a9c010a2f747970652e676f6f676c65617069732e636f6d2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131"); - EXPECT_EQ(Base64::encode(serialized), "CpwBCi90eXBlLmdvb2dsZWFwaXMuY29tL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJpCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISLWNvc21vczF6dDUwYXp1cGFucWxmYW01YWZodjNoZXh3eXV0bnVrZWg0YzU3MxoJCgRtdW9uEgEx"); - - std::string json; - google::protobuf::util::MessageToJsonString(txBody, &json); - assertJSONEqual(json, R"({"messages":[{"@type":"type.googleapis.com/cosmos.bank.v1beta1.MsgSend","amount":[{"amount":"1","denom":"muon"}],"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}]})"); -} - -TEST(CosmosProtobuf, ExecuteContractMsg) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); // obsolete - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_execute_contract_message(); - message.set_sender(fromAddress.string()); - message.set_contract(toAddress.string()); - message.set_execute_msg("transfer"); - auto* coin = message.add_coins(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto body = Protobuf::buildProtoTxBody(input); - - EXPECT_EQ(hex(body), "0a9d010a262f74657272612e7761736d2e763162657461312e4d736745786563757465436f6e747261637412730a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a087472616e736665722a090a046d756f6e120131"); -} - -TEST(CosmosProtobuf, DeterministicSerialization_Article) { - // https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-027-deterministic-protobuf-serialization.md - auto article = blog::Article(); - article.set_title("The world needs change 🌳"); - article.set_description(""); - article.set_created(1596806111080); - article.set_updated(0); - article.set_public_(true); - article.set_promoted(false); - article.set_type(blog::NEWS); - article.set_review(blog::REVIEW_UNSPECIFIED); - *article.add_comments() = "Nice one"; - *article.add_comments() = "Thank you"; - - const auto serialized = data(article.SerializeAsString()); - EXPECT_EQ(hex(serialized), "0a1b54686520776f726c64206e65656473206368616e676520f09f8cb318e8bebec8bc2e280138024a084e696365206f6e654a095468616e6b20796f75"); -} - -} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Secret/SignerTests.cpp b/tests/chains/Cosmos/Secret/SignerTests.cpp index 997ab2d3886..8ccb283c938 100644 --- a/tests/chains/Cosmos/Secret/SignerTests.cpp +++ b/tests/chains/Cosmos/Secret/SignerTests.cpp @@ -5,9 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" +#include "TrustWalletCore/TWAnySigner.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "proto/Cosmos.pb.h" #include "PublicKey.h" #include "TestUtilities.h" @@ -45,7 +46,8 @@ TEST(SecretSigner, Sign) { auto privateKey = parse_hex("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeSecret); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeSecret); // https://www.mintscan.io/secret/txs/01F4BD2458BF966F287533775C8D67BBC7CA7214CAEB1752D270A90223E9E82F // curl -H 'Content-Type: application/json' --data-binary "{\"tx_bytes\":\"CpIB...c4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}" https://scrt-lcd.blockpane.com/cosmos/tx/v1beta1/txs diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp index dc45bc5cd1c..20ec6a027a7 100644 --- a/tests/chains/Cosmos/SignerTests.cpp +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -9,7 +9,7 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" +#include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" #include "Cosmos/Protobuf/bank_tx.pb.h" #include "Cosmos/Protobuf/coin.pb.h" @@ -52,7 +52,8 @@ TEST(CosmosSigner, SignTxProtobuf) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); @@ -78,9 +79,11 @@ TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); - EXPECT_EQ(output.error_message(), "Error: No message found"); + // TODO +// EXPECT_EQ(output.error_message(), "Error: No message found"); EXPECT_EQ(output.serialized(), ""); EXPECT_EQ(output.json(), ""); EXPECT_EQ(hex(output.signature()), ""); @@ -119,7 +122,8 @@ TEST(CosmosSigner, SignTxJson) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); // the sample tx on testnet // https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json @@ -128,47 +132,6 @@ TEST(CosmosSigner, SignTxJson) { EXPECT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); } -TEST(CosmosSigner, SignTxJsonWithExecuteContractMsg) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); // obsolete - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_execute_contract_message(); - message.set_sender(fromAddress.string()); - message.set_contract(toAddress.string()); - message.set_execute_msg("transfer"); - auto* coin = message.add_coins(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - EXPECT_EQ("{\"accountNumber\":\"1037\",\"chainId\":\"gaia-13003\",\"fee\":{\"amounts\":[{\"denom\":\"muon\",\"amount\":\"200\"}],\"gas\":\"200000\"},\"sequence\":\"8\",\"messages\":[{\"executeContractMessage\":{\"sender\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"contract\":\"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573\",\"executeMsg\":\"transfer\",\"coins\":[{\"denom\":\"muon\",\"amount\":\"1\"}]}}]}", json); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - EXPECT_EQ("{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"wasm/MsgExecuteContract\",\"value\":{\"coins\":[{\"amount\":\"1\",\"denom\":\"muon\"}],\"contract\":\"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573\",\"execute_msg\":\"transfer\",\"sender\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"9iQTB1Jjw8FuYPwgzVLbs1cABGYFlk3JRKGQyojQcwY/ni+9D/ViNQMb+4UuokYi74GnpPZpH5RqbJ2ju6VL2g==\"}]}}", output.json()); - - EXPECT_EQ(hex(output.signature()), "f62413075263c3c16e60fc20cd52dbb35700046605964dc944a190ca88d073063f9e2fbd0ff56235031bfb852ea24622ef81a7a4f6691f946a6c9da3bba54bda"); -} - TEST(CosmosSigner, SignTxJsonWithRawJSONMsg) { auto input = Proto::SigningInput(); input.set_signing_mode(Proto::JSON); // obsolete @@ -199,7 +162,8 @@ TEST(CosmosSigner, SignTxJsonWithRawJSONMsg) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); EXPECT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"test\",\"value\":{\"test\":\"hello\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA==\"}]}}"); @@ -236,13 +200,15 @@ TEST(CosmosSigner, SignTxJson_WithMode) { input.set_private_key(privateKey.data(), privateKey.size()); { - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); EXPECT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); EXPECT_EQ(output.error_message(), ""); } input.set_mode(Proto::BroadcastMode::SYNC); { - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); EXPECT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); EXPECT_EQ(output.error_message(), ""); } @@ -281,7 +247,8 @@ TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs @@ -314,7 +281,8 @@ TEST(CosmosSigner, SignDirect1) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); @@ -359,7 +327,8 @@ TEST(CosmosSigner, SignDirect_0a90010a) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766"); @@ -391,7 +360,9 @@ TEST(CosmosSigner, MsgVote) { auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + auto expected = R"( {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"})"; assertJSONEqual(output.serialized(), expected); diff --git a/tests/chains/Cosmos/StakingTests.cpp b/tests/chains/Cosmos/StakingTests.cpp index 91e097e0342..ad800b54184 100644 --- a/tests/chains/Cosmos/StakingTests.cpp +++ b/tests/chains/Cosmos/StakingTests.cpp @@ -7,10 +7,10 @@ #include "Base64.h" #include "Coin.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" #include "TestUtilities.h" +#include #include namespace TW::Cosmos::tests { @@ -42,11 +42,14 @@ TEST(CosmosStaking, CompoundingAuthz) { auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI= auto expected = R"( { "mode":"BROADCAST_MODE_BLOCK", - "tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=" + "tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=" })"; assertJSONEqual(output.serialized(), expected); } @@ -75,7 +78,8 @@ TEST(CosmosStaking, RevokeCompoundingAuthz) { auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); auto expected = R"( { "mode":"BROADCAST_MODE_BLOCK", @@ -109,7 +113,8 @@ TEST(CosmosStaking, Staking) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8"); @@ -117,7 +122,8 @@ TEST(CosmosStaking, Staking) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -149,7 +155,8 @@ TEST(CosmosStaking, Unstaking) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9"); @@ -157,7 +164,7 @@ TEST(CosmosStaking, Unstaking) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -191,7 +198,8 @@ TEST(CosmosStaking, Restaking) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318"); @@ -199,7 +207,7 @@ TEST(CosmosStaking, Restaking) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -228,7 +236,8 @@ TEST(CosmosStaking, Withdraw) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39"); @@ -236,7 +245,7 @@ TEST(CosmosStaking, Withdraw) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -265,7 +274,8 @@ TEST(CosmosStaking, SetWithdrawAddress) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cp4BCpsBCjIvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1NldFdpdGhkcmF3QWRkcmVzcxJlCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3ASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpAkm2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="})"); EXPECT_EQ(hex(output.signature()), "926d9324bc3815c2303796e4a956866e60209134877980fcb14908c891cb6bcf5c3ef4e19053bf94a97104c97650032cd3a84be9c61c9783f1f81eaba45741a4"); @@ -273,7 +283,7 @@ TEST(CosmosStaking, SetWithdrawAddress) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "22cfbcec33d06ed42623264049d11d6fb86566103d5621a23b1444022eb1aace3a0790a1c46b48c0218689616daf97f99ae72c3589966205de45b57194fbada2"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); diff --git a/tests/chains/Cosmos/THORChain/SignerTests.cpp b/tests/chains/Cosmos/THORChain/SignerTests.cpp index 75f86efda13..fcb07cae3a9 100644 --- a/tests/chains/Cosmos/THORChain/SignerTests.cpp +++ b/tests/chains/Cosmos/THORChain/SignerTests.cpp @@ -5,10 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include "proto/Cosmos.pb.h" -#include "THORChain/Signer.h" +#include "Coin.h" #include "HexCoding.h" #include "Bech32Address.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TrustWalletCore/TWCoinType.h" #include #include @@ -80,7 +82,8 @@ TEST(THORChainSigner, SignTx_Protobuf_7E480F) { auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = THORChain::Signer::sign(input); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoO..89g="}' https:///cosmos/tx/v1beta1/txs @@ -158,7 +161,8 @@ TEST(THORChainSigner, SignTx_MsgDeposit) { auto privateKey = parse_hex("2659e41d54ebd449d68b9d58510d8eeeb837ee00d6ecc760b7a731238d8c3113"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = THORChain::Signer::sign(input); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); // https://viewblock.io/thorchain/tx/0162213E7F9D85965B1C57FA3BF9603C655B542F358318303A7B00661AE42510 // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUBCoIB..hiw="}' https:///cosmos/tx/v1beta1/txs @@ -225,7 +229,8 @@ TEST(THORChainSigner, SignTx_Json_Deprecated) { auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = THORChain::Signer::sign(input); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); assertJSONEqual(output.json(), R"( { @@ -275,7 +280,7 @@ TEST(THORChainSigner, SignJson) { auto inputJson = R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})"; auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - auto outputJson = THORChain::Signer::signJSON(inputJson, privateKey); + auto outputJson = TW::anySignJSON(TWCoinTypeTHORChain, inputJson, privateKey); EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", outputJson); } diff --git a/tests/chains/Cosmos/THORChain/SwapTests.cpp b/tests/chains/Cosmos/THORChain/SwapTests.cpp index 3bcebb89c8c..69901c310b2 100644 --- a/tests/chains/Cosmos/THORChain/SwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/SwapTests.cpp @@ -8,8 +8,6 @@ #include "Bitcoin/Script.h" #include "Bitcoin/SegwitAddress.h" #include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamAddress.h" -#include "Ethereum/ABI/ParamBase.h" #include "Ethereum/Address.h" #include "THORChain/Swap.h" #include "proto/Binance.pb.h" @@ -393,6 +391,59 @@ TEST(THORChainSwap, SwapBtcBnb) { // https://explorer.binance.org/tx/8D78469069118E9B9546696214CCD46E63D3FA0D7E854C094D63C8F6061278B7 } +TEST(THORChainSwap, SwapUsdtBsc) { + auto myAddress = "0x0d6aA74992eDDaaf430eadca63B87f4C99Aef8dE"; + auto vaultAddress = "0x1f3b3c6ac151bf32409fe139a5d55f3d9444729c"; + auto routerAddress = "0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146"; + auto usdtTokenId = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; + auto amount = 70000000; + auto expirationTime = 1775669796; + + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ETH)); + fromAsset.set_symbol("USDT"); + fromAsset.set_token_id(usdtTokenId); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BSC)); + toAsset.set_symbol("BSC"); + toAsset.set_token_id("BNB"); + + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(myAddress) + .toAddress(myAddress) + .vault(vaultAddress) + .router(routerAddress) + .fromAmount(std::to_string(amount)) + .expirationPolicy(expirationTime) + .affFeeAddress("tr") + .affFeeRate("0") + .streamInterval("1") + .streamQuantity("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.to_address(), routerAddress); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + auto funcData = Ethereum::ABI::Function::encodeFunctionCall("depositWithExpiry", Ethereum::ABI::BaseParams{ + std::make_shared(vaultAddress), + std::make_shared(usdtTokenId), + std::make_shared(uint256_t(amount)), + std::make_shared("=:BSC.BNB:0x0d6aA74992eDDaaf430eadca63B87f4C99Aef8dE:0/1/0:tr:0"), + std::make_shared(uint256_t(expirationTime)) + }).value(); + EXPECT_EQ(hex(funcData), "44bc937b0000000000000000000000001f3b3c6ac151bf32409fe139a5d55f3d9444729c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000042c1d8000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a4253432e424e423a3078306436614137343939326544446161663433306561646361363342383766344339394165663864453a302f312f303a74723a3000"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().amount())), "00"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(funcData)); +} + TEST(THORChainSwap, SwapAtomBnb) { Proto::Asset fromAsset; fromAsset.set_chain(static_cast(Chain::ATOM)); @@ -484,19 +535,17 @@ TEST(THORChainSwap, SwapErc20Rune) { EXPECT_EQ(tx.to_address(), "0x8f66c4ae756bebc49ec8b81966dd8bba9f127549"); ASSERT_TRUE(tx.transaction().has_contract_generic()); - Data vaultAddressBin = SwapTest_ethAddressStringToData("0xa56f6Cb1D66cd80150b1ea79643b4C5900D6E36E"); - EXPECT_EQ(hex(vaultAddressBin), "a56f6cb1d66cd80150b1ea79643b4c5900d6e36e"); - auto func = Ethereum::ABI::Function("depositWithExpiry", std::vector>{ - std::make_shared(vaultAddressBin), - std::make_shared(parse_hex("0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E")), - std::make_shared(uint256_t(1000000)), - std::make_shared("=:THOR.RUNE:thor1ad6hapypumu7su5ad9qry2d74yt9d56fssa774:51638857:t:0"), - std::make_shared(uint256_t(1775669796))}); - Data payload; - func.encode(payload); - EXPECT_EQ(hex(payload), "44bc937b000000000000000000000000a56f6cb1d66cd80150b1ea79643b4c5900d6e36e000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d6922400000000000000000000000000000000000000000000000000000000000000443d3a54484f522e52554e453a74686f72316164366861707970756d753773753561643971727932643734797439643536667373613737343a35313633383835373a743a3000000000000000000000000000000000000000000000000000000000"); + auto vaultAddress = "0xa56f6Cb1D66cd80150b1ea79643b4C5900D6E36E"; + auto funcData = Ethereum::ABI::Function::encodeFunctionCall("depositWithExpiry", Ethereum::ABI::BaseParams{ + std::make_shared(vaultAddress), + std::make_shared("0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"), + std::make_shared(uint256_t(1000000)), + std::make_shared("=:THOR.RUNE:thor1ad6hapypumu7su5ad9qry2d74yt9d56fssa774:51638857:t:0"), + std::make_shared(uint256_t(1775669796)) + }).value(); + EXPECT_EQ(hex(funcData), "44bc937b000000000000000000000000a56f6cb1d66cd80150b1ea79643b4c5900d6e36e000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d6922400000000000000000000000000000000000000000000000000000000000000443d3a54484f522e52554e453a74686f72316164366861707970756d753773753561643971727932643734797439643536667373613737343a35313633383835373a743a3000000000000000000000000000000000000000000000000000000000"); EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().amount())), "00"); - EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(payload)); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(funcData)); EXPECT_EQ(hex(TW::data(tx.private_key())), ""); @@ -523,6 +572,62 @@ TEST(THORChainSwap, SwapErc20Rune) { // https://thorchain.net/tx/B5E88D61157E7073995CA8729B75DAB2C1684A7B145DB711327CA4B8FF7DBDE7 } +TEST(THORChainSwap, SwapBscBnb) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BSC)); + fromAsset.set_token_id("0x0000000000000000000000000000000000000000"); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("0xf8192E9c51c070d199a8F262c12DDD1034274083") + .toAddress("bnb1tjcup6q8nere6r0pdt2ucc4g0xcrhm0jy5xql8") + .vault("0xcBE4334E4a0fC7C5Fa8083223B28a4b9F695A06C") + .router("0xb30eC53F98ff5947EDe720D32aC2da7e52A5f56b") + .fromAmount("10000000000000000") + .toAmountLimit("100000") + .expirationPolicy(1775669796) + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "0a01001201002201002a0100422a3078623330654335334639386666353934374544653732304433326143326461376535324135663536625293023290020a072386f26fc1000012840244bc937b000000000000000000000000cbe4334e4a0fc7c5fa8083223b28a4b9f695a06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a424e422e424e423a626e6231746a6375703671386e65726536723070647432756363346730786372686d306a793578716c383a3130303030303a743a3000"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + + // check fields + EXPECT_EQ(tx.to_address(), "0xb30eC53F98ff5947EDe720D32aC2da7e52A5f56b"); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(56)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(0)); + tx.set_nonce(nonce.data(), nonce.size()); + // 0,000000001 + auto gasPrice = store(uint256_t(3000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(50000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("74c452b55e0da4139172bc3b32bec469cfefbcdce373edda8e33afcfbf9c0a87"); + tx.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeSmartChain); + EXPECT_EQ(hex(output.encoded()), "f901718084b2d05e0082c35094b30ec53f98ff5947ede720d32ac2da7e52a5f56b872386f26fc10000b9010444bc937b000000000000000000000000cbe4334e4a0fc7c5fa8083223b28a4b9f695a06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a424e422e424e423a626e6231746a6375703671386e65726536723070647432756363346730786372686d306a793578716c383a3130303030303a743a30008194a05b0032d4150a3fa3b39a047648c02cb44b3256b9c34b7780265643c33d2aa2c6a017fece0465a271b7bddf655f7ac77419fb0433f9acf64b455b9aa17183b6eb98"); + // https://viewblock.io/thorchain/tx/4292A5068BAA5619CF7A35861058915423688DF3CAE8F241453D8FCC6E0BF0A9 + // https://bscscan.com/tx/0x4292a5068baa5619cf7a35861058915423688df3cae8f241453d8fcc6e0bf0a9 + // https://explorer.bnbchain.org/tx/88A1B6F9D64F3B48CE1107979CD325E817446C5D6729EE6FC917589A6FADA79D +} + TEST(THORChainSwap, SwapAvaxBnb) { Proto::Asset fromAsset; fromAsset.set_chain(static_cast(Chain::AVAX)); diff --git a/tests/chains/Cosmos/Terra/SignerTests.cpp b/tests/chains/Cosmos/Terra/SignerTests.cpp index 2d6373de942..aa6004567da 100644 --- a/tests/chains/Cosmos/Terra/SignerTests.cpp +++ b/tests/chains/Cosmos/Terra/SignerTests.cpp @@ -8,10 +8,9 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "Cosmos/ProtobufSerialization.h" #include "uint256.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" #include #include @@ -52,7 +51,8 @@ TEST(TerraClassicSigner, SignSendTx) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); assertJSONEqual(output.json(), R"( { @@ -161,7 +161,8 @@ TEST(TerraClassicSigner, SignWasmTransferTxProtobuf_9FF3F0) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs @@ -206,7 +207,8 @@ TEST(TerraClassicSigner, SignWasmTransferTxJson_078E90) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E // curl -H 'Content-Type: application/json' --data-binary '{"mode": "block","tx":{...}}' https:///txs @@ -284,7 +286,8 @@ TEST(TerraClassicSigner, SignWasmGeneric_EC4F85) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "Cu4BC...iVt"})' https:///cosmos/tx/v1beta1/txs @@ -333,7 +336,8 @@ TEST(TerraClassicSigner, SignWasmGenericWithCoins_6651FC) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CrIBCq8B.....0NWg=="})' https:///cosmos/tx/v1beta1/txs @@ -413,7 +417,8 @@ TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs @@ -427,23 +432,4 @@ TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { EXPECT_EQ(output.json(), ""); } -TEST(TerraClassicSigner, SignWasmTerraTransferPayload) { - auto proto = Proto::Message_WasmTerraExecuteContractTransfer(); - proto.set_recipient_address("recipient=address"); - const auto amount = store(uint256_t(250000), 0); - proto.set_amount(amount.data(), amount.size()); - - const auto payload = Protobuf::wasmTerraExecuteTransferPayload(proto); - - assertJSONEqual(payload.dump(), R"( - { - "transfer": - { - "amount": "250000", - "recipient": "recipient=address" - } - } - )"); -} - } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TerraV2/SignerTests.cpp b/tests/chains/Cosmos/TerraV2/SignerTests.cpp index cfd8dd8dde4..b6f94de7972 100644 --- a/tests/chains/Cosmos/TerraV2/SignerTests.cpp +++ b/tests/chains/Cosmos/TerraV2/SignerTests.cpp @@ -8,9 +8,8 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "Cosmos/ProtobufSerialization.h" #include "uint256.h" +#include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" #include @@ -84,7 +83,8 @@ TEST(TerraSigner, SignSendTx) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); // similar tx: https://finder.terra.money/mainnet/tx/fbbe73ad2f0db3a13911dc424f8a34370dc4b7e8b66687f536797e68ee200ece assertJSONEqual(output.serialized(), R"( @@ -161,7 +161,8 @@ TEST(TerraSigner, SignWasmTransferTx) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -202,7 +203,8 @@ TEST(TerraSigner, SignWasmGeneric) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -248,7 +250,8 @@ TEST(TerraSigner, SignWasmGenericWithCoins) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -327,7 +330,8 @@ TEST(TerraSigner, SignWasmSendTx) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -340,23 +344,4 @@ TEST(TerraSigner, SignWasmSendTx) { EXPECT_EQ(output.json(), ""); } -TEST(TerraSigner, SignWasmTransferPayload) { - auto proto = Proto::Message_WasmExecuteContractTransfer(); - proto.set_recipient_address("recipient=address"); - const auto amount = store(uint256_t(250000), 0); - proto.set_amount(amount.data(), amount.size()); - - const auto payload = Protobuf::wasmExecuteTransferPayload(proto); - - assertJSONEqual(payload.dump(), R"( - { - "transfer": - { - "amount": "250000", - "recipient": "recipient=address" - } - } - )"); -} - } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Tia/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Tia/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..7a2c17bad5b --- /dev/null +++ b/tests/chains/Cosmos/Tia/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gTiaAddr = "celestia1mry47pkga5tdswtluy0m8teslpalkdq00tp7xc"; +static const std::string gTiaHrp = "celestia"; + +TEST(TWTiaAnyAddress, AllTiaAddressTests) { + CosmosAddressParameters parameters{.hrp = gTiaHrp, .coinType = TWCoinTypeTia, .address = gTiaAddr}; + TestCosmosAddressParameters(parameters); +} + +} + \ No newline at end of file diff --git a/tests/chains/Cosmos/Tia/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Tia/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a3d656fd5f6 --- /dev/null +++ b/tests/chains/Cosmos/Tia/TWCoinTypeTests.cpp @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TWTiaCoinType, TWCoinType) { + const auto coin = TWCoinTypeTia; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + assertStringsEqual(id, "tia"); + assertStringsEqual(name, "Celestia"); + assertStringsEqual(symbol, "TIA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "celestia"); + assertStringsEqual(txUrl, "https://www.mintscan.io/celestia/txs/FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B"); + assertStringsEqual(accUrl, "https://www.mintscan.io/celestia/account/celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TransactionCompilerTests.cpp b/tests/chains/Cosmos/TransactionCompilerTests.cpp index 4500533ab7a..49284252185 100644 --- a/tests/chains/Cosmos/TransactionCompilerTests.cpp +++ b/tests/chains/Cosmos/TransactionCompilerTests.cpp @@ -139,7 +139,7 @@ TEST(CosmosCompiler, CompileWithSignatures) { Cosmos::Proto::SigningOutput output; ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); EXPECT_EQ(output.serialized().size(), 0ul); - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); } /// Step 3: Obtain json preimage hash @@ -168,10 +168,6 @@ TEST(CosmosCompiler, CompileWithSignatures) { "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37"); signature = Base64::decode("tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="); - { - auto result = TW::anySignJSON(coin, jsonPreImage, privateKey.bytes); - EXPECT_EQ(result, "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[],\"gas\":\"0\"},\"memo\":\"\",\"msg\":[],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ\"},\"signature\":\"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g==\"}]}}"); - } { // JSON const Data outputData = TransactionCompiler::compileWithSignatures( @@ -180,6 +176,6 @@ TEST(CosmosCompiler, CompileWithSignatures) { ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); EXPECT_EQ(output.error(), Common::Proto::OK); - EXPECT_EQ(hex(output.serialized()), "7b226d6f6465223a22626c6f636b222c227478223a7b22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d7367223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c2276616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a6470227d7d5d2c227369676e617475726573223a5b7b227075625f6b6579223a7b2274797065223a2274656e6465726d696e742f5075624b6579536563703235366b31222c2276616c7565223a2241757a76584f51336f774c47663556476a65537a487a62704566526e312b616c4b30484234543464566a5a4a227d2c227369676e6174757265223a227454794f72627572724845486131347169773738653953746f5a7979476d6f6b7539384978597257436d744e38516f356d54654b6130424b4b44666747344c6d6d4e64775963725874715151374634644c33633236673d3d227d5d7d7d"); + EXPECT_EQ(hex(output.json()), "7b226d6f6465223a22626c6f636b222c227478223a7b22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d7367223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c2276616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a6470227d7d5d2c227369676e617475726573223a5b7b227075625f6b6579223a7b2274797065223a2274656e6465726d696e742f5075624b6579536563703235366b31222c2276616c7565223a2241757a76584f51336f774c47663556476a65537a487a62704566526e312b616c4b30484234543464566a5a4a227d2c227369676e6174757265223a227454794f72627572724845486131347169773738653953746f5a7979476d6f6b7539384978597257436d744e38516f356d54654b6130424b4b44666747344c6d6d4e64775963725874715151374634644c33633236673d3d227d5d7d7d"); } } diff --git a/tests/chains/Ethereum/AbiStructTests.cpp b/tests/chains/Ethereum/AbiStructTests.cpp deleted file mode 100644 index 923e853fd1b..00000000000 --- a/tests/chains/Ethereum/AbiStructTests.cpp +++ /dev/null @@ -1,930 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/ABI.h" -#include "Ethereum/Address.h" -#include "Ethereum/Signer.h" -#include "TestUtilities.h" -#include -#include - -#include -#include - -using namespace TW; - -extern std::string TESTS_ROOT; - -namespace TW::Ethereum::tests { - -using namespace ABI; - -std::string load_file(const std::string path) { - std::ifstream stream(path); - std::string content((std::istreambuf_iterator(stream)), (std::istreambuf_iterator())); - return content; -} - -// https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -// clang-format off -ParamStruct msgPersonCow2("Person", std::vector>{ - std::make_shared("name", std::make_shared("Cow")), - std::make_shared("wallets", std::make_shared(std::vector>{ - std::make_shared(parse_hex("CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826")), - std::make_shared(parse_hex("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF")) - })) -}); -ParamStruct msgPersonBob3("Person", std::vector>{ - std::make_shared("name", std::make_shared("Bob")), - std::make_shared("wallets", std::make_shared(std::vector>{ - std::make_shared(parse_hex("bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")), - std::make_shared(parse_hex("B0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57")), - std::make_shared(parse_hex("B0B0b0b0b0b0B000000000000000000000000000")) - })) -}); -ParamStruct msgGroup("Group", std::vector>{ - std::make_shared("name", std::make_shared("")), - std::make_shared("members", std::make_shared(std::vector>{ - std::make_shared(msgPersonCow2) - })) -}); -ParamStruct msgMailCow1Bob1("Mail", std::vector>{ - std::make_shared("from", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Cow")), - std::make_shared("wallet", std::make_shared(parse_hex("CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"))) - })), - std::make_shared("to", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Bob")), - std::make_shared("wallet", std::make_shared(parse_hex("bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"))) - })), - std::make_shared("contents", std::make_shared("Hello, Bob!")) -}); -ParamStruct msgMailCow2Bob3("Mail", std::vector>{ - std::make_shared("from", std::make_shared(msgPersonCow2)), - std::make_shared("to", std::make_shared(std::make_shared(msgPersonBob3))), - std::make_shared("contents", std::make_shared("Hello, Bob!")) -}); -ParamStruct gMsgEIP712Domain("EIP712Domain", std::vector>{ - std::make_shared("name", std::make_shared("Ether Mail")), - std::make_shared("version", std::make_shared("1")), - std::make_shared("chainId", std::make_shared(1)), - std::make_shared("verifyingContract", std::make_shared(parse_hex("CcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"))) -}); - -PrivateKey privateKeyCow = PrivateKey(Hash::keccak256(TW::data("cow"))); -PrivateKey privateKeyDragon = PrivateKey(Hash::keccak256(TW::data("dragon"))); -PrivateKey privateKeyOilTimes12 = PrivateKey(parse_hex("b0f20d59451a2fac1be6d458e036adfa5d83ebd4c21f9a76de3c4a3a65671eba")); // 0x60c2A43Cc69658eC4b02a65A07623D7192166F4e - -// See 'signedTypeData' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes) { - EXPECT_EQ(msgMailCow1Bob1.encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashType()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2"); - - EXPECT_EQ(hex(msgMailCow1Bob1.encodeHashes()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - - EXPECT_EQ(hex(gMsgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); - - Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); -} - -TEST(EthereumAbiStruct, encodeTypes_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallet", "type": "address"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": "0x01", - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - })"); - ASSERT_EQ(hex(hash), "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d"); - EXPECT_EQ(hex(store(rsv.s)), "07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -// See 'signedTypeData with V3 string' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes_v3) { - EXPECT_EQ(msgMailCow1Bob1.encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashType()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2"); - - EXPECT_EQ(hex(msgMailCow1Bob1.encodeHashes()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - - EXPECT_EQ(hex(gMsgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); - - Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); -} - -TEST(EthereumAbiStruct, encodeTypes_v3_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallet", "type": "address"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - })"); - ASSERT_EQ(hex(hash), "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d"); - EXPECT_EQ(hex(store(rsv.s)), "07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -// See 'signedTypeData_v4' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes_v4) { - EXPECT_EQ(msgGroup.encodeType(), "Group(string name,Person[] members)Person(string name,address[] wallets)"); - - EXPECT_EQ(msgPersonCow2.encodeType(), "Person(string name,address[] wallets)"); - - EXPECT_EQ(hex(msgPersonCow2.hashType()), "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860"); - - EXPECT_EQ(hex(msgPersonCow2.encodeHashes()), - "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" - "8c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648" - "8a8bfe642b9fc19c25ada5dadfd37487461dc81dd4b0778f262c163ed81b5e2a"); - - EXPECT_EQ(hex(msgPersonCow2.hashStruct()), "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"); - - EXPECT_EQ(hex(msgPersonBob3.encodeHashes()), - "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" - "28cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6" - "d2734f4c86cc3bd9cabf04c3097589d3165d95e4648fc72d943ed161f651ec6d"); - - EXPECT_EQ(hex(msgPersonBob3.hashStruct()), "efa62530c7ae3a290f8a13a5fc20450bdb3a6af19d9d9d2542b5a94e631a9168"); - - EXPECT_EQ(msgMailCow2Bob3.encodeType(), "Mail(Person from,Person[] to,string contents)Person(string name,address[] wallets)"); - - EXPECT_EQ(hex(msgMailCow2Bob3.hashType()), "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753"); - - EXPECT_EQ(hex(msgMailCow2Bob3.encodeHashes()), - "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753" - "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f" - "ca322beec85be24e374d18d582a6f2997f75c54e7993ab5bc07404ce176ca7cd" - "b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); - - EXPECT_EQ(hex(msgMailCow2Bob3.hashStruct()), "eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8"); - - EXPECT_EQ(hex(gMsgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); - - Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); -} - -TEST(EthereumAbiStruct, encodeTypes_v4_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallets", "type": "address[]"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person[]"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallets": [ - "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" - ] - }, - "to": [ - { - "name": "Bob", - "wallets": [ - "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - "B0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", - "B0B0b0b0b0b0B000000000000000000000000000" - ] - } - ], - "contents": "Hello, Bob!" - } - })"); - ASSERT_EQ(hex(hash), "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "65cbd956f2fae28a601bebc9b906cea0191744bd4c4247bcd27cd08f8eb6b71c"); - EXPECT_EQ(hex(store(rsv.s)), "78efdf7a31dc9abee78f492292721f362d296cf86b4538e07b51303b67f74906"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -// See 'signedTypeData_v4 with recursive types' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes_v4Rec) { - ParamStruct msgPersonRecursiveMother("Person", std::vector>{ - std::make_shared("name", std::make_shared("Lyanna")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Rickard")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{})) - })) - }); - ParamStruct msgPersonRecursiveFather("Person", std::vector>{ - std::make_shared("name", std::make_shared("Rhaegar")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Aeris II")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{})) - })) - }); - ParamStruct msgPersonRecursive("Person", std::vector>{ - std::make_shared("name", std::make_shared("Jon")), - std::make_shared("mother", std::make_shared(msgPersonRecursiveMother)), - std::make_shared("father", std::make_shared(msgPersonRecursiveFather)) - }); - - EXPECT_EQ(msgPersonRecursive.encodeType(), "Person(string name,Person mother,Person father)"); - - EXPECT_EQ(hex(msgPersonRecursive.hashType()), "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116"); - - EXPECT_EQ(hex(msgPersonRecursiveMother.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "afe4142a2b3e7b0503b44951e6030e0e2c5000ef83c61857e2e6003e7aef8570" - "0000000000000000000000000000000000000000000000000000000000000000" - "88f14be0dd46a8ec608ccbff6d3923a8b4e95cdfc9648f0db6d92a99a264cb36"); - - EXPECT_EQ(hex(msgPersonRecursiveMother.hashStruct()), "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b"); - - EXPECT_EQ(hex(msgPersonRecursiveFather.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "b2a7c7faba769181e578a391a6a6811a3e84080c6a3770a0bf8a856dfa79d333" - "0000000000000000000000000000000000000000000000000000000000000000" - "02cc7460f2c9ff107904cff671ec6fee57ba3dd7decf999fe9fe056f3fd4d56e"); - - EXPECT_EQ(hex(msgPersonRecursiveFather.hashStruct()), "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); - - EXPECT_EQ(hex(msgPersonRecursive.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "e8d55aa98b6b411f04dbcf9b23f29247bb0e335a6bc5368220032fdcb9e5927f" - "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b" - "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); - - EXPECT_EQ(hex(msgPersonRecursive.hashStruct()), "fdc7b6d35bbd81f7fa78708604f57569a10edff2ca329c8011373f0667821a45"); - - ParamStruct msgEIP712Domain("EIP712Domain", std::vector>{ - std::make_shared("name", std::make_shared("Family Tree")), - std::make_shared("version", std::make_shared("1")), - std::make_shared("chainId", std::make_shared(1)), - std::make_shared("verifyingContract", std::make_shared(parse_hex("CcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"))) - }); - - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "facb2c1888f63a780c84c216bd9a81b516fc501a19bae1fc81d82df590bbdc60"); - - Address address = Address(privateKeyDragon.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0x065a687103C9F6467380beE800ecD70B17f6b72F"); -} - -TEST(EthereumAbiStruct, encodeTypes_v4Rec_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "mother", "type": "Person"}, - {"name": "father", "type": "Person"} - ] - }, - "primaryType": "Person", - "domain": { - "name": "Family Tree", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "name": "Jon", - "mother": { - "name": "Lyanna", - "father": { - "name": "Rickard" - } - }, - "father": { - "name": "Rhaegar", - "father": { - "name": "Aeris II" - } - } - } - })"); - ASSERT_EQ(hex(hash), "807773b9faa9879d4971b43856c4d60c2da15c6f8c062bd9d33afefb756de19c"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyDragon, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "f2ec61e636ff7bb3ac8bc2a4cc2c8b8f635dd1b2ec8094c963128b358e79c85c"); - EXPECT_EQ(hex(store(rsv.s)), "5ca6dd637ed7e80f0436fe8fce39c0e5f2082c9517fe677cc2917dcd6c84ba88"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -// See 'signedTypeData' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypeCow1) { - ParamStruct msgPersonCow1("Person", std::vector>{ - std::make_shared("name", std::make_shared("Cow")), - std::make_shared("wallet", std::make_shared(parse_hex("CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"))) - }); - - EXPECT_EQ(msgPersonCow1.encodeType(), "Person(string name,address wallet)"); - - EXPECT_EQ(hex(msgPersonCow1.hashType()), "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500"); - - EXPECT_EQ(hex(msgPersonCow1.encodeHashes()), "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c795008c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648000000000000000000000000cd2a3d9f938e13cd947ec05abc7fe734df8dd826"); - - EXPECT_EQ(hex(msgPersonCow1.hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); -} - -TEST(EthereumAbiStruct, hashStructJson) { - { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallet", "type": "address"} - ] - }, - "primaryType": "Person", - "domain": { - "name": "Ether Person", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "name": "Cow", - "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - } - })"); - ASSERT_EQ(hex(hash), "0b4bb85394b9ebb1c2425e283c9e734a9a7a832622e97c998f77e1c7a3f01a20"); - } - { // edge cases - EXPECT_EXCEPTION(ParamStruct::hashStructJson("NOT_A_JSON"), "Could not parse Json"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson("+/{\\"), "Could not parse Json"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(""), "Could not parse Json"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson("0"), "Expecting Json object"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson("[]"), "Expecting Json object"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({})"), "Top-level string field 'primaryType' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"domain": {}, "message": {}, "types": {}})"), "Top-level string field 'primaryType' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": [], "domain": {}, "message": {}, "types": {}})"), "Top-level string field 'primaryType' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "message": {}, "types": {}})"), "Top-level object field 'domain' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": "vDomain", "message": {}, "types": {}})"), "Top-level object field 'domain' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "types": {}})"), "Top-level object field 'message' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": "v2", "types": {}})"), "Top-level object field 'message' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}})"), "Top-level object field 'types' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": "vTypes"})"), "Top-level object field 'types' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": {}})"), "Type not found, EIP712Domain"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": {"EIP712Domain": []}})"), "No valid params found"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": {"EIP712Domain": [{"name": "param", "type": "type"}]}})"), "Unknown type type"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {"param": "val"}, "message": {}, "types": {"EIP712Domain": [{"name": "param", "type": "string"}]}})"), "Type not found, v1"); - } -} - -TEST(EthereumAbiStruct, hashStruct_emptyString) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_emptyString.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "bc9d33285c5e42b00571f5deaf9636d2e498a6fa50e0d1be81095bded070117a"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "5df6cb46d874bc0acc519695f393008a837ca9d2e316836b669b8f0de7673638"); - EXPECT_EQ(hex(store(rsv.s)), "54cc0bcc0ad657f9222f7e7be3fbe0ec4a8edb9385c39d578dfac8d38727af12"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -TEST(EthereumAbiStruct, hashStruct_emptyArray) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_emptyArray.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "9f1a1bc718e966d683c544aef6fd0b73c85a1d6244af9b64bb8f4a6fa6716086"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "de47efd592493f7189d44f071424ecb24b50d80750d3bd2bb6fc80451c13a52f"); - EXPECT_EQ(hex(store(rsv.s)), "202b8a2be1ef3c466853e8cd5275a6af15b11e7e1cc0ae4a7e249bc9bad591eb"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -TEST(EthereumAbiStruct, hashStruct_walletConnect) { - // https://github.com/WalletConnect/walletconnect-example-dapp/blob/master/src/helpers/eip712.ts - auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_walletconnect.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "abc79f527273b9e7bca1b3f1ac6ad1a8431fa6dc34ece900deabcd6969856b5e"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "e9c1ce1307593c378c7e38e8aa00dfb42b5a1ce543b59a138a12f29bd7fea75c"); - EXPECT_EQ(hex(store(rsv.s)), "3fe71ef91c37abea29fe14b5f0de805f924af19d71bcef09e74aef2f0ccdf52a"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -TEST(EthereumAbiStruct, hashStruct_cryptofights) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_cryptofights.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "db12328a6d193965801548e1174936c3aa7adbe1b54b3535a3c905bd4966467c"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "9e26bdf0d113a72805acb1c2c8b0734d264290fd1cfbdf5e6502ae65a2f2bd83"); - EXPECT_EQ(hex(store(rsv.s)), "11512c15ad0833fd457ae5dd59c3bcb3d03f35b3d33c1c5a575852163db42369"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -TEST(EthereumAbiStruct, hashStruct_rarible) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_rarible.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "df0200de55c05eb55af2597012767ea3af653d68000be49580f8e05acd91d366"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "9e6155c62a55d3dc6034973d93821dace5a0c66bfbd8413ad29205c2fb079e84"); - EXPECT_EQ(hex(store(rsv.s)), "3ca5906f24b82672304302a0e42e5dc090acc800060bad51fb81cc4469f69930"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -TEST(EthereumAbiStruct, hashStruct_snapshot) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_snapshot_v4.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "f558d08ad4a7651dbc9ec028cfcb4a8e6878a249073ef4fa694f85ee95f61c0f"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "9da563ffcafe9fa8809540ebcc4bcf8bbc26874e192f430432e06547593e8681"); - EXPECT_EQ(hex(store(rsv.s)), "164808603aca259775bdf511124b58651f1b3ce9ccbcd5a8d63df02e2359bb8b"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -TEST(EthereumAbiStruct, ParamFactoryMakeNamed) { - std::shared_ptr p = ParamFactory::makeNamed("firstparam", "uint256"); - EXPECT_EQ(p->getName(), "firstparam"); - ASSERT_NE(p->getParam().get(), nullptr); - EXPECT_EQ(p->getParam()->getType(), "uint256"); -} - -TEST(EthereumAbiStruct, ParamStructMakeStruct) { - { - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"( - {"name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"} - )", - R"({ - "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}] - })"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->getType(), "Person"); - ASSERT_EQ(s->getCount(), 2ul); - EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); - EXPECT_EQ(hex(s->hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); - } - { - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"( - {"name": "Cow", "wallets": ["CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]} - )", - R"({ - "Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}] - })"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->getType(), "Person"); - ASSERT_EQ(s->getCount(), 2ul); - EXPECT_EQ(s->encodeType(), "Person(string name,address[] wallets)"); - EXPECT_EQ(hex(s->hashStruct()), "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"); - } - { - std::shared_ptr s = ParamStruct::makeStruct("Mail", - R"({"from": {"name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"}, "to": {"name": "Bob", "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"}, "contents": "Hello, Bob!"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}],"Mail": [{"name": "from", "type": "Person"},{"name": "to", "type": "Person"},{"name": "contents", "type": "string"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->getType(), "Mail"); - ASSERT_EQ(s->getCount(), 3ul); - EXPECT_EQ(s->encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - EXPECT_EQ(hex(s->hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - } - - { // extra param - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"({"extra_param": "extra_value", "name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); - EXPECT_EQ(hex(s->hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); - } - { // empty array - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"({"extra_param": "extra_value", "name": "Cow", "wallets": []})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->encodeType(), "Person(string name,address[] wallets)"); - } - { // missing param - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"({"wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); - } - - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - "NOT_A_JSON+/{\\", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"), - "Could not parse value Json"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - "0", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"), - "Expecting object"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - // params mixed up - R"({"wallets": "Cow", "name": ["CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"), - "Could not set type for param wallets"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - R"({"name": "Cow", "wallets": ["CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "missingtype[]"}]})"), - "Unknown struct array type missingtype"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - R"({"name": "Cow", "wallets": "NOT_AN_ARRAY"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"), - "Could not set type for param wallets"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Mail", - R"({"from": {"name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"}, "to": {"name": "Bob", "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"}, "contents": "Hello, Bob!"})", - R"({"Mail": [{"name": "from", "type": "Person"},{"name": "to", "type": "Person"},{"name": "contents", "type": "string"}]})"), - "Unknown type Person"); - } -} - -TEST(EthereumAbiStruct, ParamFactoryMakeTypes) { - { - std::vector> tt = ParamStruct::makeTypes(R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_EQ(tt.size(), 1ul); - EXPECT_EQ(tt[0]->encodeType(), "Person(string name,address wallet)"); - } - { - std::vector> tt = ParamStruct::makeTypes( - R"({ - "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}], - "Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}] - })"); - ASSERT_EQ(tt.size(), 2ul); - EXPECT_EQ(tt[0]->encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - EXPECT_EQ(tt[1]->encodeType(), "Person(string name,address wallet)"); - } - { // edge cases - EXPECT_EXCEPTION(ParamStruct::makeTypes("NOT_A_JSON"), "Could not parse types Json"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("+/{\\"), "Could not parse types Json"); - EXPECT_EXCEPTION(ParamStruct::makeTypes(""), "Could not parse types Json"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("0"), "Expecting object"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("[]"), "Expecting object"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("[{}]"), "Expecting object"); - EXPECT_EQ(ParamStruct::makeTypes("{}").size(), 0ul); - EXPECT_EXCEPTION(ParamStruct::makeTypes("{\"a\": 0}"), "Expecting array"); - EXPECT_EXCEPTION(ParamStruct::makeTypes(R"({"name": 0})"), "Expecting array"); - // order does not matter - EXPECT_EQ(ParamStruct::makeTypes(R"({"Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}], "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})").size(), 2ul); - } -} - -TEST(EthereumAbiStruct, ParamFactoryMakeType) { - { - std::shared_ptr t = ParamStruct::makeType("Person", R"([{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}])"); - EXPECT_NE(t.get(), nullptr); - EXPECT_EQ(t->getType(), "Person"); - ASSERT_EQ(t->getCount(), 2ul); - ASSERT_EQ(t->encodeType(), "Person(string name,address wallet)"); - } - { // edge cases - EXPECT_EXCEPTION(ParamStruct::makeType("", ""), "Missing type name"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "NOT_A_JSON"), "Could not parse type Json"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "+/{\\"), "Could not parse type Json"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", ""), "Could not parse type Json"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "0"), "Expecting array"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "{}"), "Expecting array"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "[]"), "No valid params found"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"dummy": 0}])"), "Could not process Json: "); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "val"}])"), "Could not process Json: "); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"type": "val"}])"), "Could not process Json: "); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "", "type": "type"}])"), "Expecting 'name' and 'type', in Person"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "name", "type": ""}])"), "Expecting 'name' and 'type', in Person"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "name", "type": "UNKNOWN_TYPE"}, {"name": "wallet", "type": "address"}])"), "Unknown type UNKNOWN_TYPE"); - EXPECT_EQ(ParamStruct::makeType("Person", R"([{"name": "name", "type": "UNKNOWN_TYPE"}, {"name": "wallet", "type": "address"}])", {}, true)->encodeType(), "Person(UNKNOWN_TYPE name,address wallet)UNKNOWN_TYPE()"); - } -} - -TEST(EthereumAbiStruct, ParamNamedMethods) { - const auto ps = std::make_shared("Hello"); - auto pn = std::make_shared("name", ps); - - EXPECT_EQ(pn->getSize(), ps->getSize()); - EXPECT_EQ(pn->isDynamic(), ps->isDynamic()); - Data encoded; - pn->encode(encoded); - EXPECT_EQ(hex(encoded), "000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000"); - size_t offset = 0; - EXPECT_EQ(pn->decode(encoded, offset), true); - EXPECT_EQ(offset, 64ul); - pn->setValueJson("World"); - EXPECT_EQ(ps->getVal(), "World"); -} - -TEST(EthereumAbiStruct, ParamSetNamed) { - const auto pn1 = std::make_shared("param1", std::make_shared("Hello")); - const auto pn2 = std::make_shared("param2", std::make_shared("World")); - auto ps = std::make_shared(std::vector>{pn1, pn2}); - EXPECT_EQ(ps->getCount(), 2ul); - EXPECT_EQ(ps->addParam(std::shared_ptr(nullptr)), -1); - EXPECT_EQ(ps->findParamByName("NO_SUCH_PARAM"), nullptr); - auto pf1 = ps->findParamByName("param2"); - ASSERT_NE(pf1.get(), nullptr); - EXPECT_EQ(pf1->getName(), "param2"); -} - -TEST(EthereumAbiStruct, ParamStructMethods) { - const auto pn1 = std::make_shared("param1", std::make_shared("Hello")); - const auto pn2 = std::make_shared("param2", std::make_shared("World")); - auto ps = std::make_shared("struct", std::vector>{pn1, pn2}); - - EXPECT_EQ(ps->getSize(), 2ul); - EXPECT_EQ(ps->isDynamic(), true); - Data encoded; - ps->encode(encoded); - EXPECT_EQ(hex(encoded), ""); - size_t offset = 0; - EXPECT_EQ(ps->decode(encoded, offset), true); - EXPECT_EQ(offset, 0ul); - EXPECT_FALSE(ps->setValueJson("dummy")); - EXPECT_EQ(ps->findParamByName("param2")->getName(), "param2"); -} - -TEST(EthereumAbiStruct, ParamHashStruct) { - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000d"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ(hex(p->hashStruct()), "00000000000000000000000000000000000000000000000000000000000004d2"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(128); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(168); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000d"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ(hex(p->hashStruct()), "00000000000000000000000000000000000000000000000000000000000004d2"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(128); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(168); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("true")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000000000001"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("ABCdefGHI")); - EXPECT_EQ(hex(p->hashStruct()), "3a2aa9c027187dbf5a2f0c980281da43e810ecbe4d32e0b5c22211882c691889"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("0123456789")); - EXPECT_EQ(hex(p->hashStruct()), "79fad56e6cf52d0c8c2c033d568fc36856ba2b556774960968d79274b0e6b944"); - EXPECT_TRUE(p->setValueJson("0xa9059cbb0000000000000000000000002e0d94754b348d208d64d52d78bcd443afa9fa520000000000000000000000000000000000000000000000000000000000000007")); - EXPECT_EQ(hex(p->hashStruct()), "a9485354dd9d340e02789cfc540c6c4a2ff5511beb414b64634a5e11c6a7168c"); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "c8243991757dc8723e4976248127e573da4a2cbfad54b776d5a7c8d92b6e2a6b"); - EXPECT_TRUE(p->setValueJson("0x00")); - EXPECT_EQ(hex(p->hashStruct()), "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"); - EXPECT_TRUE(p->setValueJson("0x")); - EXPECT_EQ(hex(p->hashStruct()), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); - EXPECT_TRUE(p->setValueJson("")); - EXPECT_EQ(hex(p->hashStruct()), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); - } - { - auto p = std::make_shared(36); - EXPECT_TRUE(p->setValueJson("0x000000000000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "3deb4663f580c622d668f2121c29c3f4dacf06e40a3a76d1dea25e90bcd63b5d"); - } - { - auto p = std::make_shared(20); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000123456789000000000000000000000000"); - } - { - auto p = std::make_shared(32); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000123456789"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000123456789"); - } - { - using collection = std::vector>; - auto p = std::make_shared(collection{std::make_shared(), std::make_shared(), std::make_shared()}); - EXPECT_TRUE(p->setValueJson("[1,0,1]")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); - } - { - using collection = std::vector>; - auto p = std::make_shared(collection{std::make_shared(), std::make_shared(), std::make_shared()}); - EXPECT_TRUE(p->setValueJson("[13,14,15]")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f"); - - // Coverage - EXPECT_FALSE(p->setValueJson("NotValidJson")); - EXPECT_FALSE(p->setValueJson("{}")); - EXPECT_FALSE(p->setValueJson("[1,2,3,4]")); - EXPECT_FALSE(p->setValueJson("[1,2]")); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_TRUE(p->setValueJson("[13,14,15]")); - EXPECT_EQ(hex(p->hashStruct()), "71494e9b6acbff3356f1292cc149101310110b6b13f835ae4665e4b00892fa83"); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_TRUE(p->setValueJson("[\"0x0000000000000000000000000000000123456789\"]")); - EXPECT_EQ(hex(p->hashStruct()), "c8243991757dc8723e4976248127e573da4a2cbfad54b776d5a7c8d92b6e2a6b"); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_TRUE(p->setValueJson("[true,false,true]")); - EXPECT_EQ(hex(p->hashStruct()), "5c6090c0461491a2941743bda5c3658bf1ea53bbd3edcde54e16205e18b45792"); - } -} -// clang-format on -} // namespace TW::Ethereum::tests diff --git a/tests/chains/Ethereum/AbiTests.cpp b/tests/chains/Ethereum/AbiTests.cpp deleted file mode 100644 index 14e9e971fdb..00000000000 --- a/tests/chains/Ethereum/AbiTests.cpp +++ /dev/null @@ -1,1652 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/ABI.h" -#include "HexCoding.h" -#include "TestUtilities.h" - -#include - -namespace TW::Ethereum::ABI::tests { - -///// Parameter types - -TEST(EthereumAbi, ParamTypeNames) { - EXPECT_EQ("uint8", ParamUInt8().getType()); - EXPECT_EQ("uint16", ParamUInt16().getType()); - EXPECT_EQ("uint32", ParamUInt32().getType()); - EXPECT_EQ("uint64", ParamUInt64().getType()); - EXPECT_EQ("uint256", ParamUInt256().getType()); - EXPECT_EQ("uint168", ParamUIntN(168).getType()); - EXPECT_EQ("int8", ParamInt8().getType()); - EXPECT_EQ("int16", ParamInt16().getType()); - EXPECT_EQ("int32", ParamInt32().getType()); - EXPECT_EQ("int64", ParamInt64().getType()); - EXPECT_EQ("int256", ParamInt256().getType()); - EXPECT_EQ("int168", ParamIntN(168).getType()); - EXPECT_EQ("bool", ParamBool().getType()); - EXPECT_EQ("string", ParamString().getType()); - EXPECT_EQ("address", ParamAddress().getType()); - EXPECT_EQ("bytes", ParamByteArray().getType()); - EXPECT_EQ("bytes168", ParamByteArrayFix(168).getType()); - { - // ParamArray, non-empty - auto paramArray = ParamArray(); - paramArray.addParam(std::make_shared()); - EXPECT_EQ("bool[]", paramArray.getType()); - } - { - // ParamArray, empty with prototype - auto paramArray = ParamArray(); - paramArray.setProto(std::make_shared()); - EXPECT_EQ("bool[]", paramArray.getType()); - } - { - // ParamArray, empty, no prototype - auto paramArray = ParamArray(); - EXPECT_EQ("__empty__[]", paramArray.getType()); - } - EXPECT_EQ("()", ParamTuple().getType()); -} - -TEST(EthereumAbi, ParamBool) { - { - auto param = ParamBool(false); - EXPECT_FALSE(param.getVal()); - param.setVal(true); - EXPECT_TRUE(param.getVal()); - - EXPECT_EQ("bool", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamBool(false); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000000", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_FALSE(param.getVal()); - } - { - auto param = ParamBool(true); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_TRUE(param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt8) { - { - auto param = ParamUInt8(101); - EXPECT_EQ(101, param.getVal()); - param.setVal(1); - EXPECT_EQ(1, param.getVal()); - - EXPECT_EQ("uint8", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamUInt8(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); - } - { - auto param = ParamUInt8(1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt16) { - { - auto param = ParamUInt16(101); - EXPECT_EQ(101, param.getVal()); - param.setVal(1234); - EXPECT_EQ(1234, param.getVal()); - - EXPECT_EQ("uint16", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamUInt16(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); - } - { - auto param = ParamUInt16(1234); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt32) { - { - auto param = ParamUInt32(101); - EXPECT_EQ(101ul, param.getVal()); - param.setVal(1234); - EXPECT_EQ(1234ul, param.getVal()); - - EXPECT_EQ("uint32", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamUInt32(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101ul, param.getVal()); - } - { - auto param = ParamUInt32(1234); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234ul, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt64) { - { - auto param = ParamUInt64(101); - EXPECT_EQ(101ul, param.getVal()); - param.setVal(1234); - EXPECT_EQ(1234ul, param.getVal()); - - EXPECT_EQ("uint64", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamUInt64(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101ul, param.getVal()); - } - { - auto param = ParamUInt64(1234); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234ul, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt256) { - const uint256_t bigInt = uint256_t("0x1234567890123456789012345678901234567890"); - { - auto param = ParamUInt256(uint256_t(101)); - EXPECT_EQ(uint256_t(101), param.getVal()); - param.setVal(uint256_t(1234)); - EXPECT_EQ(uint256_t(1234), param.getVal()); - - EXPECT_EQ("uint256", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamUInt256(uint256_t(101)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(uint256_t(101), param.getVal()); - } - { - auto param = ParamUInt256(uint256_t(1234)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(uint256_t(1234), param.getVal()); - } - { - auto param = ParamUInt256(bigInt); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000001234567890123456789012345678901234567890", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(bigInt, param.getVal()); - } - { - auto param = ParamUInt256(uint256_t(-1)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(uint256_t(-1), param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt80) { - { - auto param = ParamUIntN(80, 0); - EXPECT_EQ(0, param.getVal()); - param.setVal(100); - EXPECT_EQ(100, param.getVal()); - - EXPECT_EQ("uint80", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - - // above number of bits, masked - param.setVal(load(Data(parse_hex("1010101010101010101010101010101010101010101010101010101010101010")))); - EXPECT_EQ(load(Data(parse_hex("00000010101010101010101010"))), param.getVal()); - } - { - auto param = ParamUIntN(80, 1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1, param.getVal()); - } - { - auto param = ParamUIntN(80, 0x123); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000123", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(0x123, param.getVal()); - } -} - -TEST(EthereumAbi, ParamInt80) { - // large negative, above number of bits, and its counterpart truncated to 80 bits - int256_t largeNeg2 = ValueEncoder::int256FromUint256(load(Data(parse_hex("ffff101010101010101010101010101010101010101010101010101010101010")))); - int256_t largeNeg1 = ValueEncoder::int256FromUint256(load(Data(parse_hex("ffffffffffffffffffffffffffffffffffffffffffff10101010101010101010")))); - { - auto param = ParamIntN(80, 0); - EXPECT_EQ(0, param.getVal()); - param.setVal(int256_t(101)); - EXPECT_EQ(int256_t(101), param.getVal()); - - EXPECT_EQ("int80", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - - param.setVal(int256_t(-101)); - EXPECT_EQ(int256_t(-101), param.getVal()); - param.setVal(largeNeg1); - EXPECT_EQ(largeNeg1, param.getVal()); - // large negative, above number of bits, masked - param.setVal(largeNeg2); - EXPECT_EQ(largeNeg1, param.getVal()); - } - { - auto param = ParamIntN(80, 1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1, param.getVal()); - } - { - auto param = ParamIntN(80, int256_t(-1234)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2e", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(int256_t(-1234), param.getVal()); - } - { - auto param = ParamIntN(80, largeNeg1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("ffffffffffffffffffffffffffffffffffffffffffff10101010101010101010", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(int256_t(largeNeg1), param.getVal()); - } - { - auto param = ParamIntN(80, largeNeg2); - Data encoded; - param.encode(encoded); - EXPECT_EQ("ffffffffffffffffffffffffffffffffffffffffffff10101010101010101010", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(int256_t(largeNeg1), param.getVal()); - } -} - -TEST(EthereumAbi, ParamString) { - const std::string helloStr = "Hello World! Hello World! Hello World!"; - { - auto param = ParamString(helloStr); - EXPECT_EQ(helloStr, param.getVal()); - - EXPECT_EQ("string", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(3 * 32ul, param.getSize()); - } - { - auto param = ParamString(helloStr); - Data encoded; - param.encode(encoded); - EXPECT_EQ( - "000000000000000000000000000000000000000000000000000000000000002c" - "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" - "48656c6c6f20576f726c64210000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ(3 * 32ul, encoded.size()); - EXPECT_EQ(3 * 32ul, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(helloStr, param.getVal()); - } -} - -TEST(EthereumAbi, ParamAddress) { - std::string val1Str("f784682c82526e245f50975190ef0fff4e4fc077"); - Data val1(parse_hex(val1Str)); - { - auto param = ParamAddress(val1); - EXPECT_EQ(val1, param.getData()); - - EXPECT_EQ("address", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamAddress(val1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(val1, param.getData()); - } - { - auto param = ParamAddress(parse_hex("0000000000000000000000000000000000000012")); - EXPECT_EQ("0000000000000000000000000000000000000012", hex(param.getData())); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000012", hex(encoded)); - } - { - auto param = ParamAddress(parse_hex("4300000000000000000000000000000000000000")); - EXPECT_EQ("4300000000000000000000000000000000000000", hex(param.getData())); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000004300000000000000000000000000000000000000", hex(encoded)); - } -} - -TEST(EthereumAbi, ParamByteArray) { - Data data10 = parse_hex("31323334353637383930"); - { - auto param = ParamByteArray(data10); - EXPECT_EQ(data10, param.getVal()); - - EXPECT_EQ("bytes", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(2 * 32ul, param.getSize()); - } - { - auto param = ParamByteArray(data10); - Data encoded; - param.encode(encoded); - EXPECT_EQ(2 * 32ul, encoded.size()); - EXPECT_EQ(2 * 32ul, param.getSize()); - EXPECT_EQ( - "000000000000000000000000000000000000000000000000000000000000000a" - "3132333435363738393000000000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ(2 * 32ul, encoded.size()); - EXPECT_EQ(2 * 32ul, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(data10, param.getVal()); - } -} - -TEST(EthereumAbi, ParamByteArrayFix) { - Data data10 = parse_hex("31323334353637383930"); - { - auto param = ParamByteArrayFix(10, data10); - EXPECT_EQ(data10, param.getVal()); - - EXPECT_EQ("bytes10", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32ul, param.getSize()); - } - { - auto param = ParamByteArrayFix(10, data10); - Data encoded; - param.encode(encoded); - EXPECT_EQ(32ul, encoded.size()); - EXPECT_EQ(32ul, param.getSize()); - EXPECT_EQ( - "3132333435363738393000000000000000000000000000000000000000000000", - hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(data10, param.getVal()); - } -} - -TEST(EthereumAbi, ParamArrayByte) { - { - auto param = ParamArray(); - param.addParam(std::make_shared(49)); - param.addParam(std::make_shared(50)); - param.addParam(std::make_shared(51)); - EXPECT_EQ(3ul, param.getVal().size()); - EXPECT_EQ(49, (std::dynamic_pointer_cast(param.getVal()[0]))->getVal()); - EXPECT_EQ(51, (std::dynamic_pointer_cast(param.getVal()[2]))->getVal()); - - EXPECT_EQ("uint8[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((3 + 1) * 32ul, param.getSize()); - EXPECT_EQ(3ul, param.getCount()); - } - { - auto param = ParamArray(); - param.addParam(std::make_shared(49)); - param.addParam(std::make_shared(50)); - param.addParam(std::make_shared(51)); - Data encoded; - param.encode(encoded); - EXPECT_EQ(4 * 32ul, encoded.size()); - EXPECT_EQ(4 * 32ul, param.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000031" - "0000000000000000000000000000000000000000000000000000000000000032" - "0000000000000000000000000000000000000000000000000000000000000033", - hex(encoded)); - EXPECT_EQ(4 * 32ul, encoded.size()); - EXPECT_EQ(4 * 32ul, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(3ul, param.getVal().size()); - EXPECT_EQ(49, (std::dynamic_pointer_cast(param.getVal()[0]))->getVal()); - EXPECT_EQ(51, (std::dynamic_pointer_cast(param.getVal()[2]))->getVal()); - } -} - -TEST(EthereumAbi, ParamArrayEmpty) { - auto param = ParamArray(); - param.setProto(std::make_shared(0)); - - EXPECT_EQ(0ul, param.getCount()); - Data encoded; - param.encode(encoded); - EXPECT_EQ(32ul, encoded.size()); - EXPECT_EQ(32ul, param.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ("uint8[]", param.getType()); - EXPECT_EQ("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", hex(param.hashStruct())); -} - -TEST(EthereumAbi, ParamFixedArrayAddress) { - { - auto param = ParamArrayFix({std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))}); - EXPECT_EQ(param.getType(), "address[1]"); - EXPECT_EQ(param.getCount(), 1ul); - EXPECT_EQ(param.getSize(), 32ul); - EXPECT_FALSE(param.isDynamic()); - Data encoded; - param.encode(encoded); - EXPECT_EQ(hex(encoded), "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"); - std::size_t offset{0}; - EXPECT_TRUE(param.decode(encoded, offset)); - } - { - auto param = ParamArrayFix({std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077"))), - std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))}); - EXPECT_EQ(param.getType(), "address[2]"); - EXPECT_EQ(param.getCount(), 2ul); - Data encoded; - param.encode(encoded); - EXPECT_EQ(hex(encoded), "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3"); - std::size_t offset{0}; - EXPECT_TRUE(param.decode(encoded, offset)); - } - { - // nullptr - EXPECT_THROW(ParamArrayFix({nullptr}), std::runtime_error); - // Should be the same type - EXPECT_THROW( - ParamArrayFix({std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077"))), - std::make_shared("Foo")}), - std::runtime_error); - } -} - -TEST(EthereumAbi, ParamArrayAddress) { - { - auto param = ParamArray(); - param.addParam(std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))); - param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); - EXPECT_EQ(2ul, param.getVal().size()); - - EXPECT_EQ("address[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((2 + 1) * 32ul, param.getSize()); - EXPECT_EQ(2ul, param.getCount()); - } - { - auto param = ParamArray(); - param.addParam(std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))); - param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); - Data encoded; - param.encode(encoded); - EXPECT_EQ(3 * 32ul, encoded.size()); - EXPECT_EQ(3 * 32ul, param.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000002" - "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3", - hex(encoded)); - EXPECT_EQ(3 * 32ul, encoded.size()); - EXPECT_EQ(3 * 32ul, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(2ul, param.getCount()); - EXPECT_EQ(2ul, param.getVal().size()); - EXPECT_EQ( - "2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", - hex((std::dynamic_pointer_cast(param.getVal()[1]))->getData())); - } -} - -TEST(EthereumAbi, ParamArrayOfByteArray) { - auto param = ParamArray(); - param.addParam(std::make_shared(parse_hex("1011"))); - param.addParam(std::make_shared(parse_hex("102222"))); - param.addParam(std::make_shared(parse_hex("10333333"))); - EXPECT_EQ(3ul, param.getVal().size()); - - EXPECT_EQ("bytes[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((1 + 3 + 3 * 2) * 32ul, param.getSize()); - EXPECT_EQ(3ul, 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(4ul, param.getCount()); - EXPECT_EQ(4ul, param.getVal().size()); - - EXPECT_EQ("bytes[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - - Data encoded; - param.encode(encoded); - EXPECT_EQ(896ul, encoded.size()); - - EXPECT_EQ(896ul, param.getSize()); -} - -TEST(EthereumAbi, ParamTupleStatic) { - { - auto param = ParamTuple(); - param.addParam(std::make_shared(true)); - param.addParam(std::make_shared(123)); - EXPECT_EQ("(bool,uint64)", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(2ul, param.getCount()); - EXPECT_EQ(64ul, param.getSize()); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b", hex(encoded)); - { // decode - size_t offset = 0; - auto param2 = ParamTuple(); - param2.addParam(std::make_shared()); - param2.addParam(std::make_shared()); - EXPECT_TRUE(param2.decode(encoded, offset)); - EXPECT_EQ(2ul, param2.getCount()); - Data encoded2; - param2.encode(encoded2); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b", hex(encoded)); - } - } - { - auto param = ParamTuple(); - param.addParam(std::make_shared(456)); - param.addParam(std::make_shared(parse_hex("000102030405060708090a0b0c0d0e0f10111213"))); - EXPECT_EQ("(uint64,address)", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(2ul, param.getCount()); - EXPECT_EQ(64ul, param.getSize()); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000102030405060708090a0b0c0d0e0f10111213", hex(encoded)); - } -} - -TEST(EthereumAbi, ParamTupleDynamic) { - { - auto param = ParamTuple(); - param.addParam(std::make_shared("Don't trust, verify!")); - param.addParam(std::make_shared(13)); - param.addParam(std::make_shared(parse_hex("00010203040506070809"))); - EXPECT_EQ("(string,uint64,bytes)", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(3ul, param.getCount()); - EXPECT_EQ(7 * 32ul, param.getSize()); - Data encoded; - param.encode(encoded); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000060" // offet 3*32 - "000000000000000000000000000000000000000000000000000000000000000d" - "00000000000000000000000000000000000000000000000000000000000000a0" // offet 5*32 - "0000000000000000000000000000000000000000000000000000000000000014" // len - "446f6e27742074727573742c2076657269667921000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000a" // len - "0001020304050607080900000000000000000000000000000000000000000000", - hex(encoded)); - } -} - -///// Direct encode & decode - -TEST(EthereumAbi, EncodeVectorByte10) { - auto p = ParamByteArrayFix(10, parse_hex("31323334353637383930")); - EXPECT_EQ("bytes10", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ("3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); -} - -TEST(EthereumAbi, EncodeVectorByte) { - auto p = ParamByteArray(parse_hex("31323334353637383930")); - EXPECT_EQ("bytes", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ( - "000000000000000000000000000000000000000000000000000000000000000a" - "3132333435363738393000000000000000000000000000000000000000000000", - hex(encoded)); -} - -TEST(EthereumAbi, EncodeArrayByte) { - auto p = ParamArray(std::vector>{ - std::make_shared(parse_hex("1011")), - std::make_shared(parse_hex("102222"))}); - EXPECT_EQ("bytes[]", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000040" - "0000000000000000000000000000000000000000000000000000000000000080" - "0000000000000000000000000000000000000000000000000000000000000002" - "1011000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000003" - "1022220000000000000000000000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ((1 + 2 + 2 * 2) * 32ul, encoded.size()); - EXPECT_EQ((1 + 2 + 2 * 2) * 32ul, p.getSize()); -} - -TEST(EthereumAbi, DecodeUInt) { - Data encoded = parse_hex("000000000000000000000000000000000000000000000000000000000000002a"); - size_t offset = 0; - uint256_t decoded; - bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(42), decoded); - EXPECT_EQ(32ul, offset); -} - -TEST(EthereumAbi, DecodeUInt8) { - Data encoded = parse_hex("0000000000000000000000000000000000000000000000000000000000000018"); - size_t offset = 0; - uint8_t decoded; - bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(24, decoded); - EXPECT_EQ(32ul, offset); -} - -TEST(EthereumAbi, DecodeUInt8WithOffset) { - Data encoded = parse_hex("abcdef0000000000000000000000000000000000000000000000000000000000000018"); - size_t offset = 3; - uint8_t decoded; - bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(24, decoded); - EXPECT_EQ(3 + 32ul, offset); -} - -TEST(EthereumAbi, DecodeUIntWithOffset) { - Data encoded = parse_hex("abcdef000000000000000000000000000000000000000000000000000000000000002a"); - size_t offset = 3; - uint256_t decoded; - bool res = decode(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(42), decoded); - EXPECT_EQ(3 + 32ul, offset); -} - -TEST(EthereumAbi, DecodeUIntErrorTooShort) { - Data encoded = parse_hex("000000000000000000000000000000000000000000000000002a"); - size_t offset = 0; - uint256_t decoded; - bool res = decode(encoded, decoded, offset); - EXPECT_FALSE(res); - EXPECT_EQ(uint256_t(0), decoded); - EXPECT_EQ(0ul, offset); -} - -TEST(EthereumAbi, DecodeArrayUint) { - Data encoded; - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000a")); - append(encoded, parse_hex("3132333435363738393000000000000000000000000000000000000000000000")); - size_t offset = 0; - std::vector decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(10ul, decoded.size()); - if (decoded.size() >= 2) { - EXPECT_EQ(49u, decoded[0]); - EXPECT_EQ(50u, decoded[1]); - } - EXPECT_EQ(32 + 32ul, offset); -} - -TEST(EthereumAbi, DecodeArrayTooShort) { - Data encoded; - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000a")); - append(encoded, parse_hex("313233343536373839")); - size_t offset = 0; - std::vector decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_FALSE(res); -} - -TEST(EthereumAbi, DecodeArrayInvalidLen) { - Data encoded = parse_hex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); - size_t offset = 0; - std::vector decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_FALSE(res); -} - -TEST(EthereumAbi, DecodeByteArray) { - Data encoded; - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000a")); - append(encoded, parse_hex("3132333435363738393000000000000000000000000000000000000000000000")); - size_t offset = 0; - Data decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ("31323334353637383930", hex(decoded)); - EXPECT_EQ(32 + 32ul, offset); -} - -TEST(EthereumAbi, DecodeByteArray10) { - Data encoded = parse_hex("3132333435363738393000000000000000000000000000000000000000000000"); - size_t offset = 0; - Data decoded; - bool res = ParamByteArrayFix::decodeBytesFix(encoded, 10, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(10ul, decoded.size()); - EXPECT_EQ(49u, decoded[0]); - EXPECT_EQ(50u, decoded[1]); - EXPECT_EQ(32ul, 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(2ul, param.getCount()); - // `size_of(array.len())` + `size_of(byte_array[0].len())` + `size_of(byte_array[1].len()) - EXPECT_EQ(3 * 32ul, offset); - EXPECT_EQ(2ul, param.getVal().size()); -} - -///// Parameters encode & decode - -TEST(EthereumAbi, EncodeParamsSimple) { - auto p = Parameters(std::vector>{ - std::make_shared(16u), - std::make_shared(17u), - std::make_shared(true)}); - EXPECT_EQ("(uint256,uint256,bool)", p.getType()); - Data encoded; - p.encode(encoded); - - EXPECT_EQ(3 * 32ul, encoded.size()); - EXPECT_EQ(3 * 32ul, p.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000010" - "0000000000000000000000000000000000000000000000000000000000000011" - "0000000000000000000000000000000000000000000000000000000000000001", - hex(encoded)); -} - -TEST(EthereumAbi, EncodeParamsMixed) { - auto p = Parameters(std::vector>{ - std::make_shared(69u), - std::make_shared(std::vector>{ - std::make_shared(1), - std::make_shared(2), - std::make_shared(3)}), - std::make_shared(true), - std::make_shared("Hello"), - std::make_shared(Data{0x64, 0x61, 0x76, 0x65})}); - EXPECT_EQ("(uint256,uint256[],bool,string,bytes)", p.getType()); - Data encoded; - p.encode(encoded); - - EXPECT_EQ(13 * 32ul, encoded.size()); - EXPECT_EQ(13 * 32ul, p.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000045" - "00000000000000000000000000000000000000000000000000000000000000a0" - "0000000000000000000000000000000000000000000000000000000000000001" - "0000000000000000000000000000000000000000000000000000000000000120" - "0000000000000000000000000000000000000000000000000000000000000160" - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000001" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000005" - "48656c6c6f000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000004" - "6461766500000000000000000000000000000000000000000000000000000000", - hex(encoded)); - /* - * explained: - * uint256 69u - * idx of dynamic vector, 5*32 - * true - * index of dynamic string, 9*32 - * index of dynamic data, 11*32 - * vector size 3 - * vector val1 - * vector val2 - * vector val3 - * string size 5 - * string - * data size 4 - * data - */ -} - -TEST(EthereumAbi, DecodeParamsSimple) { - Data encoded; - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000010")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000011")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - auto p = Parameters(std::vector>{ - std::make_shared(0), - std::make_shared(0), - std::make_shared(false)}); - EXPECT_EQ("(uint256,uint256,bool)", p.getType()); - size_t offset = 0; - bool res = p.decode(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(16u), (std::dynamic_pointer_cast(p.getParam(0)))->getVal()); - EXPECT_EQ(uint256_t(17u), (std::dynamic_pointer_cast(p.getParam(1)))->getVal()); - EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); - EXPECT_EQ(3 * 32ul, offset); -} - -TEST(EthereumAbi, DecodeParamsMixed) { - Data encoded; - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); - append(encoded, parse_hex("00000000000000000000000000000000000000000000000000000000000000a0")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000120")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000160")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000005")); - append(encoded, parse_hex("48656c6c6f000000000000000000000000000000000000000000000000000000")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000004")); - append(encoded, parse_hex("6461766500000000000000000000000000000000000000000000000000000000")); - // clang-format off - auto p = Parameters(std::vector>{ - std::make_shared(), - std::make_shared(std::vector>{ - std::make_shared(), - std::make_shared(), - std::make_shared() - }), - std::make_shared(), - std::make_shared(), - std::make_shared() - }); - // clang-format on - EXPECT_EQ("(uint256,uint256[],bool,string,bytes)", p.getType()); - size_t offset = 0; - bool res = p.decode(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(69u), (std::dynamic_pointer_cast(p.getParam(0)))->getVal()); - EXPECT_EQ(3ul, (std::dynamic_pointer_cast(p.getParam(1)))->getCount()); - EXPECT_EQ(1, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(0)))->getVal()); - EXPECT_EQ(3, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(2)))->getVal()); - EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); - EXPECT_EQ("Hello", (std::dynamic_pointer_cast(p.getParam(3)))->getVal()); - // Offset of `ParamUInt256`, `ParamBool` static elements and sizes of `ParamArray`, `ParamBool`, `ParamByteArray`. - EXPECT_EQ(5 * 32ul, offset); -} - -///// Function encode & decode - -TEST(EthereumAbi, EncodeSignature) { - // clang-format off - auto func = Function("baz", std::vector>{ - std::make_shared(69u), - std::make_shared(true) - }); - // clang-format on - EXPECT_EQ("baz(uint256,bool)", func.getType()); - Data encoded; - func.encode(encoded); - - EXPECT_EQ(encoded.size(), 32 * 2 + 4ul); - EXPECT_EQ(hex(data(encoded.begin(), encoded.begin() + 4)), "72ed38b6"); - EXPECT_EQ(hex(data(encoded.begin() + 4, encoded.begin() + 36)), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(data(encoded.begin() + 36, encoded.begin() + 68)), "0000000000000000000000000000000000000000000000000000000000000001"); -} - -TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase1) { - // clang-format off - auto func = Function("sam", std::vector>{ - std::make_shared(Data{0x64, 0x61, 0x76, 0x65}), - std::make_shared(true), - std::make_shared(std::vector>{ - std::make_shared(1), - std::make_shared(2), - std::make_shared(3) - }) - }); - // clang-format on - EXPECT_EQ("sam(bytes,bool,uint256[])", func.getType()); - Data encoded; - func.encode(encoded); - - EXPECT_EQ(encoded.size(), 32 * 9 + 4ul); - EXPECT_EQ(hex(data(encoded.begin() + 0, encoded.begin() + 4)), "a5643bf2"); - EXPECT_EQ(hex(data(encoded.begin() + 4, encoded.begin() + 36)), "0000000000000000000000000000000000000000000000000000000000000060"); - EXPECT_EQ(hex(data(encoded.begin() + 36, encoded.begin() + 68)), "0000000000000000000000000000000000000000000000000000000000000001"); - EXPECT_EQ(hex(data(encoded.begin() + 68, encoded.begin() + 100)), "00000000000000000000000000000000000000000000000000000000000000a0"); - EXPECT_EQ(hex(data(encoded.begin() + 100, encoded.begin() + 132)), "0000000000000000000000000000000000000000000000000000000000000004"); - EXPECT_EQ(hex(data(encoded.begin() + 132, encoded.begin() + 164)), "6461766500000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(data(encoded.begin() + 164, encoded.begin() + 196)), "0000000000000000000000000000000000000000000000000000000000000003"); - EXPECT_EQ(hex(data(encoded.begin() + 196, encoded.begin() + 228)), "0000000000000000000000000000000000000000000000000000000000000001"); - EXPECT_EQ(hex(data(encoded.begin() + 228, encoded.begin() + 260)), "0000000000000000000000000000000000000000000000000000000000000002"); - EXPECT_EQ(hex(data(encoded.begin() + 260, encoded.begin() + 292)), "0000000000000000000000000000000000000000000000000000000000000003"); -} - -TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase2) { - // clang-format off - auto func = Function("f", std::vector>{ - std::make_shared(0x123), - std::make_shared(std::vector>{ - std::make_shared(0x456), - std::make_shared(0x789)}), - std::make_shared(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}), - std::make_shared("Hello, world!") - }); - // clamp-format on - EXPECT_EQ("f(uint256,uint32[],bytes10,string)", func.getType()); - Data encoded; - func.encode(encoded); - - EXPECT_EQ(encoded.size(), 32 * 9 + 4ul); - EXPECT_EQ(hex(data(encoded.begin() + 0, encoded.begin() + 4)), "47b941bf"); - EXPECT_EQ(hex(data(encoded.begin() + 4, encoded.begin() + 36)), "0000000000000000000000000000000000000000000000000000000000000123"); - EXPECT_EQ(hex(data(encoded.begin() + 36, encoded.begin() + 68)), "0000000000000000000000000000000000000000000000000000000000000080"); - EXPECT_EQ(hex(data(encoded.begin() + 68, encoded.begin() + 100)), "3132333435363738393000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(data(encoded.begin() + 100, encoded.begin() + 132)), "00000000000000000000000000000000000000000000000000000000000000e0"); - EXPECT_EQ(hex(data(encoded.begin() + 132, encoded.begin() + 164)), "0000000000000000000000000000000000000000000000000000000000000002"); - EXPECT_EQ(hex(data(encoded.begin() + 164, encoded.begin() + 196)), "0000000000000000000000000000000000000000000000000000000000000456"); - EXPECT_EQ(hex(data(encoded.begin() + 196, encoded.begin() + 228)), "0000000000000000000000000000000000000000000000000000000000000789"); - EXPECT_EQ(hex(data(encoded.begin() + 228, encoded.begin() + 260)), "000000000000000000000000000000000000000000000000000000000000000d"); - EXPECT_EQ(hex(data(encoded.begin() + 260, encoded.begin() + 292)), "48656c6c6f2c20776f726c642100000000000000000000000000000000000000"); -} - -TEST(EthereumAbi, DecodeFunctionOutputCase1) { - Data encoded; - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); - - // clang-format off - auto func = Function("readout", std::vector>{ - std::make_shared(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")), - std::make_shared(1000) - }); - // clang-format on - func.addOutParam(std::make_shared()); - EXPECT_EQ("readout(address,uint64)", func.getType()); - - // original output value - std::shared_ptr param; - EXPECT_TRUE(func.getOutParam(0, param)); - EXPECT_EQ(0ul, (std::dynamic_pointer_cast(param))->getVal()); - - size_t offset = 0; - bool res = func.decodeOutput(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(32ul, offset); - - // new output value - EXPECT_EQ(0x45ul, (std::dynamic_pointer_cast(param))->getVal()); -} - -TEST(EthereumAbi, DecodeFunctionOutputCase2) { - // clang-format off - auto func = Function("getAmountsOut", std::vector>{ - std::make_shared(100), - std::make_shared(std::make_shared(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"))) - }); - // clang-format on - func.addOutParam(std::make_shared(std::vector>{ - std::make_shared(66), - std::make_shared(67)})); - EXPECT_EQ("getAmountsOut(uint256,address[])", func.getType()); - - Data encoded; - append(encoded, parse_hex( - "0000000000000000000000000000000000000000000000000000000000000020" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000004" - "0000000000000000000000000000000000000000000000000000000000000005")); - size_t offset = 0; - bool res = func.decodeOutput(encoded, offset); - EXPECT_TRUE(res); - // The offset should only be shifted by the size of the array. - EXPECT_EQ(32ul, offset); - - // new output values - std::shared_ptr param; - EXPECT_TRUE(func.getOutParam(0, param)); - EXPECT_EQ(2ul, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(4, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getParam(0)))->getVal()); - EXPECT_EQ(5, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getParam(1)))->getVal()); -} - -TEST(EthereumAbi, DecodeInputSignature) { - Data encoded; - append(encoded, parse_hex("72ed38b6")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - // clang-format off - auto func = Function("baz", std::vector>{ - std::make_shared(), std::make_shared() - }); - // clang-format on - EXPECT_EQ("baz(uint256,bool)", func.getType()); - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - std::shared_ptr param; - EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(69u, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(true, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_EQ(4 + 2 * 32ul, offset); -} - -TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { - Data encoded; - append(encoded, parse_hex("a5643bf2")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000060")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("00000000000000000000000000000000000000000000000000000000000000a0")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000004")); - append(encoded, parse_hex("6461766500000000000000000000000000000000000000000000000000000000")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - - // clang-format off - auto func = Function("sam", std::vector>{ - std::make_shared(Data{0x64, 0x61, 0x76, 0x65}), - std::make_shared(true), - std::make_shared(std::vector>{ - std::make_shared(1), - std::make_shared(2), - std::make_shared(3) - }) - }); - // clang-format on - EXPECT_EQ("sam(bytes,bool,uint256[])", func.getType()); - - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - std::shared_ptr param; - EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(4ul, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(0x64, (std::dynamic_pointer_cast(param))->getVal()[0]); - EXPECT_EQ(0x65, (std::dynamic_pointer_cast(param))->getVal()[3]); - EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(true, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_TRUE(func.getInParam(2, param)); - EXPECT_EQ(3ul, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(uint256_t(1), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); - EXPECT_EQ(uint256_t(3), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[2]))->getVal()); - // Offset of `ParamBool` static element and sizes of `ParamByteArray`, `ParamArray` arrays. - EXPECT_EQ(4 + 3 * 32ul, offset); -} - -TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { - Data encoded; - append(encoded, parse_hex("47b941bf")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000123")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000080")); - append(encoded, parse_hex("3132333435363738393000000000000000000000000000000000000000000000")); - append(encoded, parse_hex("00000000000000000000000000000000000000000000000000000000000000e0")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000456")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000789")); - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000d")); - append(encoded, parse_hex("48656c6c6f2c20776f726c642100000000000000000000000000000000000000")); - - // clang-format off - auto func = Function("f", std::vector>{ - std::make_shared(0x123), - std::make_shared(std::vector>{ - std::make_shared(0x456), - std::make_shared(0x789)}), - std::make_shared(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}), - std::make_shared("Hello, world!") - }); - // clang-format on - EXPECT_EQ("f(uint256,uint32[],bytes10,string)", func.getType()); - - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - std::shared_ptr param; - EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(uint256_t(0x123), (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(2ul, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(0x456ul, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); - EXPECT_EQ(0x789ul, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[1]))->getVal()); - EXPECT_TRUE(func.getInParam(2, param)); - EXPECT_EQ(10ul, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ("31323334353637383930", hex((std::dynamic_pointer_cast(param))->getVal())); - EXPECT_TRUE(func.getInParam(3, param)); - EXPECT_EQ(std::string("Hello, world!"), (std::dynamic_pointer_cast(param))->getVal()); - // Offset of `ParamUInt256` static element and sizes of `ParamArray`, `ParamByteArrayFix`, `ParamString` dynamic arrays. - EXPECT_EQ(4 + 4 * 32ul, 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 + 928ul, encoded.size()); - - // clang-format off - 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()) - }), - }); - // clang-format on - EXPECT_EQ("multicall(bytes[])", func.getType()); - - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - // The offset should only be shifted by the size of the array. - EXPECT_EQ(4 + 32ul, offset); -} - -TEST(EthereumAbi, ParamFactoryMake) { - { - // test for UInt256: ParamUInt256 and ParamUIntN(256), both have type "uint256", factory produces the more specific ParamUInt256 - // there was confusion about this - std::shared_ptr p = ParamFactory::make("uint256"); - EXPECT_EQ("uint256", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - EXPECT_EQ(nullptr, std::dynamic_pointer_cast(p).get()); - } - { - // int32 is ParamInt32, not ParamIntN - std::shared_ptr p = ParamFactory::make("int32"); - EXPECT_EQ("int32", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - EXPECT_EQ(nullptr, std::dynamic_pointer_cast(p).get()); - } - { - // int168 is ParamIntN - std::shared_ptr p = ParamFactory::make("int168"); - EXPECT_EQ("int168", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - } - { - // uint is uint256 - std::shared_ptr p = ParamFactory::make("uint"); - EXPECT_EQ("uint256", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - } - { - // int is int256 - std::shared_ptr p = ParamFactory::make("int"); - EXPECT_EQ("int256", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - } - { - std::shared_ptr p = ParamFactory::make("uint8[]"); - EXPECT_EQ("uint8[]", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - auto elemParam = std::dynamic_pointer_cast(p)->getParam(0); - EXPECT_TRUE(nullptr != elemParam.get()); - } - { - std::shared_ptr p = ParamFactory::make("address[]"); - EXPECT_EQ("address[]", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - auto elemParam = std::dynamic_pointer_cast(p)->getParam(0); - EXPECT_TRUE(nullptr != elemParam.get()); - } - { - std::shared_ptr p = ParamFactory::make("bytes[]"); - EXPECT_EQ("bytes[]", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - auto elemParam = std::dynamic_pointer_cast(p)->getParam(0); - EXPECT_TRUE(nullptr != elemParam.get()); - } -} - -TEST(EthereumAbi, ParamFactoryMakeException) { - EXPECT_EXCEPTION(ParamFactory::make("uint93"), "invalid bit size"); -} - -TEST(EthereumAbi, ParamFactoryGetArrayValue) { - { - auto pArray = std::make_shared(std::make_shared()); - const auto vals = ParamFactory::getArrayValue(pArray, pArray->getType()); - ASSERT_EQ(vals.size(), 1ul); - EXPECT_EQ(vals[0], "0"); - } - { // wrong type, not array - auto pArray = std::make_shared(std::make_shared()); - const auto vals = ParamFactory::getArrayValue(pArray, "bool"); - EXPECT_EQ(vals.size(), 0ul); - } - { // wrong param, not array - auto pArray = std::make_shared(); - const auto vals = ParamFactory::getArrayValue(pArray, "uint8[]"); - EXPECT_EQ(vals.size(), 0ul); - } -} - -TEST(EthereumAbi, ParamFactorySetGetValue) { - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ("13", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ("1234", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(128); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(168); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x00")); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0x")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ("13", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ("1234", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(128); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(168); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x00")); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0x")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("false", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("true")); - EXPECT_EQ("true", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("false")); - EXPECT_TRUE(p->setValueJson("1")); - EXPECT_TRUE(p->setValueJson("0")); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("ABCdefGHI")); - EXPECT_EQ("ABCdefGHI", ParamFactory::getValue(p, p->getType())); - EXPECT_EQ(9ul, p->getCount()); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0x", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0123456789")); - EXPECT_EQ("0x0123456789", ParamFactory::getValue(p, p->getType())); - } - { - auto p = std::make_shared(36); - EXPECT_EQ("0x000000000000000000000000000000000000000000000000000000000000000000000000", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x000000000000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ("0x000000000000000000000000000000000000000000000000000000000000000123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x0123456789")); // will be padded - EXPECT_EQ("0x012345678900000000000000000000000000000000000000000000000000000000000000", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xabcdef0000000000000000000000000000000000000000000000000000000000000123456789")); // will be cropped - EXPECT_EQ("0xabcdef000000000000000000000000000000000000000000000000000000000000012345", ParamFactory::getValue(p, p->getType())); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0x0000000000000000000000000000000000000000", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); - EXPECT_EQ("0x0000000000000000000000000000000123456789", ParamFactory::getValue(p, p->getType())); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_EQ("[0]", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("[13,14,15]")); - EXPECT_EQ("[13,14,15]", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("13")); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_EQ("[\"0x0000000000000000000000000000000000000000\"]", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("[\"0x0000000000000000000000000000000123456789\"]")); - EXPECT_EQ("[\"0x0000000000000000000000000000000123456789\"]", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("0x0000000000000000000000000000000123456789")); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_EQ("[false]", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("[true,false,true]")); - EXPECT_EQ("[true,false,true]", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("true")); - } -} - -TEST(EthereumAbi, ParamFactoryGetValue) { - const std::vector types = { - "uint8", - "uint16", - "uint32", - "uint64", - "uint128", - "uint168", - "uint256", - "int8", - "int16", - "int32", - "int64", - "int128", - "int168", - "int256", - "bool", - "string", - "bytes", - "bytes168", - "address", - "uint8[]", - "address[]", - "bool[]", - "bytes[]", - }; - for (auto t : types) { - std::shared_ptr p = ParamFactory::make(t); - EXPECT_EQ(t, p->getType()); - - std::string expected = ""; - // for numerical values, value is "0" - if (t == "uint8[]" || t == "int8[]") { - expected = "[0]"; - } else if (t == "bool") { - expected = "false"; - } else if (t == "address[]") { - expected = "[\"0x0000000000000000000000000000000000000000\"]"; - } else if (t == "address") { - expected = "0x0000000000000000000000000000000000000000"; - } else if (t == "bool[]") { - expected = "[false]"; - } else if (t == "bytes[]") { - expected = "[\"0x\"]"; - } else if (t == "bytes") { - expected = "0x"; - } else if (t == "bytes168") { - expected = "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - } else if (t.substr(0, 3) == "int" || t.substr(0, 4) == "uint") { - expected = "0"; - } - EXPECT_EQ(expected, ParamFactory::getValue(p, t)); - } -} - -TEST(EthereumAbi, MaskForBits) { - EXPECT_EQ(0x000000ffffff, ParamUIntN::maskForBits(24)); - EXPECT_EQ(0x00ffffffffff, ParamUIntN::maskForBits(40)); -} - -TEST(EthereumAbi, ParamSetMethods) { - { - auto p = ParamSet(std::vector>{ - std::make_shared(16u), - std::make_shared(true)}); - EXPECT_EQ(p.getCount(), 2ul); - EXPECT_EQ(p.addParam(std::shared_ptr(nullptr)), -1); - - std::shared_ptr getparam; - EXPECT_TRUE(p.getParam(1, getparam)); - EXPECT_EQ(getparam->getType(), "bool"); - EXPECT_FALSE(p.getParam(2, getparam)); - - EXPECT_EQ(p.getParamUnsafe(0)->getType(), "uint256"); - EXPECT_EQ(p.getParamUnsafe(1)->getType(), "bool"); - EXPECT_EQ(p.getParamUnsafe(2)->getType(), "uint256"); - EXPECT_EQ(p.getParamUnsafe(99)->getType(), "uint256"); - } - { - auto pEmpty = ParamSet(std::vector>{}); - EXPECT_EQ(pEmpty.getParamUnsafe(0).get(), nullptr); - } -} - -TEST(EthereumAbi, ParametersMethods) { - auto p = Parameters(std::vector>{ - std::make_shared(16u), - std::make_shared(true)}); - EXPECT_TRUE(p.isDynamic()); - EXPECT_EQ(p.getCount(), 2ul); - EXPECT_FALSE(p.setValueJson("value")); - EXPECT_EQ(hex(p.hashStruct()), "755311b9e2cee471a91b161ccc5deed933d844b5af2b885543cc3c04eb640983"); -} - -} // namespace TW::Ethereum::ABI::tests diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index 699a7b1b50c..7fbf6d9875e 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -177,7 +177,7 @@ TEST(Barz, SignK1TransferAccountDeployed) { auto& transfer = *input.mutable_transaction()->mutable_transfer(); transfer.set_amount(amount.data(), amount.size()); - std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16db98b365b1f89191996942612b14f1da4bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"; + std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"; { // sign test @@ -230,7 +230,7 @@ TEST(Barz, SignR1TransferAccountNotDeployed) { auto& transfer = *input.mutable_transaction()->mutable_transfer(); transfer.set_amount(amount.data(), amount.size()); - std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392ae041bfbdbaa0cff9234a0c8f64df97b7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"; + std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"; { // sign test TW::Ethereum::Proto::SigningOutput output; @@ -279,6 +279,7 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { TWEthereumAbiFunctionAddParamUInt256(approveFunc, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("8AC7230489E80000")).get())).get(), false); auto approveCallEncoded = WRAPD(TWEthereumAbiEncode(approveFunc)); auto approveCall = data(TWDataBytes(approveCallEncoded.get()), TWDataSize(approveCallEncoded.get())); + EXPECT_EQ(hex(approveCall), "095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000"); // transfer TWEthereumAbiFunction* transferFunc = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("transfer")).get()); @@ -286,6 +287,7 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { TWEthereumAbiFunctionAddParamUInt256(transferFunc, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("8AC7230489E80000")).get())).get(), false); auto transferCallEncoded = WRAPD(TWEthereumAbiEncode(transferFunc)); auto transferCall = data(TWDataBytes(transferCallEncoded.get()), TWDataSize(transferCallEncoded.get())); + EXPECT_EQ(hex(transferCall), "a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000"); auto *batch = input.mutable_transaction()->mutable_batch(); auto *c1 = batch->add_calls(); @@ -299,7 +301,7 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { input.set_private_key(key.data(), key.size()); - std::string expected = "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1e6c542ebc7c960c6a155a9094db838cef842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}"; + std::string expected = "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}"; { // sign test TW::Ethereum::Proto::SigningOutput output; diff --git a/tests/chains/Ethereum/ContractCallTests.cpp b/tests/chains/Ethereum/ContractCallTests.cpp index 19af2d14dbf..164a9fc332b 100644 --- a/tests/chains/Ethereum/ContractCallTests.cpp +++ b/tests/chains/Ethereum/ContractCallTests.cpp @@ -14,29 +14,36 @@ extern std::string TESTS_ROOT; namespace TW::Ethereum::ABI::tests { -static nlohmann::json load_json(std::string path) { +static nlohmann::json load_json(const std::string& path) { std::ifstream stream(path); nlohmann::json json; stream >> json; return json; } +static std::string load_json_str(const std::string& path) { + std::ifstream stream(path); + std::string json((std::istreambuf_iterator(stream)), + std::istreambuf_iterator()); + return json; +} + TEST(ContractCall, Approval) { auto path = TESTS_ROOT + "/chains/Ethereum/Data/erc20.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); auto call = parse_hex("095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed" "0000000000000000000000000000000000000000000000000000000000000001"); auto decoded = decodeCall(call, abi); auto expected = - R"|({"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"},{"name":"_value","type":"uint256","value":"1"}]})|"; + R"|({"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"},{"name":"_value","type":"uint256","value":"1"}]})|"; EXPECT_EQ(decoded.value(), expected); } TEST(ContractCall, UniswapSwapTokens) { auto path = TESTS_ROOT + "/chains/Ethereum/Data/uniswap_router_v2.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); // https://etherscan.io/tx/0x57a2414f3cd9ca373b7e663ae67ecf933e40cb77a6e4ed28e4e28b5aa0d8ec63 auto call = parse_hex( "0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000" @@ -49,14 +56,14 @@ TEST(ContractCall, UniswapSwapTokens) { "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"}]})|"; + R"|({"function":"swapExactTokensForTokens(uint256,uint256,address[],address,uint256)","inputs":[{"name":"amountIn","type":"uint256","value":"1000000000000000000"},{"name":"amountOutMin","type":"uint256","value":"2494851601099271131"},{"name":"path","type":"address[]","value":["0x6B175474E89094C44Da98b954EedeAC495271d0F","0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2","0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","0xE41d2489571d322189246DaFA5ebDe1F4699F498"]},{"name":"to","type":"address","value":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1"},{"name":"deadline","type":"uint256","value":"1594806384"}]})|"; EXPECT_EQ(decoded.value(), expected); } TEST(ContractCall, KyberTrade) { auto path = TESTS_ROOT + "/chains/Ethereum/Data/kyber_proxy.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); // https://etherscan.io/tx/0x51ffab782b9a27d754389505d5a50db525c04c68142ce20512d579f10f9e13e4 auto call = parse_hex( @@ -71,14 +78,14 @@ TEST(ContractCall, KyberTrade) { 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"}]})|"; + R"|({"function":"tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)","inputs":[{"name":"src","type":"address","value":"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"},{"name":"srcAmount","type":"uint256","value":"86000000000000000000"},{"name":"dest","type":"address","value":"0xdAC17F958D2ee523a2206206994597C13D831ec7"},{"name":"destAddress","type":"address","value":"0x7755297C6A26D495739206181Fe81646dbD0Bf39"},{"name":"maxDestAmount","type":"uint256","value":"115792089237316195423570985008687907853269984665640564039457584007913129639935"},{"name":"minConversionRate","type":"uint256","value":"237731504554534883721"},{"name":"platformWallet","type":"address","value":"0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9"},{"name":"platformFeeBps","type":"uint256","value":"8"},{"name":"hint","type":"bytes","value":"0x"}]})|"; EXPECT_EQ(decoded.value(), expected); } TEST(ContractCall, ApprovalForAll) { auto path = TESTS_ROOT + "/chains/Ethereum/Data/erc721.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); // https://etherscan.io/tx/0xc2744000a107aee4761cf8a638657f91c3003a54e2f1818c37d781be7e48187a auto call = parse_hex("0xa22cb46500000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b" @@ -86,19 +93,19 @@ TEST(ContractCall, ApprovalForAll) { 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}]})|"; + R"|({"function":"setApprovalForAll(address,bool)","inputs":[{"name":"to","type":"address","value":"0x88341d1a8F672D2780C8dC725902AAe72F143B0c"},{"name":"approved","type":"bool","value":true}]})|"; EXPECT_EQ(decoded.value(), expected); } TEST(ContractCall, CustomCall) { auto path = TESTS_ROOT + "/chains/Ethereum/Data/custom.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); auto call = parse_hex("ec37a4a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000067472757374790000000000000000000000000000000000000000000000000000"); auto decoded = decodeCall(call, abi); auto expected = - R"|({"function":"setName(string,uint256,int32)","inputs":[{"name":"name","type":"string","value":"trusty"},{"name":"age","type":"uint","value":"3"},{"name":"height","type":"int32","value":"100"}]})|"; + R"|({"function":"setName(string,uint256,int32)","inputs":[{"name":"name","type":"string","value":"trusty"},{"name":"age","type":"uint256","value":"3"},{"name":"height","type":"int32","value":"100"}]})|"; EXPECT_EQ(decoded.value(), expected); } @@ -107,10 +114,10 @@ TEST(ContractCall, SetResolver) { auto call = parse_hex("0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c" "6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"); auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); auto decoded = decodeCall(call, abi); auto expected = - R"|({"function":"setResolver(bytes32,address)","inputs":[{"name":"node","type":"bytes32","value":"0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f"},{"name":"resolver","type":"address","value":"0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"}]})|"; + R"|({"function":"setResolver(bytes32,address)","inputs":[{"name":"node","type":"bytes32","value":"0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f"},{"name":"resolver","type":"address","value":"0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"}]})|"; EXPECT_EQ(decoded.value(), expected); } @@ -121,7 +128,7 @@ TEST(ContractCall, RenewENS) { "000000000000000000000000000000000000000001e18558000000000000000000000000000000000000000000" "000000000000000000000a68657769676f76656e7300000000000000000000000000000000000000000000"); auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); auto decoded = decodeCall(call, abi); auto expected = R"|({"function":"renew(string,uint256)","inputs":[{"name":"name","type":"string","value":"hewigovens"},{"name":"duration","type":"uint256","value":"31556952"}]})|"; @@ -154,7 +161,7 @@ TEST(ContractCall, Multicall) { "000000000000000000000000000000000000000000000000000000000000000000"); ASSERT_EQ(4 + 928ul, call.size()); auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); auto decoded = decodeCall(call, abi); auto expected = R"|({"function":"multicall(bytes[])","inputs":[{"name":"data","type":"bytes[]","value":["0xd5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000047331175b23c2f067204b506ca1501c26731c990","0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"]}]})|"; @@ -175,32 +182,37 @@ TEST(ContractCall, GetAmountsOut) { "0000000000000000000000000000000000000000000000000000000000000001" "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"); auto path = TESTS_ROOT + "/chains/Ethereum/Data/getAmountsOut.json"; - auto abi = load_json(path); + auto abi = load_json_str(path); auto decoded = decodeCall(call, abi); ASSERT_TRUE(decoded.has_value()); auto expected = - R"|({"function":"getAmountsOut(uint256,address[])","inputs":[{"name":"amountIn","type":"uint256","value":"100"},{"name":"path","type":"address[]","value":["0xf784682c82526e245f50975190ef0fff4e4fc077"]}]})|"; + R"|({"function":"getAmountsOut(uint256,address[])","inputs":[{"name":"amountIn","type":"uint256","value":"100"},{"name":"path","type":"address[]","value":["0xF784682C82526e245F50975190EF0fff4E4fC077"]}]})|"; EXPECT_EQ(decoded.value(), expected); } TEST(ContractCall, 1inch) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/1inch.json"; - auto abi = load_json(path); + auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/1inch.json"; + auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/1inch_decoded.json"; + + auto abi = load_json_str(abiPath); + auto expected = load_json(decodedPath); // https://etherscan.io/tx/0xc2d113151124579c21332d4cc6ab2b7f61e81d62392ed8596174513cb47e35ba auto call = parse_hex( "7c02520000000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd2000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd20000000000000000000000001611c227725c5e420ef058275ae772b41775e261000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000005c31df1da000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000"); auto decoded = decodeCall(call, abi); + ASSERT_TRUE(decoded.has_value()); - auto expected = - R"|({"function":"swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)","inputs":[{"name":"caller","type":"address","value":"0x27239549dd40e1d60f5b80b0c4196923745b1fd2"},{"components":[{"name":"srcToken","type":"address","value":"0x2b591e99afe9f32eaa6214f7b7629768c40eeb39"},{"name":"dstToken","type":"address","value":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},{"name":"srcReceiver","type":"address","value":"0x27239549dd40e1d60f5b80b0c4196923745b1fd2"},{"name":"dstReceiver","type":"address","value":"0x1611c227725c5e420ef058275ae772b41775e261"},{"name":"amount","type":"uint256","value":"6395120000000"},{"name":"minReturnAmount","type":"uint256","value":"24748356058"},{"name":"flags","type":"uint256","value":"4"},{"name":"permit","type":"bytes","value":"0x"}],"name":"desc","type":"tuple"},{"name":"data","type":"bytes","value":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000"}]})|"; - EXPECT_EQ(decoded.value(), expected); + nlohmann::json decodedJson = nlohmann::json::parse(decoded.value()); + EXPECT_EQ(decodedJson, expected); } TEST(ContractCall, TupleNested) { - auto path = TESTS_ROOT + "/chains/Ethereum/Data/tuple_nested.json"; - auto abi = load_json(path); + auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/tuple_nested.json"; + auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/tuple_nested_decoded.json"; + auto abi = load_json_str(abiPath); + auto expected = load_json(decodedPath); auto call = parse_hex( "74b6ef0b" @@ -212,15 +224,14 @@ TEST(ContractCall, TupleNested) { "0000000000000000000000000000000000000000000000000000000000000001"); auto decoded = decodeCall(call, abi); ASSERT_TRUE(decoded.has_value()); - auto expected = - R"|({"function":"nested_tuple(uint16,(uint16,(uint16,uint64),uint32),bool)","inputs":[{"name":"param1","type":"uint16","value":"1"},{"components":[{"name":"param21","type":"uint16","value":"2"},{"components":[{"name":"param221","type":"uint16","value":"3"},{"name":"param222","type":"uint64","value":"4"}],"name":"param22","type":"tuple"},{"name":"param23","type":"uint32","value":"5"}],"name":"param2","type":"tuple"},{"name":"param3","type":"bool","value":true}]})|"; - EXPECT_EQ(decoded.value(), expected); + nlohmann::json decodedJson = nlohmann::json::parse(decoded.value()); + EXPECT_EQ(decodedJson, expected); } TEST(ContractCall, TupleArray) { auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/swap_v2.json"; auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/swap_v2_decoded.json"; - auto abi = load_json(abiPath); + auto abi = load_json_str(abiPath); auto expectedJson = load_json(decodedPath); auto call = parse_hex("846a1bc6000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000820000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000003840651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000762696e616e636500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); diff --git a/tests/chains/Ethereum/Data/1inch_decoded.json b/tests/chains/Ethereum/Data/1inch_decoded.json new file mode 100644 index 00000000000..a9f8a63895e --- /dev/null +++ b/tests/chains/Ethereum/Data/1inch_decoded.json @@ -0,0 +1,61 @@ +{ + "function": "swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)", + "inputs": [ + { + "name": "caller", + "type": "address", + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" + }, + { + "components": [ + { + "name": "srcToken", + "type": "address", + "value": "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39" + }, + { + "name": "dstToken", + "type": "address", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "name": "srcReceiver", + "type": "address", + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" + }, + { + "name": "dstReceiver", + "type": "address", + "value": "0x1611C227725c5E420Ef058275AE772b41775e261" + }, + { + "name": "amount", + "type": "uint256", + "value": "6395120000000" + }, + { + "name": "minReturnAmount", + "type": "uint256", + "value": "24748356058" + }, + { + "name": "flags", + "type": "uint256", + "value": "4" + }, + { + "name": "permit", + "type": "bytes", + "value": "0x" + } + ], + "name": "desc", + "type": "tuple" + }, + { + "name": "data", + "type": "bytes", + "value": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000" + } + ] +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/swap_v2_decoded.json b/tests/chains/Ethereum/Data/swap_v2_decoded.json index 49a95d7806e..be3dab098ec 100644 --- a/tests/chains/Ethereum/Data/swap_v2_decoded.json +++ b/tests/chains/Ethereum/Data/swap_v2_decoded.json @@ -4,7 +4,7 @@ { "name": "token", "type": "address", - "value": "0xdac17f958d2ee523a2206206994597c13d831ec7" + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" }, { "name": "amount", @@ -22,7 +22,7 @@ { "name": "target", "type": "address", - "value": "0xdac17f958d2ee523a2206206994597c13d831ec7" + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" }, { "name": "value", @@ -49,7 +49,7 @@ { "name": "target", "type": "address", - "value": "0x99a58482bd75cbab83b27ec03ca68ff489b5788f" + "value": "0x99a58482BD75cbab83b27EC03CA68fF489b5788f" }, { "name": "value", @@ -94,7 +94,7 @@ { "name": "gasRefundRecipient", "type": "address", - "value": "0xa140f413c63fbda84e9008607e678258fffbc76b" + "value": "0xa140F413C63FBDA84E9008607E678258ffFbC76b" }, { "name": "enableExpress", diff --git a/tests/chains/Ethereum/Data/tuple_nested_decoded.json b/tests/chains/Ethereum/Data/tuple_nested_decoded.json new file mode 100644 index 00000000000..806049882ad --- /dev/null +++ b/tests/chains/Ethereum/Data/tuple_nested_decoded.json @@ -0,0 +1,47 @@ +{ + "function": "nested_tuple(uint16,(uint16,(uint16,uint64),uint32),bool)", + "inputs": [ + { + "name": "param1", + "type": "uint16", + "value": "1" + }, + { + "components": [ + { + "name": "param21", + "type": "uint16", + "value": "2" + }, + { + "components": [ + { + "name": "param221", + "type": "uint16", + "value": "3" + }, + { + "name": "param222", + "type": "uint64", + "value": "4" + } + ], + "name": "param22", + "type": "tuple" + }, + { + "name": "param23", + "type": "uint32", + "value": "5" + } + ], + "name": "param2", + "type": "tuple" + }, + { + "name": "param3", + "type": "bool", + "value": true + } + ] +} \ No newline at end of file diff --git a/tests/chains/Ethereum/EthereumMessageSignerTests.cpp b/tests/chains/Ethereum/EthereumMessageSignerTests.cpp index 5f43f3b6538..d119f4006da 100644 --- a/tests/chains/Ethereum/EthereumMessageSignerTests.cpp +++ b/tests/chains/Ethereum/EthereumMessageSignerTests.cpp @@ -120,7 +120,7 @@ namespace TW::Ethereum { } })"; auto signature = Ethereum::MessageSigner::signTypedData(ethKey, msg, MessageType::Eip155, 0); - ASSERT_EQ(signature, "EIP712 chainId is different than the current chainID."); + ASSERT_EQ(signature, ""); } TEST(EthereumEip191, SignMessageAndVerifyLegacy) { diff --git a/tests/chains/Ethereum/RLPTests.cpp b/tests/chains/Ethereum/RLPTests.cpp deleted file mode 100644 index e5502d58a2e..00000000000 --- a/tests/chains/Ethereum/RLPTests.cpp +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/RLP.h" -#include "HexCoding.h" - -#include - -namespace TW::Ethereum::tests { - -using boost::multiprecision::uint256_t; - -std::string stringifyItems(const RLP::DecodedItem& di); - -std::string stringifyData(const Data& data) { - if (data.size() == 0) - return "0"; - // try if only letters - bool isLettersOnly = true; - for (auto i : data) { - if (!((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || i == ' ' || i == ',')) { - isLettersOnly = false; - break; - } - } - if (isLettersOnly) - return std::string("'") + std::string(data.begin(), data.end()) + "'"; - // try if it can be parsed (recursive) - if (data.size() >= 2) { - try { - const auto di = RLP::decode(data); - if (di.decoded.size() > 0 && di.remainder.size() == 0) { - return stringifyItems(di); - } - } catch (...) { - } - } - // any other: as hex string - return hex(data); -} - -std::string stringifyItems(const RLP::DecodedItem& di) { - const auto n = di.decoded.size(); - if (n == 0) { - return "-"; - } - if (n == 1) { - return stringifyData(di.decoded[0]); - } - std::string res = "(" + std::to_string(n) + ": "; - int count = 0; - for (auto i : di.decoded) { - if (count++) - res += " "; - res += stringifyData(i); - } - res += ")"; - return res; -} - -std::string decodeHelper(const std::string& hexData) { - const auto data = parse_hex(hexData); - const auto di = RLP::decode(data); - return stringifyItems(di); -} - -TEST(RLP, EncodeString) { - EXPECT_EQ(hex(RLP::encode("")), "80"); - EXPECT_EQ(hex(RLP::encode("d")), "64"); - EXPECT_EQ(hex(RLP::encode("dog")), "83646f67"); -} - -TEST(RLP, EncodeInteger) { - EXPECT_EQ(hex(RLP::encode(0)), "80"); - EXPECT_EQ(hex(RLP::encode(127)), "7f"); - EXPECT_EQ(hex(RLP::encode(128)), "8180"); - EXPECT_EQ(hex(RLP::encode(255)), "81ff"); - EXPECT_EQ(hex(RLP::encode(256)), "820100"); - EXPECT_EQ(hex(RLP::encode(1024)), "820400"); - EXPECT_EQ(hex(RLP::encode(0xffff)), "82ffff"); - EXPECT_EQ(hex(RLP::encode(0x010000)), "83010000"); - EXPECT_EQ(hex(RLP::encode(0xffffff)), "83ffffff"); - EXPECT_EQ(hex(RLP::encode(static_cast(0xffffffffULL))), "84ffffffff"); - EXPECT_EQ(hex(RLP::encode(static_cast(0xffffffffffffffULL))), "87ffffffffffffff"); -} - -TEST(RLP, EncodeUInt256) { - EXPECT_EQ(hex(RLP::encode(uint256_t(0))), "80"); - EXPECT_EQ(hex(RLP::encode(uint256_t(1))), "01"); - EXPECT_EQ(hex(RLP::encode(uint256_t(127))), "7f"); - EXPECT_EQ(hex(RLP::encode(uint256_t(128))), "8180"); - EXPECT_EQ(hex(RLP::encode(uint256_t(256))), "820100"); - EXPECT_EQ(hex(RLP::encode(uint256_t(1024))), "820400"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffff))), "83ffffff"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffULL))), "84ffffffff"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffffffffULL))), "87ffffffffffffff"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x102030405060708090a0b0c0d0e0f2"))), - "8f102030405060708090a0b0c0d0e0f2"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x0100020003000400050006000700080009000a000b000c000d000e01"))), - "9c0100020003000400050006000700080009000a000b000c000d000e01"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x0100000000000000000000000000000000000000000000000000000000000000"))), - "a00100000000000000000000000000000000000000000000000000000000000000"); -} - -TEST(RLP, EncodeList) { - EXPECT_EQ(hex(RLP::encodeList(std::vector())), "c0"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{1, 2, 3})), "c3010203"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{"a", "b"})), "c26162"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{"cat", "dog"})), "c88363617483646f67"); - { - const auto encoded = RLP::encodeList(std::vector(1024)); - EXPECT_EQ(hex(subData(encoded, 0, 20)), "f904008080808080808080808080808080808080"); - } -} - -TEST(RLP, EncodeListNested) { - const auto l11 = RLP::encodeList(std::vector{1, 2, 3}); - const auto l12 = RLP::encodeList(std::vector{"apple", "banana", "cherry"}); - const auto l21 = RLP::encodeList(std::vector{parse_hex("abcdef"), parse_hex("00010203040506070809")}); - const auto l22 = RLP::encodeList(std::vector{"bitcoin", "beeenbee", "eth"}); - const auto l1 = RLP::encodeList(std::vector{l11, l12}); - const auto l2 = RLP::encodeList(std::vector{l21, l22}); - const auto encoded = RLP::encodeList(std::vector{l1, l2}); - EXPECT_EQ(hex(encoded), "f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"); -} - -TEST(RLP, EncodeInvalid) { - ASSERT_TRUE(RLP::encode(-1).empty()); - ASSERT_TRUE(RLP::encodeList(std::vector{0, -1}).empty()); -} - -TEST(RLP, DecodeInteger) { - EXPECT_EQ(decodeHelper("00"), "00"); // not the primary encoding for 0 - EXPECT_EQ(decodeHelper("01"), "01"); - EXPECT_EQ(decodeHelper("09"), "09"); - EXPECT_EQ(decodeHelper("7f"), "7f"); - EXPECT_EQ(decodeHelper("80"), "0"); - EXPECT_EQ(decodeHelper("8180"), "80"); - EXPECT_EQ(decodeHelper("81ff"), "ff"); - EXPECT_EQ(decodeHelper("820100"), "0100"); - EXPECT_EQ(decodeHelper("820400"), "0400"); - EXPECT_EQ(decodeHelper("82ffff"), "ffff"); - EXPECT_EQ(decodeHelper("83010000"), "010000"); - EXPECT_EQ(decodeHelper("83ffffff"), "ffffff"); - EXPECT_EQ(decodeHelper("84ffffffff"), "ffffffff"); - EXPECT_EQ(decodeHelper("87ffffffffffffff"), "ffffffffffffff"); -} - -TEST(RLP, DecodeString) { - EXPECT_EQ(decodeHelper("80"), "0"); - EXPECT_EQ(decodeHelper("64"), "'d'"); - EXPECT_EQ(decodeHelper("83646f67"), "'dog'"); - EXPECT_EQ(decodeHelper("83636174"), "'cat'"); - EXPECT_EQ(decodeHelper("8f102030405060708090a0b0c0d0e0f2"), "102030405060708090a0b0c0d0e0f2"); - EXPECT_EQ(decodeHelper("9c0100020003000400050006000700080009000a000b000c000d000e01"), "0100020003000400050006000700080009000a000b000c000d000e01"); - EXPECT_EQ(decodeHelper("a00100000000000000000000000000000000000000000000000000000000000000"), "0100000000000000000000000000000000000000000000000000000000000000"); - // long string - EXPECT_EQ(decodeHelper("b87674686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e67"), - "'this is a a very long string, this is a a very long string, this is a a very long string, this is a a very long string'"); -} - -TEST(RLP, DecodeList) { - // empty list - EXPECT_EQ(decodeHelper("c0"), "-"); - // short list - EXPECT_EQ(decodeHelper("c3010203"), "(3: 01 02 03)"); - EXPECT_EQ(decodeHelper("c26162"), "(2: 'a' 'b')"); - EXPECT_EQ(decodeHelper("c88363617483646f67"), "(2: 'cat' 'dog')"); - - // long list, raw ether transfer tx - EXPECT_EQ(decodeHelper("f86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"), - "(9: " - "a9 " // nonce - "051f4d5ce9 " // gas price - "5208 " // gas limit - "515778891c99e3d2e7ae489980cb7c77b37b5e76 " // to - "1b48eb57e000 " // amount - "0 " // data - "25 " // v - "ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475 " // r - "0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65" // s - ")"); - - // long list, raw token transfer tx - EXPECT_EQ(decodeHelper("f8aa81d485077359400082db9194dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf0801ca02843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6aca05d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a"), - "(9: " - "d4 " - "0773594000 " - "db91 " - "dac17f958d2ee523a2206206994597c13d831ec7 " - "0 " - "a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080 " - "1c " - "2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac " - "5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a" - ")"); - - { - // long list, with 2-byte size - const std::string elem = "0123"; - const std::size_t n = 500; - std::vector longarr; - for (auto i = 0ul; i < n; ++i) - longarr.push_back(elem); - - const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(hex(subData(encoded, 0, 20)), "f909c48430313233843031323384303132338430"); - - auto decoded = RLP::decode(encoded); - ASSERT_EQ(decoded.decoded.size(), n); - for (int i = 0; i < 20; i++) { - EXPECT_EQ(hex(decoded.decoded[i]), "30313233"); - } - } - { - // long list, with 3-byte size - const std::string elem = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - const std::size_t n = 650; - std::vector longarr; - for (auto i = 0ul; i < n; ++i) - longarr.push_back(elem); - - const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(encoded.size(), 66304ul); - ASSERT_EQ(hex(subData(encoded, 0, 30)), "fa0102fcb864303132333435363738393031323334353637383930313233"); - - auto decoded = RLP::decode(encoded); - ASSERT_EQ(decoded.decoded.size(), n); - } - - // nested list - EXPECT_EQ(decodeHelper("f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"), - "(2: (2: (3: 01 02 03) (3: 'apple' 'banana' 'cherry')) (2: (2: abcdef 00010203040506070809) (3: 'bitcoin' 'beeenbee' 'eth')))"); -} - -TEST(RLP, DecodeInvalid) { - // decode empty data - EXPECT_THROW(RLP::decode(Data()), std::invalid_argument); - - // incorrect length - EXPECT_THROW(RLP::decode(parse_hex("0x81636174")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("0xb9ffff")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("0xc883636174")), std::invalid_argument); - - // some tests are from https://github.com/ethereum/tests/blob/develop/RLPTests/invalidRLPTest.json - // int32 overflow - EXPECT_THROW(RLP::decode(parse_hex("0xbf0f000000000000021111")), std::invalid_argument); - - // wrong size list - EXPECT_THROW(RLP::decode(parse_hex("0xf80180")), std::invalid_argument); - - // bytes should be single byte - EXPECT_THROW(RLP::decode(parse_hex("0x8100")), std::invalid_argument); - - // leading zeros in long length list - EXPECT_THROW(RLP::decode(parse_hex("fb00000040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("f800")), std::invalid_argument); -} - -TEST(RLP, putVarInt) { - EXPECT_EQ(hex(RLP::putVarInt(0)), "00"); - EXPECT_EQ(hex(RLP::putVarInt(1)), "01"); - EXPECT_EQ(hex(RLP::putVarInt(0x21)), "21"); - EXPECT_EQ(hex(RLP::putVarInt(0xff)), "ff"); - EXPECT_EQ(hex(RLP::putVarInt(0x100)), "0100"); - EXPECT_EQ(hex(RLP::putVarInt(0x4321)), "4321"); - EXPECT_EQ(hex(RLP::putVarInt(0x654321)), "654321"); - EXPECT_EQ(hex(RLP::putVarInt(0x87654321)), "87654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xa987654321)), "a987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xcba987654321)), "cba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xedcba987654321)), "edcba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0x21edcba987654321)), "21edcba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xffffffffffffffff)), "ffffffffffffffff"); -} - -TEST(RLP, parseVarInt) { - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("00"), 0))), "00"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("01"), 0))), "01"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("fc"), 0))), "fc"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("ff"), 0))), "ff"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("abcd"), 1))), "cd"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("0102"), 0))), "0102"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("0100"), 0))), "0100"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("fedc"), 0))), "fedc"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("ffff"), 0))), "ffff"); - EXPECT_EQ(hex(store(RLP::parseVarInt(3, parse_hex("010203"), 0))), "010203"); - EXPECT_EQ(hex(store(RLP::parseVarInt(4, parse_hex("01020304"), 0))), "01020304"); - EXPECT_EQ(hex(store(RLP::parseVarInt(5, parse_hex("0102030405"), 0))), "0102030405"); - EXPECT_EQ(hex(store(RLP::parseVarInt(6, parse_hex("010203040506"), 0))), "010203040506"); - EXPECT_EQ(hex(store(RLP::parseVarInt(7, parse_hex("01020304050607"), 0))), "01020304050607"); - EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("0102030405060708"), 0))), "0102030405060708"); - EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("abcd0102030405060708"), 2))), "0102030405060708"); - EXPECT_THROW(RLP::parseVarInt(0, parse_hex("01"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(9, parse_hex("010203040506070809"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("0102"), 0), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("01020304"), 2), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(2, parse_hex("0002"), 0), std::invalid_argument); // starts with 0 -} - -TEST(RLP, decodeLenOverflow) { - EXPECT_THROW(RLP::decode(parse_hex("c9bffffffffffffffff7")), std::invalid_argument); // String length overflow (64 bit) - EXPECT_THROW(RLP::decode(parse_hex("c7fbfffffffbc17f")), std::invalid_argument); // List length overflow (32 bit) - EXPECT_THROW(RLP::decode(parse_hex("cbfffffffffffffffff7c17f")), std::invalid_argument); // List length overflow (64 bit) -} - -} // namespace TW::Ethereum::tests diff --git a/tests/chains/Ethereum/SignerTests.cpp b/tests/chains/Ethereum/SignerTests.cpp deleted file mode 100644 index 524542ff212..00000000000 --- a/tests/chains/Ethereum/SignerTests.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/Address.h" -#include "Ethereum/MessageSigner.h" -#include "Ethereum/RLP.h" -#include "Ethereum/Signer.h" -#include "Ethereum/Transaction.h" -#include "HexCoding.h" - -#include - -namespace TW::Ethereum { - -using boost::multiprecision::uint256_t; - -TEST(EthereumTransaction, encodeTransactionNonTyped) { - const auto transaction = TransactionNonTyped::buildERC20Transfer( - /* nonce: */ 0, - /* gasPrice: */ 42000000000, // 0x09c7652400 - /* gasLimit: */ 78009, // 130B9 - /* tokenContract: */ parse_hex("0x6b175474e89094c44da98b954eedeac495271d0f"), // DAI - /* toAddress: */ parse_hex("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), - /* amount: */ 2000000000000000000 - ); - const uint256_t dummyChain = 0x34; - const auto dummySignature = Signature{0, 0, 0}; - - const auto preHash = transaction->preHash(dummyChain); - EXPECT_EQ(hex(preHash), "b3525019dc367d3ecac48905f9a95ff3550c25a24823db765f92cae2dec7ebfd"); - - const auto encoded = transaction->encoded(dummySignature, dummyChain); - EXPECT_EQ(hex(encoded), "f86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); -} - -TEST(EthereumSigner, PreHash) { - const auto address = parse_hex("0x3535353535353535353535353535353535353535"); - const auto transaction = TransactionNonTyped::buildNativeTransfer( - /* nonce: */ 9, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ address, - /* amount: */ 1000000000000000000 - ); - const auto preHash = transaction->preHash(1); - - ASSERT_EQ(hex(preHash), "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"); -} - -TEST(EthereumSigner, Sign) { - const auto address = parse_hex("0x3535353535353535353535353535353535353535"); - const auto transaction = TransactionNonTyped::buildNativeTransfer( - /* nonce: */ 9, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ address, - /* amount: */ 1000000000000000000 - ); - - const auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); - const auto signature = Signer::sign(key, 1, transaction); - - ASSERT_EQ(signature.v, 37); - ASSERT_EQ(signature.r, uint256_t("18515461264373351373200002665853028612451056578545711640558177340181847433846")); - ASSERT_EQ(signature.s, uint256_t("46948507304638947509940763649030358759909902576025900602547168820602576006531")); -} - -TEST(EthereumSigner, SignERC20Transfer) { - const auto transaction = TransactionNonTyped::buildERC20Transfer( - /* nonce: */ 0, - /* gasPrice: */ 42000000000, // 0x09c7652400 - /* gasLimit: */ 78009, // 130B9 - /* tokenContract: */ parse_hex("0x6b175474e89094c44da98b954eedeac495271d0f"), // DAI - /* toAddress: */ parse_hex("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), - /* amount: */ 2000000000000000000 - ); - - const auto key = PrivateKey(parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")); - const auto signature = Signer::sign(key, 1, transaction); - - ASSERT_EQ(signature.v, 37); - ASSERT_EQ(hex(store(signature.r)), "724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98ce"); - ASSERT_EQ(hex(store(signature.s)), "032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"); -} - -TEST(EthereumSigner, EIP1559_1442) { - const auto transaction = TransactionEip1559::buildNativeTransfer( - /* nonce: */ 6, - /* maxInclusionFeePerGas */ 2000000000, - /* maxFeePerGas */ 3000000000, - /* gasLimit */ 21100, - /* toAddress */ parse_hex("B9F5771C27664bF2282D98E09D7F50cEc7cB01a7"), - /* amount */ 543210987654321, - /* data */ Data() - ); - - const uint256_t chainID = 3; - const auto key = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - const auto signature = Signer::sign(key, chainID, transaction); - EXPECT_EQ(signature.v, 0); - EXPECT_EQ(hex(store(signature.r)), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64"); - EXPECT_EQ(hex(store(signature.s)), "6487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); - - const auto encoded = transaction->encoded(signature, chainID); - // https://ropsten.etherscan.io/tx/0x14429509307efebfdaa05227d84c147450d168c68539351fbc01ed87c916ab2e - EXPECT_EQ(hex(encoded), "02f8710306847735940084b2d05e0082526c94b9f5771c27664bf2282d98e09d7f50cec7cb01a78701ee0c29f50cb180c080a092c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64a06487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); -} - -TEST(EthereumSigner, SignatureBreakdownNoEip155) { - const auto key = PrivateKey(parse_hex("f9fb27c90dcaa5631f373330eeef62ae7931587a19bd8215d0c2addf28e439c8")); - const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); - const auto signature = Signer::sign(key, hash, false, 5); - - const auto r = store(signature.r); - EXPECT_EQ(hex(r), "d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); - - const auto v = store(signature.v); - EXPECT_EQ(hex(v), "00"); - - const auto s = store(signature.s); - EXPECT_EQ(hex(s), "786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); - - const auto converted = Signer::simpleStructToSignatureData(signature); - EXPECT_EQ(hex(converted), "d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a00"); -} - -TEST(EthereumSigner, SignatureBreakdownEip155Legacy) { - const auto key = PrivateKey(parse_hex("f9fb27c90dcaa5631f373330eeef62ae7931587a19bd8215d0c2addf28e439c8")); - const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); - const auto signature = Signer::sign(key, hash, true, 0); - - const auto r = store(signature.r); - EXPECT_EQ(hex(r), "d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); - - const auto v = store(signature.v); - EXPECT_EQ(hex(v), "1b"); - - const auto s = store(signature.s); - EXPECT_EQ(hex(s), "786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); - - const auto converted = Signer::simpleStructToSignatureData(signature); - EXPECT_EQ(hex(converted), "d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a1b"); -} - -TEST(EthereumSigner, SignatureBreakdownEip155) { - const auto key = PrivateKey(parse_hex("f9fb27c90dcaa5631f373330eeef62ae7931587a19bd8215d0c2addf28e439c8")); - const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); - const auto signature = Signer::sign(key, hash, true, 1); - - const auto r = store(signature.r); - EXPECT_EQ(hex(r), "d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); - - const auto v = store(signature.v); - EXPECT_EQ(hex(v), "25"); - - const auto s = store(signature.s); - EXPECT_EQ(hex(s), "786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); - - const auto converted = Signer::simpleStructToSignatureData(signature); - EXPECT_EQ(hex(converted), "d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a25"); -} - -} // namespace TW::Ethereum diff --git a/tests/chains/Ethereum/TWAnySignerTests.cpp b/tests/chains/Ethereum/TWAnySignerTests.cpp index 1008271ca04..c26d026c51b 100644 --- a/tests/chains/Ethereum/TWAnySignerTests.cpp +++ b/tests/chains/Ethereum/TWAnySignerTests.cpp @@ -9,10 +9,7 @@ #include "HexCoding.h" #include "uint256.h" #include "proto/Ethereum.pb.h" -#include "Ethereum/Address.h" #include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamBase.h" -#include "Ethereum/ABI/ParamAddress.h" #include "PrivateKey.h" #include @@ -111,10 +108,10 @@ TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { // expected payload Data payload; { - auto func = ABI::Function("transfer", std::vector>{ - std::make_shared(parse_hex(toAddress)), - std::make_shared(amount)}); - func.encode(payload); + payload = ABI::Function::encodeFunctionCall("transfer", Ethereum::ABI::BaseParams{ + std::make_shared(toAddress), + std::make_shared(amount) + }).value(); } ASSERT_EQ(hex(output.data()), hex(payload)); ASSERT_EQ(hex(output.data()), "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); @@ -285,13 +282,13 @@ TEST(TWAnySignerEthereum, SignERC1155Transfer) { // expected payload Data payload; { - auto func = ABI::Function("safeTransferFrom", std::vector>{ - std::make_shared(parse_hex(fromAddress)), - std::make_shared(parse_hex(toAddress)), - std::make_shared(uint256_t(0x23c47ee5)), - std::make_shared(value), - std::make_shared(data)}); - func.encode(payload); + auto funcData = ABI::Function::encodeFunctionCall("safeTransferFrom", Ethereum::ABI::BaseParams{ + std::make_shared(fromAddress), + std::make_shared(toAddress), + std::make_shared(uint256_t(0x23c47ee5)), + std::make_shared(value), + std::make_shared(data)}); + payload = funcData.value(); } ASSERT_EQ(hex(output.data()), hex(payload)); ASSERT_EQ(hex(output.data()), "f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000"); @@ -503,8 +500,8 @@ TEST(TWAnySignerEthereum, StakeRocketPool) { Data payload; { - auto func = ABI::Function("deposit", std::vector>{ }); - func.encode(payload); + auto funcData = ABI::Function::encodeFunctionCall("deposit", Ethereum::ABI::BaseParams{ }); + payload = funcData.value(); } @@ -551,9 +548,10 @@ TEST(TWAnySignerEthereum, UnstakeRocketPool) { Data payload; { - auto func = ABI::Function("burn", std::vector>{ - std::make_shared(uint256_t(0x21faa32ab2502b))}); - func.encode(payload); + auto funcData = ABI::Function::encodeFunctionCall("burn", ABI::BaseParams{ + std::make_shared(uint256_t(0x21faa32ab2502b)) + }); + payload = funcData.value(); } Proto::SigningInput input; diff --git a/tests/chains/Ethereum/TWEthereumAbiTests.cpp b/tests/chains/Ethereum/TWEthereumAbiTests.cpp index 655b9e2c610..56fc343c08a 100644 --- a/tests/chains/Ethereum/TWEthereumAbiTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiTests.cpp @@ -8,9 +8,9 @@ #include #include -#include "Ethereum/ABI.h" #include "Data.h" #include "HexCoding.h" +#include "proto/EthereumAbi.pb.h" #include "uint256.h" #include "TestUtilities.h" @@ -60,7 +60,7 @@ TEST(TWEthereumAbi, FuncCreate2) { auto p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val.get(), false); EXPECT_EQ(0, p1index); - Data dummy(0); + Data dummy = store(0); auto p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); EXPECT_EQ(0, p2index); @@ -154,7 +154,7 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { TWEthereumAbiFunctionAddParamUIntN(func, 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); TWEthereumAbiFunctionAddParamUInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); TWEthereumAbiFunctionAddParamInt8(func, 1, false); - TWEthereumAbiFunctionAddParamInt16(func, 2, false); + TWEthereumAbiFunctionAddParamInt16(func, -3, false); TWEthereumAbiFunctionAddParamInt32(func, 3, false); TWEthereumAbiFunctionAddParamInt64(func, 4, false); TWEthereumAbiFunctionAddParamIntN(func, 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); @@ -173,7 +173,7 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { TWEthereumAbiFunctionAddInArrayParamUIntN(func, TWEthereumAbiFunctionAddParamArray(func, false), 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); TWEthereumAbiFunctionAddInArrayParamUInt256(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); TWEthereumAbiFunctionAddInArrayParamInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); - TWEthereumAbiFunctionAddInArrayParamInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), 2); + TWEthereumAbiFunctionAddInArrayParamInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), -3); TWEthereumAbiFunctionAddInArrayParamInt32(func, TWEthereumAbiFunctionAddParamArray(func, false), 3); TWEthereumAbiFunctionAddInArrayParamInt64(func, TWEthereumAbiFunctionAddParamArray(func, false), 4); TWEthereumAbiFunctionAddInArrayParamIntN(func, TWEthereumAbiFunctionAddParamArray(func, false), 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); @@ -201,6 +201,7 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { auto encoded = WRAPD(TWEthereumAbiEncode(func)); Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); EXPECT_EQ(4ul + 76 * 32, enc2.size()); + EXPECT_EQ(hex(enc2), "70efb5a50000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000440000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000000000000000000000000000000000000000000480313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c64210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053132333435000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013132333435000000000000000000000000000000000000000000000000000000"); // delete TWEthereumAbiFunctionDelete(func); @@ -261,13 +262,22 @@ TEST(TWEthereumAbi, GetParamWrongType) { } 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"}]})|"; + auto encodedCall = parse_hex("c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000"); + auto abiJson = R"|({"c47f0027":{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}})|"; + + EthereumAbi::Proto::ContractCallDecodingInput input; + input.set_encoded(encodedCall.data(), encodedCall.size()); + input.set_smart_contract_abi_json(abiJson); - assertStringsEqual(decoded, expected); + const auto inputData = data(input.SerializeAsString()); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t*)inputData.data(), inputData.size())); + auto outputTWData = WRAPD(TWEthereumAbiDecodeContractCall(TWCoinTypeEthereum, inputTWData.get())); + + EthereumAbi::Proto::ContractCallDecodingOutput output; + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get()))); + + auto expected = R"|({"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]})|"; + EXPECT_EQ(output.decoded_json(), expected); } TEST(TWEthereumAbi, DecodeInvalidCall) { diff --git a/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp index 0ef20806179..b5d7ac0aef8 100644 --- a/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp @@ -49,7 +49,7 @@ TEST(TWEthereumAbiValue, decodeValue) { { const auto input = "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"; const auto type = "address"; - const auto expected = "0xf784682c82526e245f50975190ef0fff4e4fc077"; + const auto expected = "0xF784682C82526e245F50975190EF0fff4E4fC077"; auto data = WRAPD(TWDataCreateWithHexString(STRING(input).get())); auto result = WRAPS(TWEthereumAbiValueDecodeValue(data.get(), WRAPS(TWStringCreateWithUTF8Bytes(type)).get())); EXPECT_EQ(std::string(expected), std::string(TWStringUTF8Bytes(result.get()))); @@ -97,8 +97,8 @@ TEST(TWEthereumAbiValue, decodeArray) { "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3"; const auto type = "address[]"; const auto expected = - "[\"0xf784682c82526e245f50975190ef0fff4e4fc077\"," - "\"0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3\"]"; + "[\"0xF784682C82526e245F50975190EF0fff4E4fC077\"," + "\"0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3\"]"; auto data = WRAPD(TWDataCreateWithHexString(STRING(input).get())); auto result = WRAPS(TWEthereumAbiValueDecodeArray(data.get(), WRAPS(TWStringCreateWithUTF8Bytes(type)).get())); EXPECT_EQ(std::string(expected), std::string(TWStringUTF8Bytes(result.get()))); diff --git a/tests/chains/Ethereum/TWRlpTests.cpp b/tests/chains/Ethereum/TWRlpTests.cpp new file mode 100644 index 00000000000..745f3467a59 --- /dev/null +++ b/tests/chains/Ethereum/TWRlpTests.cpp @@ -0,0 +1,51 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "TrustWalletCore/TWEthereumRlp.h" +#include "proto/EthereumRlp.pb.h" +#include "HexCoding.h" +#include "TestUtilities.h" +#include "uint256.h" + +#include + +using namespace TW; + +TEST(TWEthereumRlp, Eip1559) { + auto chainId = store(10); + auto nonce = store(6); + auto maxInclusionFeePerGas = 2'000'000'000; + auto maxFeePerGas = store(3'000'000'000); + auto gasLimit = store(21'100); + const auto* to = "0x6b175474e89094c44da98b954eedeac495271d0f"; + auto amount = 0; + auto payload = parse_hex("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1"); + + EthereumRlp::Proto::EncodingInput input; + auto* list = input.mutable_item()->mutable_list(); + + list->add_items()->set_number_u256(chainId.data(), chainId.size()); + list->add_items()->set_number_u256(nonce.data(), nonce.size()); + list->add_items()->set_number_u64(maxInclusionFeePerGas); + list->add_items()->set_number_u256(maxFeePerGas.data(), maxFeePerGas.size()); + list->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + list->add_items()->set_address(to); + list->add_items()->set_number_u64(amount); + list->add_items()->set_data(payload.data(), payload.size()); + // Append an empty `access_list`. + list->add_items()->mutable_list(); + + auto inputData = input.SerializeAsString(); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + auto outputTWData = WRAPD(TWEthereumRlpEncode(TWCoinTypeEthereum, inputTWData.get())); + + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(output.error_message().empty()); + EXPECT_EQ(hex(output.encoded()), "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0"); +} diff --git a/tests/chains/Ethereum/TransactionCompilerTests.cpp b/tests/chains/Ethereum/TransactionCompilerTests.cpp index 79c0feb2c7c..5a37950eeb6 100644 --- a/tests/chains/Ethereum/TransactionCompilerTests.cpp +++ b/tests/chains/Ethereum/TransactionCompilerTests.cpp @@ -25,33 +25,22 @@ using namespace TW; TEST(EthereumCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = - TransactionCompiler::buildInput(coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "" // chainId - ); - - // Check, by parsing - EXPECT_EQ((int)txInputData0.size(), 61); Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); - EXPECT_EQ(hex(input.chain_id()), "01"); - EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(input.transaction().has_transfer()); - EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); - // Set a few other values const auto nonce = store(uint256_t(11)); + const auto chainId = store(uint256_t(1)); const auto gasPrice = store(uint256_t(20000000000)); const auto gasLimit = store(uint256_t(21000)); + const auto amount = store(uint256_t(1'000'000'000'000'000'000)); + input.set_nonce(nonce.data(), nonce.size()); + input.set_chain_id(chainId.data(), chainId.size()); input.set_gas_price(gasPrice.data(), gasPrice.size()); input.set_gas_limit(gasLimit.data(), gasLimit.size()); input.set_tx_mode(Ethereum::Proto::Legacy); + input.set_to_address("0x3535353535353535353535353535353535353535"); + + input.mutable_transaction()->mutable_transfer()->set_amount(amount.data(), amount.size()); // Serialize back, this shows how to serialize SigningInput protobuf to byte array const auto txInputData = data(input.SerializeAsString()); @@ -120,7 +109,7 @@ TEST(EthereumCompiler, CompileWithSignatures) { } } -TEST(EthereumCompiler, BuildTransactionInput) { +TEST(EthereumCompiler, BuildTransactionInputShouldFail) { const auto coin = TWCoinTypeEthereum; const auto txInputData0 = TransactionCompiler::buildInput(coin, @@ -131,27 +120,6 @@ TEST(EthereumCompiler, BuildTransactionInput) { "Memo", // memo "05" // chainId ); - - // Check, by parsing - EXPECT_EQ(txInputData0.size(), 61ul); - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); - EXPECT_EQ(hex(input.chain_id()), "05"); - EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(input.transaction().has_transfer()); - EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); -} - -TEST(EthereumCompiler, BuildTransactionInputInvalidAddress) { - const auto coin = TWCoinTypeEthereum; - EXPECT_EXCEPTION( - TransactionCompiler::buildInput(coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "__INVALID_ADDRESS__", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "" // chainId - ), - "Invalid to address"); + // Ethereum doesn't support `buildInput`. + EXPECT_TRUE(txInputData0.empty()); } diff --git a/tests/chains/Ethereum/ValueDecoderTests.cpp b/tests/chains/Ethereum/ValueDecoderTests.cpp index d2cdc0e8b24..2f9e547dea5 100644 --- a/tests/chains/Ethereum/ValueDecoderTests.cpp +++ b/tests/chains/Ethereum/ValueDecoderTests.cpp @@ -29,7 +29,7 @@ TEST(EthereumAbiValueDecoder, decodeValue) { EXPECT_EQ("42", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000000002a"), "uint")); EXPECT_EQ("24", ABI::ValueDecoder::decodeValue(parse_hex("0000000000000000000000000000000000000000000000000000000000000018"), "uint8")); EXPECT_EQ("123456", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000001e240"), "uint256")); - EXPECT_EQ("0xf784682c82526e245f50975190ef0fff4e4fc077", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")); + EXPECT_EQ("0xF784682C82526e245F50975190EF0fff4E4fC077", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")); EXPECT_EQ("Hello World! Hello World! Hello World!", ABI::ValueDecoder::decodeValue(parse_hex( "000000000000000000000000000000000000000000000000000000000000002c" @@ -39,46 +39,4 @@ TEST(EthereumAbiValueDecoder, decodeValue) { EXPECT_EQ("0x31323334353637383930", ABI::ValueDecoder::decodeValue(parse_hex("3132333435363738393000000000000000000000000000000000000000000000"), "bytes10")); } -TEST(EthereumAbiValueDecoder, decodeArray) { - { - // Array of UInt8 - Data input = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000031" - "0000000000000000000000000000000000000000000000000000000000000032" - "0000000000000000000000000000000000000000000000000000000000000033"); - auto res = ABI::ValueDecoder::decodeArray(input, "uint8[]"); - EXPECT_EQ(3ul, res.size()); - EXPECT_EQ("49", res[0]); - EXPECT_EQ("50", res[1]); - EXPECT_EQ("51", res[2]); - } - { - // Array of Address - Data input = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000002" - "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3"); - auto res = ABI::ValueDecoder::decodeArray(input, "address[]"); - EXPECT_EQ(2ul, res.size()); - EXPECT_EQ("0xf784682c82526e245f50975190ef0fff4e4fc077", res[0]); - EXPECT_EQ("0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", res[1]); - } - { - // Array of ByteArray - Data input = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000040" - "0000000000000000000000000000000000000000000000000000000000000080" - "0000000000000000000000000000000000000000000000000000000000000002" - "1011000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000003" - "1022220000000000000000000000000000000000000000000000000000000000"); - auto res = ABI::ValueDecoder::decodeArray(input, "bytes[]"); - EXPECT_EQ(2ul, res.size()); - EXPECT_EQ("0x1011", res[0]); - EXPECT_EQ("0x102222", res[1]); - } -} - } // namespace TW::Ethereum::tests diff --git a/tests/chains/Evmos/SignerTests.cpp b/tests/chains/Evmos/SignerTests.cpp index 02ae7c9b45e..981bd727b27 100644 --- a/tests/chains/Evmos/SignerTests.cpp +++ b/tests/chains/Evmos/SignerTests.cpp @@ -8,9 +8,9 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "TestUtilities.h" +#include #include #include @@ -46,7 +46,8 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeEvmos); auto anotherExpectedJson =R"( { "mode":"block", @@ -64,7 +65,7 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { "type":"ethermint/PubKeyEthSecp256k1", "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" }, - "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" } ]} })"_json; @@ -82,7 +83,7 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { "type":"ethermint/PubKeyEthSecp256k1", "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" }, - "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" } ])"_json; EXPECT_EQ(signatures, expectedSignatures); @@ -115,10 +116,13 @@ TEST(EvmosSigner, CompoundingAuthz) { auto privateKey = parse_hex("79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeEvmos); + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM auto expected = R"( { - "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM" + "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAESNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkBXaTo3nk5EMFW9Euheez5ADx2bWo7XisNJ5vuGj1fKXh6CGNJGfJj/q1XUkBzaCvPNg+EcFHgtJdVSyF4cJZTg" })"; assertJSONEqual(output.serialized(), expected); } diff --git a/tests/chains/Evmos/TransactionCompilerTests.cpp b/tests/chains/Evmos/TransactionCompilerTests.cpp index 72d8ab82606..c58e374fb1d 100644 --- a/tests/chains/Evmos/TransactionCompilerTests.cpp +++ b/tests/chains/Evmos/TransactionCompilerTests.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Base64.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" #include "proto/TransactionCompiler.pb.h" @@ -95,8 +94,7 @@ TEST(EvmosCompiler, CompileWithSignatures) { EXPECT_EQ(output.error(), Common::Proto::OK); EXPECT_EQ(output.serialized(), expectedTx); - EXPECT_EQ(output.signature(), ""); - EXPECT_EQ(hex(output.signature()), ""); + EXPECT_EQ(hex(output.signature()), hex(signature)); } } diff --git a/tests/chains/Greenfield/TWCoinTypeTests.cpp b/tests/chains/Greenfield/TWCoinTypeTests.cpp index 7e1545727eb..09299b33942 100644 --- a/tests/chains/Greenfield/TWCoinTypeTests.cpp +++ b/tests/chains/Greenfield/TWCoinTypeTests.cpp @@ -19,9 +19,9 @@ TEST(TWGreenfieldCoinType, TWCoinType) { const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); const auto chainId = WRAPS(TWCoinTypeChainId(coin)); - const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("9F895CF2DD64FB1F428CEFCF2A6585A813C3540FC9FE1EF42DB1DA2CB1DF55AB")); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3")); const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); - const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9d1d97adfcd324bbd603d3872bd78e04098510b1")); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xcf0f6b88ed72653b00fdebbffc90b98072cb3285")); const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); assertStringsEqual(id, "greenfield"); @@ -31,7 +31,7 @@ TEST(TWGreenfieldCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainGreenfield); ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); - assertStringsEqual(chainId, "5600"); - assertStringsEqual(txUrl, "https://greenfieldscan.com/tx/9F895CF2DD64FB1F428CEFCF2A6585A813C3540FC9FE1EF42DB1DA2CB1DF55AB"); - assertStringsEqual(accUrl, "https://greenfieldscan.com/account/0x9d1d97adfcd324bbd603d3872bd78e04098510b1"); + assertStringsEqual(chainId, "9000"); + assertStringsEqual(txUrl, "https://greenfieldscan.com/tx/0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3"); + assertStringsEqual(accUrl, "https://greenfieldscan.com/account/0xcf0f6b88ed72653b00fdebbffc90b98072cb3285"); } diff --git a/tests/chains/ImmutableX/StarkKeyTests.cpp b/tests/chains/ImmutableX/StarkKeyTests.cpp index 72cc24fe1ef..f1ef1fce5b4 100644 --- a/tests/chains/ImmutableX/StarkKeyTests.cpp +++ b/tests/chains/ImmutableX/StarkKeyTests.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Ethereum/EIP2645.h" -#include "Ethereum/Signer.h" #include "HexCoding.h" #include "ImmutableX/Constants.h" #include "ImmutableX/StarkKey.h" @@ -26,8 +25,8 @@ TEST(ImmutableX, ExtraGrinding) { auto data = parse_hex(signature); auto path = DerivationPath(Ethereum::accountPathFromAddress(address, gLayer, gApplication, gIndex)); auto privKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), path); - auto pubKey = hexEncoded(getPublicKeyFromPrivateKey(privKey.bytes)); - ASSERT_EQ(pubKey, "0x035919acd61e97b3ecdc75ff8beed8d1803f7ea3cad2937926ae59cc3f8070d4"); + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); + ASSERT_EQ(hexEncoded(pubKey.bytes), "0x035919acd61e97b3ecdc75ff8beed8d1803f7ea3cad2937926ae59cc3f8070d4"); } TEST(ImmutableX, GrindKey) { @@ -39,8 +38,8 @@ TEST(ImmutableX, GrindKey) { TEST(ImmutableX, GetPrivateKeySignature) { std::string signature = "0x21fbf0696d5e0aa2ef41a2b4ffb623bcaf070461d61cf7251c74161f82fec3a4370854bc0a34b3ab487c1bc021cd318c734c51ae29374f2beb0e6f2dd49b4bf41c"; auto data = parse_hex(signature); - auto ethSignature = Ethereum::Signer::signatureDataToStructSimple(data); - auto seed = store(ethSignature.r); + // The signature is `rsv`, where `r` starts at 0 and is 32 long. + auto seed = subData(data, 0, 32); auto result = grindKey(seed); ASSERT_EQ(result, "766f11e90cd7c7b43085b56da35c781f8c067ac0d578eabdceebc4886435bda"); } @@ -56,32 +55,23 @@ TEST(ImmutableX, GetPrivateKeyFromSignature) { } TEST(ImmutableX, GetPublicKeyFromPrivateKey) { - auto privKey = parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe", true); - auto pubKey = hexEncoded(getPublicKeyFromPrivateKey(privKey)); - ASSERT_EQ(pubKey, "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); - - { - auto priv = PrivateKey(parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe")); - auto pub = priv.getPublicKey(TWPublicKeyTypeStarkex); - ASSERT_EQ(hexEncoded(pub.bytes), "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); - } + auto privKeyData = parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe", true); + PrivateKey privKey(privKeyData); + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); + auto pubKeyHex = hexEncoded(pubKey.bytes); + ASSERT_EQ(pubKeyHex, "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); } TEST(ImmutableX, SimpleSign) { - auto privKey = parse_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + auto privKeyBytes = parse_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + PrivateKey privKey(privKeyBytes); auto digest = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - auto signature = hex(ImmutableX::sign(privKey, digest)); + auto signature = hex(privKey.sign(digest, TWCurve::TWCurveStarkex)); auto expectedSignature = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; ASSERT_EQ(signature.size(), 128ULL); ASSERT_EQ(signature.substr(0, 64), "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); ASSERT_EQ(signature.substr(64, 64), "04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); ASSERT_EQ(signature, expectedSignature); - - { - PrivateKey priv(privKey); - auto result = hex(priv.sign(digest, TWCurveStarkex)); - ASSERT_EQ(result, expectedSignature); - } } TEST(ImmutableX, VerifySign) { @@ -90,7 +80,6 @@ TEST(ImmutableX, VerifySign) { auto pubKeyData = parse_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); auto hash = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); auto signature = parse_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); - ASSERT_TRUE(verify(pubKeyData, signature, hash)); auto pubKey = PublicKey(pubKeyData, TWPublicKeyTypeStarkex); ASSERT_TRUE(pubKey.verify(signature, hash)); } @@ -99,7 +88,6 @@ TEST(ImmutableX, VerifySign) { auto pubKeyData = parse_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); auto hash = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); auto signature = parse_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b"); - ASSERT_FALSE(verify(pubKeyData, signature, hash)); auto pubKey = PublicKey(pubKeyData, TWPublicKeyTypeStarkex); ASSERT_FALSE(pubKey.verify(signature, hash)); } diff --git a/tests/chains/InternetComputer/TWAnyAddressTests.cpp b/tests/chains/InternetComputer/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..013ff0f158e --- /dev/null +++ b/tests/chains/InternetComputer/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWInternetComputer, Address) { + auto string = STRING("58b26ace22a36a0011608a130e84c7cf34ba469c38d24ccf606152ce7de91f4e"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeInternetComputer)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); +} diff --git a/tests/chains/InternetComputer/TWAnySignerTests.cpp b/tests/chains/InternetComputer/TWAnySignerTests.cpp new file mode 100644 index 00000000000..71e4ba77e62 --- /dev/null +++ b/tests/chains/InternetComputer/TWAnySignerTests.cpp @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "proto/Common.pb.h" +#include "proto/InternetComputer.pb.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::InternetComputer { + +TEST(TWAnySignerInternetComputer, Sign) { + auto key = parse_hex("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + auto input = Proto::SigningInput(); + input.set_private_key(key.data(), key.size()); + input.mutable_transaction()->mutable_transfer()->set_to_account_identifier("943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a"); + input.mutable_transaction()->mutable_transfer()->set_amount(100'000'000); + input.mutable_transaction()->mutable_transfer()->set_memo(0); + input.mutable_transaction()->mutable_transfer()->set_current_timestamp_nanos(1'691'709'940'000'000'000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeInternetComputer); + + const auto signed_transaction = output.signed_transaction(); + const auto hex_encoded = hex(signed_transaction); + ASSERT_EQ(hex_encoded, "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"); +} + +} // namespace TW::InternetComputer diff --git a/tests/chains/InternetComputer/TWCoinTypeTests.cpp b/tests/chains/InternetComputer/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ea7bbc624d1 --- /dev/null +++ b/tests/chains/InternetComputer/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "TestUtilities.h" +#include +#include + +TEST(TWInternetComputerCoinType, TWCoinType) { + const auto coin = TWCoinTypeInternetComputer; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "internet_computer"); + assertStringsEqual(name, "Internet Computer"); + assertStringsEqual(symbol, "ICP"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainInternetComputer); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://dashboard.internetcomputer.org/transaction/9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85"); + assertStringsEqual(accUrl, "https://dashboard.internetcomputer.org/account/529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b"); +} diff --git a/tests/chains/Mantle/TWCoinTypeTests.cpp b/tests/chains/Mantle/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..85483037897 --- /dev/null +++ b/tests/chains/Mantle/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "TestUtilities.h" +#include +#include + + +TEST(TWMantleCoinType, TWCoinType) { + const auto coin = TWCoinTypeMantle; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xfae996ea23f1ff9909ac04d26ae6e52ab600a84163fab9e0e893483c685629dd")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xA295EEFd401C8BE1457F266d3e73cdD015e5CFbb")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "mantle"); + assertStringsEqual(name, "Mantle"); + assertStringsEqual(symbol, "MNT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "5000"); + assertStringsEqual(txUrl, "https://explorer.mantle.xyz/tx/0xfae996ea23f1ff9909ac04d26ae6e52ab600a84163fab9e0e893483c685629dd"); + assertStringsEqual(accUrl, "https://explorer.mantle.xyz/address/0xA295EEFd401C8BE1457F266d3e73cdD015e5CFbb"); +} diff --git a/tests/chains/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp index b5b54b97f44..643ea53a5b9 100644 --- a/tests/chains/NEO/SignerTests.cpp +++ b/tests/chains/NEO/SignerTests.cpp @@ -4,8 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "PublicKey.h" #include "HexCoding.h" +#include "PublicKey.h" +#include "PublicKeyLegacy.h" #include "NEO/Address.h" #include "NEO/Signer.h" @@ -51,7 +52,8 @@ TEST(NEOAccount, validity) { } TEST(NEOSigner, SigningTransaction) { - auto signer = Signer(PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"))); + auto privateKey = PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + auto signer = Signer(privateKey); auto transaction = Transaction(); transaction.type = TransactionType::TT_ContractTransaction; transaction.version = 0x00; @@ -78,9 +80,12 @@ TEST(NEOSigner, SigningTransaction) { out.scriptHash = load(scriptHash); transaction.outputs.push_back(out); } + signer.sign(transaction); auto signedTx = transaction.serialize(); EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c37a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } } // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TWAnySignerTests.cpp b/tests/chains/NEO/TWAnySignerTests.cpp index 039940c5e49..3f6770b6fb2 100644 --- a/tests/chains/NEO/TWAnySignerTests.cpp +++ b/tests/chains/NEO/TWAnySignerTests.cpp @@ -4,6 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "PrivateKey.h" +#include "PublicKeyLegacy.h" #include "TestUtilities.h" #include #include "HexCoding.h" @@ -13,12 +15,14 @@ namespace TW::NEO::tests { +const std::string SECRET = "F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"; + Proto::SigningInput createInput() { const std::string NEO_ASSET_ID = "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; const std::string GAS_ASSET_ID = "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; Proto::SigningInput input; - auto privateKey = parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"); + auto privateKey = parse_hex(SECRET); input.set_private_key(privateKey.data(), privateKey.size()); input.set_fee(12345); // too low input.set_gas_asset_id(GAS_ASSET_ID); @@ -165,6 +169,8 @@ TEST(TWAnySignerNEO, Sign) { // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } TEST(TWAnySignerNEO, Plan) { @@ -184,6 +190,8 @@ TEST(TWAnySignerNEO, SignMultiOutput) { ANY_SIGN(input, TWCoinTypeNEO); ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e26eb0cf111674ff0802a42357671867b11c334807c40146419825eed8c45a6eed232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e2914f30ede98b00f8fd5bdca898e7984ea0b3b2a5e31658435b93dbea3808b664232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } TEST(TWAnySignerNEO, PlanMultiOutput) { diff --git a/tests/chains/NEO/TransactionCompilerTests.cpp b/tests/chains/NEO/TransactionCompilerTests.cpp index de7339cf848..8ca243df57e 100644 --- a/tests/chains/NEO/TransactionCompilerTests.cpp +++ b/tests/chains/NEO/TransactionCompilerTests.cpp @@ -75,6 +75,10 @@ TEST(NEOCompiler, CompileWithSignatures) { const auto signature = parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c18585762" "28f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // const auto signature = + // parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89e" + // "d70e01073f6ba574e65071c87cc8cce59833d4d30479c37a"); // Verify signature (pubkey & hash & signature) EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); @@ -87,6 +91,14 @@ TEST(NEOCompiler, CompileWithSignatures) { "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" "057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961" "d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; + // TODO uncomment when nist256p1 Rust implementation is enabled. + // auto expectedTx = + // "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674" + // "beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9" + // "569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc4" + // "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" + // "057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c3" + // "7a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); diff --git a/tests/chains/Ontology/Oep4Tests.cpp b/tests/chains/Ontology/Oep4Tests.cpp index 41984c60e49..a0cb87f8d90 100644 --- a/tests/chains/Ontology/Oep4Tests.cpp +++ b/tests/chains/Ontology/Oep4Tests.cpp @@ -87,11 +87,11 @@ TEST(OntologyOep4, addressHack) { } TEST(OntologyOep4, transfer) { - auto from = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + PrivateKey fromPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer from(fromPrivate); - auto payer = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + PrivateKey payerPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer payer(payerPrivate); auto toAddress = Address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); @@ -105,10 +105,13 @@ TEST(OntologyOep4, transfer) { Oep4 wing(wing_addr); auto tx = wing.transfer(from, toAddress, amount, payer, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); + auto rawTxBytes = tx.serialize(); + auto rawTx = hex(rawTxBytes); // Transaction Hex tab // https://explorer.ont.io/testnet/tx/710266b8d497e794ecd47e01e269e4aeb6f4ff2b01eaeafc4cd371e062b13757 EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTx); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ(rawTx, "00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); } TEST(OntologyOep4, transferMainnet) { diff --git a/tests/chains/Ontology/OngTests.cpp b/tests/chains/Ontology/OngTests.cpp index a140e670d00..fcea1ec34e6 100644 --- a/tests/chains/Ontology/OngTests.cpp +++ b/tests/chains/Ontology/OngTests.cpp @@ -35,15 +35,20 @@ TEST(OntologyOng, balanceOf) { } TEST(OntologyOng, transfer) { - auto signer1 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer signer1(privateKey1); + + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer signer2(privateKey2); + + Address toAddress("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); uint32_t nonce = 0; uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; + auto tx = Ong().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); + auto rawTx = tx.serialize(); + auto rawTxHex = hex(rawTx); + EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" @@ -53,19 +58,35 @@ TEST(OntologyOng, transfer) { "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" "488828f308c263b35287363e51add8cd49136eb57a397c6ade95df80d9a16282232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); + rawTxHex); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140ac3edf2d00540f9c" + // "2f3b24878936b409c995c425ab5edf247c5b0d812a50df29c009c1e7c4538e5a32f88d0087bea3b91082" + // "0487db572e9be6ebddc953200b8d2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" + // "b777d70bf73d9c4dad78c9c1ae52273273d38bf82cde221a1523eb4222c1c2cf232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // rawTxHex); } TEST(OntologyOng, withdraw) { - auto signer1 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer signer1(privateKey1); + + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer signer2(privateKey2); + uint32_t nonce = 0; uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; auto tx = Ong().withdraw(signer1, signer1.getAddress(), amount, signer2, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); + auto rawTx = tx.serialize(); + auto rawTxHex = hex(rawTx); + EXPECT_EQ( "00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" @@ -76,7 +97,20 @@ TEST(OntologyOng, withdraw) { "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); + rawTxHex); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ( + // "00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" + // "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" + // "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" + // "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" + // "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab9b1ff3d62164e0" + // "d86de3429d1943292b6a3b623bae21cc5c6ba6cb90cd8030a22321031bec1250aa8f78275f99a6663688f31085" + // "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" + // "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" + // "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // rawTxHex); } } // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/OntTests.cpp b/tests/chains/Ontology/OntTests.cpp index 310c6f75319..a645eb49708 100644 --- a/tests/chains/Ontology/OntTests.cpp +++ b/tests/chains/Ontology/OntTests.cpp @@ -5,12 +5,9 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" - #include "Ontology/Ont.h" #include - -#include #include namespace TW::Ontology::tests { @@ -37,15 +34,19 @@ TEST(OntologyOnt, queryBalance) { } TEST(OntologyOnt, transfer) { - auto signer1 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer signer1(privateKey1); + + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer signer2(privateKey2); + auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); uint32_t nonce = 0; uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; auto tx = Ont().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); + auto rawTx = tx.serialize(); + auto rawTxHex = hex(rawTx); + EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" @@ -55,7 +56,60 @@ TEST(OntologyOnt, transfer) { "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" "0576d7b092fabafd0913a67ccf8b2f8e3d2bd708f768c2bb67e2d2f759805608232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); + rawTxHex); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65000241407531e7d5bb9ae138" + // "862585a65c26d624f1a7a61011298809d9ed9cf60d10a450bf9821152ab657ca4b7f3b1b76fb1d7021a4" + // "1d4e05d427b941caa2e9ca783afc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" + // "fa89284e6d054503f6ec59833074d0717fbb23a4afaedbc98bd6f7cba2e2cf49232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // rawTxHex); } +// Successfully broadcasted: https://explorer.ont.io/tx/785af64758886099b995e2aed914c56b15ab63c6e0c5acf42b66f3bbc3e95f98 +// TEST(OntologyOnt, transferUpdatedSign) { +// PrivateKey privateKey1(parse_hex("3b2bca95860af1baf5ef55f60167ef59db098b5871617c889f42dee4ffcb0c6f")); +// Signer signer1(privateKey1); +// +// PrivateKey privateKey2(parse_hex("3b2bca95860af1baf5ef55f60167ef59db098b5871617c889f42dee4ffcb0c6f")); +// Signer signer2(privateKey2); +// +// auto toAddress = Address("AUyL4TZ1zFEcSKDJrjFnD7vsq5iFZMZqT7"); +// uint32_t nonce = 2760697417; +// +// uint64_t amount = 1, gasPrice = 2500, gasLimit = 20000; +// auto tx = Ont().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); +// auto rawTx = tx.serialize(); +// auto rawTxHex = hex(rawTx); +// +// // The transaction hex signed by using Rust implementation: +// EXPECT_EQ("00d149e68ca4c409000000000000204e0000000000007ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c2" +// "7100c66b147ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c26a7cc81490c44262e0c9740f7b3c0e3d04" +// "6c106901c72cc46a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" +// "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140362bc7dd7d005b47" +// "464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42bcd043b2349ba7030c2a3d7700d6653cc921" +// "d17a9f8942d46a93e84ae7968ed223210332aa8304797de2817ca65ce25aede0b176c3c5a61a20caffaf" +// "2df7fb6bb0b1b4ac4140362bc7dd7d005b47464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42" +// "bcd043b2349ba7030c2a3d7700d6653cc921d17a9f8942d46a93e84ae7968ed223210332aa8304797de2" +// "817ca65ce25aede0b176c3c5a61a20caffaf2df7fb6bb0b1b4ac", +// rawTxHex); +// +// // The transaction hex signed by using `trezor-crypto`: +// EXPECT_NE("00d149e68ca4c409000000000000204e0000000000007ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c2" +// "7100c66b147ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c26a7cc81490c44262e0c9740f7b3c0e3d04" +// "6c106901c72cc46a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" +// "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140362bc7dd7d005b47" +// "464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42432fbc4ccb6458fdf3d5c288ff299ac2f3c5" +// "2933078e5bb08925e27814cc967f23210332aa8304797de2817ca65ce25aede0b176c3c5a61a20caffaf" +// "2df7fb6bb0b1b4ac4140362bc7dd7d005b47464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42" +// "432fbc4ccb6458fdf3d5c288ff299ac2f3c52933078e5bb08925e27814cc967f23210332aa8304797de2" +// "817ca65ce25aede0b176c3c5a61a20caffaf2df7fb6bb0b1b4ac", +// rawTxHex); +// } + } // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/TWAnySignerTests.cpp b/tests/chains/Ontology/TWAnySignerTests.cpp index 23569706dbd..64558fc5be3 100644 --- a/tests/chains/Ontology/TWAnySignerTests.cpp +++ b/tests/chains/Ontology/TWAnySignerTests.cpp @@ -87,6 +87,18 @@ TEST(TWAnySingerOntology, OntTransfer) { "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex(output.encoded())); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6e" + // "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aac" + // "d6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860" + // "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex(output.encoded())); } TEST(TWAnySingerOntology, OngDecimals) { @@ -157,6 +169,18 @@ TEST(TWAnySingerOntology, OngTransfer) { "3b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex(output.encoded())); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efa" + // "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b007301438" + // "00049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74" + // "c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex(output.encoded())); } TEST(TWAnySingerOntology, OngWithdraw) { @@ -234,7 +258,13 @@ TEST(TWAnySingerOntology, Oep4Transfer) { Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeOntology); + auto rawTx = data(output.encoded()); + auto rawTxHex = hex(rawTx); + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", hex(output.encoded())); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTxHex); } TEST(TWAnySingerOntology, Oep4TokenBalanceOf) { diff --git a/tests/chains/Ronin/AddressTests.cpp b/tests/chains/Ronin/AddressTests.cpp deleted file mode 100644 index bc2c439fb21..00000000000 --- a/tests/chains/Ronin/AddressTests.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ronin/Address.h" - -#include - -namespace TW::Ronin::tests { - -TEST(RoninAddress, Create) { - EXPECT_ANY_THROW(new TW::Ronin::Address("")); -} - -} // namespace TW::Ronin::tests diff --git a/tests/chains/Ronin/TWAnyAddressTests.cpp b/tests/chains/Ronin/TWAnyAddressTests.cpp index f8556a443af..7bb11ed3560 100644 --- a/tests/chains/Ronin/TWAnyAddressTests.cpp +++ b/tests/chains/Ronin/TWAnyAddressTests.cpp @@ -9,7 +9,6 @@ #include #include -#include "Ronin/Address.h" #include "Ronin/Entry.h" #include diff --git a/tests/chains/Scroll/TWAnySignerTests.cpp b/tests/chains/Scroll/TWAnySignerTests.cpp index 8d712b8f39d..8d00ce21d66 100644 --- a/tests/chains/Scroll/TWAnySignerTests.cpp +++ b/tests/chains/Scroll/TWAnySignerTests.cpp @@ -18,7 +18,7 @@ namespace TW::Scroll::tests { /// https://blockscout.scroll.io/tx/0x5a7ba291e0490079bddda54ca5592e5990d6b0eb49f8d239202941e3f63d32bc TEST(TWAnySignerScroll, Sign) { Ethereum::Proto::SigningInput input; - auto chainId = store(uint256_t(534353)); + auto chainId = store(uint256_t(534352)); auto nonce = store(uint256_t(1)); auto gasPrice = store(uint256_t(1000000)); auto gasLimit = store(uint256_t(200000)); @@ -35,7 +35,7 @@ TEST(TWAnySignerScroll, Sign) { auto amount = store(uint256_t(200000000000000)); transfer.set_amount(amount.data(), amount.size()); - std::string expected = "f86c01830f424083030d4094a6bc5ee0b1e904dd0773c5555d2f6833fe937a6886b5e620f480008083104ec6a0a30f1e29c3d1447d7f7f76c7112af3fb86eb3cb7cc536c932e720cd9813ae274a048ffcadba06d3645829d74fa1cf28744111cfc35893761239fa6b510c84c396e"; + std::string expected = "f86c01830f424083030d4094a6bc5ee0b1e904dd0773c5555d2f6833fe937a6886b5e620f480008083104ec3a0c43ee3d34f7758e05e2f54df227eb7780ad97d06e91e03ef6a3c91d4bea6e42fa07d075f20776f7f485faca6f057110fd2745a5cdd6cf121682ef7791619a03ade"; // sign test Ethereum::Proto::SigningOutput output; diff --git a/tests/chains/Scroll/TWCoinTypeTests.cpp b/tests/chains/Scroll/TWCoinTypeTests.cpp index 4486d9d7370..fe07ff53096 100644 --- a/tests/chains/Scroll/TWCoinTypeTests.cpp +++ b/tests/chains/Scroll/TWCoinTypeTests.cpp @@ -19,9 +19,9 @@ TEST(TWScrollCoinType, TWCoinType) { const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); const auto chainId = WRAPS(TWCoinTypeChainId(coin)); - const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xee9196d6840c8d31626324d91c886d20e65711c2026c559133fb23741d3b2f9d")); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa2062a4530b194a438bb9f9e87cdce59f70775a52e8336892364445847c43ca2")); const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); - const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xFE993660cd35d68D94b6Eba29F4D928d979cd65B")); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xf9062b8a30e0d7722960e305049fa50b86ba6253")); const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); assertStringsEqual(id, "scroll"); @@ -31,7 +31,7 @@ TEST(TWScrollCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); - assertStringsEqual(chainId, "534353"); - assertStringsEqual(txUrl, "https://blockscout.scroll.io/tx/0xee9196d6840c8d31626324d91c886d20e65711c2026c559133fb23741d3b2f9d"); - assertStringsEqual(accUrl, "https://blockscout.scroll.io/address/0xFE993660cd35d68D94b6Eba29F4D928d979cd65B"); + assertStringsEqual(chainId, "534352"); + assertStringsEqual(txUrl, "https://scrollscan.com/tx/0xa2062a4530b194a438bb9f9e87cdce59f70775a52e8336892364445847c43ca2"); + assertStringsEqual(accUrl, "https://scrollscan.com/address/0xf9062b8a30e0d7722960e305049fa50b86ba6253"); } diff --git a/tests/chains/Theta/SignerTests.cpp b/tests/chains/Theta/SignerTests.cpp index ebb037b5111..6fd92992a84 100644 --- a/tests/chains/Theta/SignerTests.cpp +++ b/tests/chains/Theta/SignerTests.cpp @@ -26,7 +26,7 @@ TEST(Signer, Sign) { ASSERT_EQ(hex(signature), "5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8" "fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501"); - ASSERT_EQ(hex(transaction.encode()), + ASSERT_EQ(hex(transaction.encodePayload()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" diff --git a/tests/chains/Theta/TransactionTests.cpp b/tests/chains/Theta/TransactionTests.cpp index aae8c5be97b..a5dc2cd456c 100644 --- a/tests/chains/Theta/TransactionTests.cpp +++ b/tests/chains/Theta/TransactionTests.cpp @@ -12,23 +12,23 @@ namespace TW::Theta::tests { -TEST(ThetaTransaction, Encode) { +TEST(ThetaTransaction, EncodePayload) { const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); auto transaction = Transaction(from, to, 10, 20, 1); - ASSERT_EQ(hex(transaction.encode()), + ASSERT_EQ(hex(transaction.encodePayload()), "02f843c78085e8d4a51000e0df942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a51014" "0180d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); } -TEST(ThetaTransaction, EncodeWithSignature) { +TEST(ThetaTransaction, EncodePayloadWithSignature) { const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); auto transaction = Transaction(from, to, 10, 20, 1); transaction.setSignature( from, parse_hex("5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501")); - ASSERT_EQ(hex(transaction.encode()), + ASSERT_EQ(hex(transaction.encodePayload()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" diff --git a/tests/chains/ThetaFuel/TWAnySignerTests.cpp b/tests/chains/ThetaFuel/TWAnySignerTests.cpp index 1e1d79ec3d2..610c24c4eea 100644 --- a/tests/chains/ThetaFuel/TWAnySignerTests.cpp +++ b/tests/chains/ThetaFuel/TWAnySignerTests.cpp @@ -9,7 +9,6 @@ #include "HexCoding.h" #include "uint256.h" #include "proto/Ethereum.pb.h" -#include "Ethereum/ABI/ParamBase.h" #include diff --git a/tests/chains/TomoChain/TWCoinTypeTests.cpp b/tests/chains/Viction/TWCoinTypeTests.cpp similarity index 53% rename from tests/chains/TomoChain/TWCoinTypeTests.cpp rename to tests/chains/Viction/TWCoinTypeTests.cpp index ad7780223cc..f138c29f6c9 100644 --- a/tests/chains/TomoChain/TWCoinTypeTests.cpp +++ b/tests/chains/Viction/TWCoinTypeTests.cpp @@ -13,22 +13,22 @@ #include -TEST(TWTomoChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTomoChain)); +TEST(TWVictionType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeViction)); auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTomoChain, txId.get())); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeViction, txId.get())); auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x86cCbD9bfb371c355202086882bC644A7D0b024B")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTomoChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTomoChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTomoChain)); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeViction, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeViction)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeViction)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTomoChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeTomoChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTomoChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTomoChain)); - assertStringsEqual(symbol, "TOMO"); - assertStringsEqual(txUrl, "https://tomoscan.io/tx/0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b"); - assertStringsEqual(accUrl, "https://tomoscan.io/address/0x86cCbD9bfb371c355202086882bC644A7D0b024B"); - assertStringsEqual(id, "tomochain"); - assertStringsEqual(name, "TomoChain"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeViction), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeViction)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeViction)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeViction)); + assertStringsEqual(symbol, "VIC"); + assertStringsEqual(txUrl, "https://www.vicscan.xyz/tx/0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b"); + assertStringsEqual(accUrl, "https://www.vicscan.xyz/address/0x86cCbD9bfb371c355202086882bC644A7D0b024B"); + assertStringsEqual(id, "viction"); + assertStringsEqual(name, "Viction"); } diff --git a/tests/chains/XRP/TWAnySignerTests.cpp b/tests/chains/XRP/TWAnySignerTests.cpp index 6b55b2976b3..a6753435329 100644 --- a/tests/chains/XRP/TWAnySignerTests.cpp +++ b/tests/chains/XRP/TWAnySignerTests.cpp @@ -121,6 +121,280 @@ TEST(TWAnySignerRipple, SignTokenPayment1) { EXPECT_EQ(hex(output.encoded()), "12000022000000002401ec61e0201b01ec61f561d48a68d1c931200000000000000000000000000055534400000000004b4e9c06f24296074f7bc48f92a97916c6dc5ea968400000000000000a73210348c331ab218ba964150490c83875b06ccad2100b1f5707f296764712738cf1ca74473045022100a938783258d33e2e3e6099d1ab68fd85c3fd21adfa00e136a67bed8fddec6c9a02206cc6784c1f212f19dc939207643d361ceaa8334eb366722cf33b24dc7669dd7a81143a2f2f189d05abb8519cc9dee0e2dbc6fa53924183148132e4e20aecf29090ac428a9c43f230a829220d"); } +TEST(TWAnySignerRipple, SignEscrowCreateMain) { + // https://xrpscan.com/tx/3576E5D413CBDC228D13F281BB66304C1EE9DDEAA5563F1783EDB1848266D739 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + // with finish after and dest tag + input.mutable_op_escrow_create()->set_amount(21300); + input.mutable_op_escrow_create()->set_destination("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.mutable_op_escrow_create()->set_destination_tag(67); + input.mutable_op_escrow_create()->set_cancel_after(755015907); + input.mutable_op_escrow_create()->set_finish_after(755015897); + input.mutable_op_escrow_create()->set_condition(""); + + input.set_fee(12); + input.set_sequence(84363229); + input.set_last_ledger_sequence(84363920); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024050747dd2e00000043201b05074a9020242d00a0e320252d00a0d961400000000000533468400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd640717374473045022100e62d5005401f1d2b1d9eaa42e0fdbb8b8a433d0cfe71455e782882aa6ab0656f02207b589489b4f344e87a956382e5ede6a55fbfc7e38701364c1fe7d056e9a3253a81143194b932f389b95922fba31662f3c8a606fedfd68314a0a67483ad4d51b2524eb304c0fcef6b2025b865"); +} + +TEST(TWAnySignerRipple, SignEscrowCreate) { + // https://testnet.xrpl.org/transactions/3F581927C742D5FAE65FB0759D0F04EF3B64B4A087911B07975816ECCB59915B + auto key = parse_hex("f157cf7951908b9a2b28d6c5817a3212c3971d8c05a1e964bbafaa5ad7529cb0"); + Proto::SigningInput input; + + // with finish after and dest tag + input.mutable_op_escrow_create()->set_amount(345941506); + input.mutable_op_escrow_create()->set_destination("rNS1tYfynXoKC3eX52gvVnSyU9mqWXvCgh"); + input.mutable_op_escrow_create()->set_destination_tag(2467); + input.mutable_op_escrow_create()->set_cancel_after(0); + input.mutable_op_escrow_create()->set_finish_after(750095491); + input.mutable_op_escrow_create()->set_condition(""); + + input.set_fee(12); + input.set_sequence(41874843); + input.set_last_ledger_sequence(41874865); + input.set_account("rL6iE1bbAHekMavpGot6gRxqkQKm6yfoQ6"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027ef59b2e000009a3201b027ef5b120252cb58c836140000000149ea60268400000000000000c7321021846a49ea81238d03dff5a89a9da82eb06b23a276af9a06b45d4aba39713311f744630440220176318f29d2b815f599072230690397f91262c1f801bafada9820d89c719359c0220756eb74d815e20e86f6748c6821d3204f93221a95b4481a572a10530f5776c698114d8242542e6108fccf75a7f5bb0059cfae6d155378314937e838cb1033342c72acfae58fe2e3875ce7693"); +} + +TEST(TWAnySignerRipple, SignEscrowCreate2) { + // https://testnet.xrpl.org/transactions/3F581927C742D5FAE65FB0759D0F04EF3B64B4A087911B07975816ECCB59915B + auto key = parse_hex("8b488ed9b9875174140a97cad53cd8c652789889612f94a9006b7ced18a1c6ef"); + Proto::SigningInput input; + + // with cancel after > 0x7fffffff + input.mutable_op_escrow_create()->set_amount(88941506); + input.mutable_op_escrow_create()->set_destination("rfC73DuBhDqF3Zw1K3uxaQNCkwT8pPKyf5"); + input.mutable_op_escrow_create()->set_destination_tag(0); + input.mutable_op_escrow_create()->set_cancel_after(2147483648); + input.mutable_op_escrow_create()->set_finish_after(750097108); + input.mutable_op_escrow_create()->set_condition(""); + + input.set_fee(12); + input.set_sequence(41875372); + input.set_last_ledger_sequence(41875394); + input.set_account("rEE4PdEYhEikJ1bvQjdE9HdjBV8yp8FsGC"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027ef7ac201b027ef7c220248000000020252cb592d46140000000054d23c268400000000000000c73210211cfeb81bc410e694e98c6a0f17c9c89d85e2b89bc17d2699063c0920217ab0574463044022038d27cd842422d8ee72d5cab11734ce128aef21d7cec17654d21c27d0556d23e0220059f913178a4c65a5d3289896876989e0fcaf3add9769459fb232ab94398368a81149c4970a2b763b9484e3b65d67f3d9b7b1698cb7f83144917342345fbe5cef1e22d3f1353fc468bf696ac"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateWithConditionMain) { + // https://xrpscan.com/tx/77E01FD30A788BFC96F28960F099D4076255252F33FCD31EEBBCBB61E3318544 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + // with cancel after and crypto condition + input.mutable_op_escrow_create()->set_amount(37000); + input.mutable_op_escrow_create()->set_destination("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.mutable_op_escrow_create()->set_destination_tag(0); + input.mutable_op_escrow_create()->set_cancel_after(755014300); + input.mutable_op_escrow_create()->set_finish_after(0); + input.mutable_op_escrow_create()->set_condition("a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b810120"); // PREIMAGE-SHA-256 crypto-condition of secret 5d729ac237c4c7976403817b6409be7190efbfad49af2cf974b9582a854e8794 + + input.set_fee(12); + input.set_sequence(84363226); + input.set_last_ledger_sequence(84363509); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024050747da201b050748f520242d009a9c61400000000000908868400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd6407173744630440220307f4c91e91166db1428eb1ab8f65a84bd9b89542ed844045ffd040f5e13d12b022061120350b9685381e9941c7ec54ce154ca0ef0d01f630aeb3e78dd9fd087ff80701127a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b81012081143194b932f389b95922fba31662f3c8a606fedfd68314a0a67483ad4d51b2524eb304c0fcef6b2025b865"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateWithCondition) { + // https://testnet.xrpl.org/transactions/A8EE35E26CD09E3D6A415DDEFEA6723CA5AFEB1838C5FE06835937FA49DEF3A0 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + // with cancel after and crypto condition + input.mutable_op_escrow_create()->set_amount(30941506); + input.mutable_op_escrow_create()->set_destination("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.mutable_op_escrow_create()->set_destination_tag(0); + input.mutable_op_escrow_create()->set_cancel_after(750090371); + input.mutable_op_escrow_create()->set_finish_after(0); + input.mutable_op_escrow_create()->set_condition("a0258020b3dda5c580919ce0fd6acdf013c337461951946e54b41446467961568cdd9e7b810120"); // PREIMAGE-SHA-256 crypto-condition of secret b3dda5c580919ce0fd6acdf013c337461951946e54b41446467961568cdd9e7b + + input.set_fee(12); + input.set_sequence(41872968); + input.set_last_ledger_sequence(41873012); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027eee48201b027eee7420242cb57883614000000001d8214268400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd640717374473045022100931b3a6634471fa22f709417d7280b76564a8f3a700cf51a50a2c1b1e0162d570220217c0f2e3922e9bc5b2175712c0e244f2f05bf42ccd1e632b06476f66704203f701127a0258020b3dda5c580919ce0fd6acdf013c337461951946e54b41446467961568cdd9e7b81012081143194b932f389b95922fba31662f3c8a606fedfd68314a0a67483ad4d51b2524eb304c0fcef6b2025b865"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateWithCondition2) { + // https://testnet.xrpl.org/transactions/25AE9F7CBC9944B140A4BE338A47DD8C2C29313B44694533D9D47CD758A60A8F + auto key = parse_hex("be60f33cbeb2b5ee688dcb1e93986f2522d8ad76b3c48398bf2be02a6699e781"); + Proto::SigningInput input; + + // with cancel after, crypto condition and dest tag + input.mutable_op_escrow_create()->set_amount(28941506); + input.mutable_op_escrow_create()->set_destination("r9YD31TAtbS8EPwEt2gzGDjsaMDyV1s5QE"); + input.mutable_op_escrow_create()->set_destination_tag(2467); + input.mutable_op_escrow_create()->set_cancel_after(750094604); + input.mutable_op_escrow_create()->set_finish_after(0); + input.mutable_op_escrow_create()->set_condition("a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b250810120"); // PREIMAGE-SHA-256 crypto-condition of secret ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b250 + + input.set_fee(12); + input.set_sequence(41874370); + input.set_last_ledger_sequence(41874392); + input.set_account("rpLGh11T9B6b4UjAU1WRCJowLw8uk7vS44"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027ef3c22e000009a3201b027ef3d820242cb5890c614000000001b99cc268400000000000000c7321035e6cd73289f9b1a796fba572f7a2732aae23b2a9ea6b0ec239d5b9feb388774074473045022100c4bb3b65acd5d30aa8f85ea2a0d2c0e18d2025a005a827722059a9a636eb1bca02207d73b4a64d679e605a6cb31881d7ea3642c1e54e3bf38d13d0dd4219c27d1420701127a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b25081012081140e9c9b31b826671aaa387555cdeccab82a78402083145da8080d21fecf98f24ea2223482e5d24f107799"); +} + +TEST(TWAnySignerRipple, SignEscrowCancelMain) { + // https://xrpscan.com/tx/949B3C3D8B4528C95D07654BBA10B08ABA65FFD339E31706BC93CB0824427F97 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + input.mutable_op_escrow_cancel()->set_owner("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.mutable_op_escrow_cancel()->set_offer_sequence(84363227); + + input.set_fee(12); + input.set_sequence(84363228); + input.set_last_ledger_sequence(84363740); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120004220000000024050747dc2019050747db201b050749dc68400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd64071737446304402202c0416934dbf3a0c42d0b0da9e893cec69e42c81f41424f4a388c3ba8862e65a02201781e22ef85b251902e918f6d923769993757a79b865a62ecdebc1a015368f1f81143194b932f389b95922fba31662f3c8a606fedfd682143194b932f389b95922fba31662f3c8a606fedfd6"); +} + +TEST(TWAnySignerRipple, SignEscrowCancel) { + // https://testnet.xrpl.org/transactions/5B0F8766FFBDE7D3A9ACAA63361BF00FE0739DC8718507776EB2C1AD980BC965 + auto key = parse_hex("bf9810cc4f7cc5e6dea8a0c29f3389d9d511e795d467b402a870e71d93243705"); + Proto::SigningInput input; + + input.mutable_op_escrow_cancel()->set_owner("rE16pf2ZQUZBDLKAyTFF9Q1b3YY1nc7v2J"); + input.mutable_op_escrow_cancel()->set_offer_sequence(41875229); + + input.set_fee(12); + input.set_sequence(41875230); + input.set_last_ledger_sequence(41875263); + input.set_account("rE16pf2ZQUZBDLKAyTFF9Q1b3YY1nc7v2J"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120004220000000024027ef71e2019027ef71d201b027ef73f68400000000000000c73210277314966f72e9520199faa3941bd45b89e444f7eabf203e805527f880de80b8674473045022100ec04d05db5725ce154a511f93056fde0b825b7e0bb4a59b4d4264a008eafdcfe0220676f30f916c6ea0644c11c0bcafcfa8209a083041742d672a726a5c8d99230ea8114a327f724d30f2732f78a4ec6744db298e827ba2b8214a327f724d30f2732f78a4ec6744db298e827ba2b"); +} + +TEST(TWAnySignerRipple, SignEscrowFinishMain) { + // https://xrpscan.com/tx/F015FB9E893877289E3058F14DD2FAA93D7F1E44AC2C7F71E684BD65B94EEB59 + auto key = parse_hex("f60edca8e4bb25f9916017c9c7fe93e633b800550f86cf305a2e99271d7cede5"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.mutable_op_escrow_finish()->set_offer_sequence(84363235); + + input.set_fee(12); + input.set_sequence(84363475); + input.set_last_ledger_sequence(84364395); + input.set_account("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024050748d32019050747e3201b05074c6b68400000000000000c7321025a4c754a3f836ebe18520e7d3861c6e38a4adfe466465d5db6cbb2d745d27ee574473045022100df5a22c475fa039d8fd7f1dd9a3b248e2f11232bf23ae0206b79b6ac3014a80e02202df2da6d98ed9ced91b9790a3d84f28c2aeb368e3cde84806926086d74d406c18114a0a67483ad4d51b2524eb304c0fcef6b2025b86582143194b932f389b95922fba31662f3c8a606fedfd6"); +} + +TEST(TWAnySignerRipple, SignEscrowFinish) { + // https://testnet.xrpl.org/transactions/690E04E97761E3E5F33A9FF3DA42C16E8E234043850DA294BB3FE38CAE551E71 + auto key = parse_hex("a6e306206d400dcc4a2d00e70b4a3925d511b2dabc1a85f4ffbf174a334e28e6"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rL6iE1bbAHekMavpGot6gRxqkQKm6yfoQ6"); + input.mutable_op_escrow_finish()->set_offer_sequence(41874843); + + input.set_fee(12); + input.set_sequence(41874845); + input.set_last_ledger_sequence(41874877); + input.set_account("rNS1tYfynXoKC3eX52gvVnSyU9mqWXvCgh"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024027ef59d2019027ef59b201b027ef5bd68400000000000000c7321026f8adad2b4071daa02916f8759ff148fad37c1562e48e71bb608d896d1c833cb74473045022100a7f06325574a9c4300725cb069029645b94d67217e5ae15a2e20bc0387e32aaf02206f9f1a7ae4aaccf2f4c2ab8d90f868e786a285aae677e0b01507eca5dd6823818114937e838cb1033342c72acfae58fe2e3875ce76938214d8242542e6108fccf75a7f5bb0059cfae6d15537"); +} + +TEST(TWAnySignerRipple, SignEscrowFinishWithConditionMain) { + // https://xrpscan.com/tx/7E9AC2C8286E3EC0410784920A0F8048C79257EDF19B392F98A31F62E3CF4FAD + auto key = parse_hex("f60edca8e4bb25f9916017c9c7fe93e633b800550f86cf305a2e99271d7cede5"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.mutable_op_escrow_finish()->set_offer_sequence(84363226); + input.mutable_op_escrow_finish()->set_condition("a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b810120"); + input.mutable_op_escrow_finish()->set_fulfillment("a02280205d729ac237c4c7976403817b6409be7190efbfad49af2cf974b9582a854e8794"); + + input.set_fee(423); + input.set_sequence(84363473); + input.set_last_ledger_sequence(84363511); + input.set_account("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024050748d12019050747da201b050748f76840000000000001a77321025a4c754a3f836ebe18520e7d3861c6e38a4adfe466465d5db6cbb2d745d27ee574473045022100f06a6ac18efc1280f9d26cbb47c31f7ecd72ed200f9d05c6c762ffcf18b53534022049c6bb4ac8e79c478939a55dd1cb571d56ec929e5200332e410fd69c0fe1ef48701024a02280205d729ac237c4c7976403817b6409be7190efbfad49af2cf974b9582a854e8794701127a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b8101208114a0a67483ad4d51b2524eb304c0fcef6b2025b86582143194b932f389b95922fba31662f3c8a606fedfd6"); +} + +TEST(TWAnySignerRipple, SignEscrowFinishWithCondition) { + // https://testnet.xrpl.org/transactions/4A49D4AD05FBDC4A354E31C7453829509F59DD2B51CDE560C4350155F5DBFD86 + auto key = parse_hex("4bae9219cf9c58e5db0c395900085f07fc06709e1b2223ccac40191fbcbdab2a"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rpLGh11T9B6b4UjAU1WRCJowLw8uk7vS44"); + input.mutable_op_escrow_finish()->set_offer_sequence(41874370); + input.mutable_op_escrow_finish()->set_condition("a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b250810120"); + input.mutable_op_escrow_finish()->set_fulfillment("a022802049b9ab20ca85b55d0c12b948ec7c524f843c77be1ef1561a42b7167dce174b7a"); + + input.set_fee(423); + input.set_sequence(41874372); + input.set_last_ledger_sequence(41874394); + input.set_account("r9YD31TAtbS8EPwEt2gzGDjsaMDyV1s5QE"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024027ef3c42019027ef3c2201b027ef3da6840000000000001a773210277c5d02c3c774c96017234a532dae12023ac8fb499c5d90a56488900ecc746d07446304402206698c1d296bf1493c97beb64945558724c6c88474cd3e0b90e9dc9e7313ac1970220175fef60c48646be934be28a964af0cc55843fb6e6ef17c886716a03af849f74701024a022802049b9ab20ca85b55d0c12b948ec7c524f843c77be1ef1561a42b7167dce174b7a701127a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b25081012081145da8080d21fecf98f24ea2223482e5d24f10779982140e9c9b31b826671aaa387555cdeccab82a784020"); +} + TEST(TWAnySignerRipple, SignNfTokenBurn) { // https://devnet.xrpl.org/transactions/37DA90BE3C30016B3A2C3D47D9677278A3F6D4141B318793CE6AA467A6530E2D auto key = parse_hex("7c2ea5c7b1fd7dfc62d879918b7fc779cdff6bf6391d02ec99854297e916318e"); diff --git a/tests/chains/ZenEON/TWCoinTypeTests.cpp b/tests/chains/ZenEON/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a326e6c2772 --- /dev/null +++ b/tests/chains/ZenEON/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "TestUtilities.h" +#include +#include + + +TEST(TWZenEONCoinType, TWCoinType) { + const auto coin = TWCoinTypeZenEON; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb462e3dac8eef21957d3b6cff3c184d083434367a726dd871e98a774f4d037a5")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x09bCfC348101B1179BCF3837aC996cF09357215f")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zeneon"); + assertStringsEqual(name, "Zen EON"); + assertStringsEqual(symbol, "ZEN"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "7332"); + assertStringsEqual(txUrl, "https://eon-explorer.horizenlabs.io/tx/0xb462e3dac8eef21957d3b6cff3c184d083434367a726dd871e98a774f4d037a5"); + assertStringsEqual(accUrl, "https://eon-explorer.horizenlabs.io/address/0x09bCfC348101B1179BCF3837aC996cF09357215f"); +} diff --git a/tests/common/BCSTests.cpp b/tests/common/BCSTests.cpp deleted file mode 100644 index f12f8242612..00000000000 --- a/tests/common/BCSTests.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Created by Clément Doumergue -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "BCS.h" -#include "HexCoding.h" - -#include - -namespace TW::BCS::tests { - -TEST(BCS, Integral) { - Serializer os; - os << uint32_t(0xAABBCCDD); - ASSERT_EQ(os.bytes, parse_hex("0xDDCCBBAA")); - - os.clear(); - os << int32_t(-305419896); - ASSERT_EQ(os.bytes, parse_hex("0x88A9CBED")); -} - -TEST(BCS, ULEB128) { - Serializer os; - os << uleb128{0x00000001}; - ASSERT_EQ(os.bytes, parse_hex("0x01")); - - os.clear(); - os << uleb128{0x00000080}; - ASSERT_EQ(os.bytes, parse_hex("0x8001")); - - os.clear(); - os << uleb128{0x00004000}; - ASSERT_EQ(os.bytes, parse_hex("0x808001")); - - os.clear(); - os << uleb128{0x00200000}; - ASSERT_EQ(os.bytes, parse_hex("0x80808001")); - - os.clear(); - os << uleb128{0x10000000}; - ASSERT_EQ(os.bytes, parse_hex("0x8080808001")); - - os.clear(); - os << uleb128{0x0000250F}; - ASSERT_EQ(os.bytes, parse_hex("0x8F4A")); -} - -TEST(BCS, String) { - Serializer os; - os << std::string_view("abcd"); - ASSERT_EQ(os.bytes, parse_hex("0x0461626364")); - - os.clear(); - os << std::string_view(""); - ASSERT_EQ(os.bytes, parse_hex("0x00")); -} - -TEST(BCS, Optional) { - Serializer os; - os << std::optional{0xBBCCDD}; - ASSERT_EQ(os.bytes, parse_hex("0x01DDCCBB00")); - - os.clear(); - os << std::optional{}; - ASSERT_EQ(os.bytes, parse_hex("0x00")); - - os.clear(); - os << std::nullopt; - ASSERT_EQ(os.bytes, parse_hex("0x00")); -} - -TEST(BCS, Tuple) { - Serializer os; - os << std::tuple{uint16_t(1), 'a'}; - ASSERT_EQ(os.bytes, parse_hex("0x010061")); - - os.clear(); - os << std::tuple{std::optional{123}, std::string_view("abcd"), uint8_t(0x0E)}; - ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); - - os.clear(); - os << std::tuple{}; - ASSERT_EQ(os.bytes, (Data{})); -} - -TEST(BCS, Pair) { - Serializer os; - os << std::pair{uint16_t(1), 'a'}; - ASSERT_EQ(os.bytes, parse_hex("0x010061")); - - os.clear(); - os << std::pair{std::optional{123}, std::string_view("abcd")}; - ASSERT_EQ(os.bytes, parse_hex("0x017b0000000461626364")); -} - -struct my_struct { - std::optional first; - std::string_view second; - uint8_t third; -}; - -TEST(BCS, Struct) { - Serializer os; - os << my_struct{{123}, "abcd", 0x0E}; - ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); -} - -TEST(BCS, Variant) { - using V = std::variant; - - Serializer os; - os << V{uint32_t(1)}; - ASSERT_EQ(os.bytes, parse_hex("0x0001000000")); - - os.clear(); - os << V{char('a')}; - ASSERT_EQ(os.bytes, parse_hex("0x0161")); - - os.clear(); - os << V{true}; - ASSERT_EQ(os.bytes, parse_hex("0x0201")); -} - -TEST(BCS, Map) { - Serializer os; - os << std::map{{'a', 0}, {'b', 1}, {'c', 2}}; - ASSERT_EQ(os.bytes, parse_hex("0x03610062016302")); -} - -class my_number { -private: - int value; - -public: - explicit my_number(int value) noexcept - : value(value) { - } - - [[nodiscard]] auto get_value() const { - return value; - } -}; - -Serializer& operator<<(Serializer& stream, my_number n) noexcept { - return stream << n.get_value(); -} - -static_assert(CustomSerializable, "my_number does not model the CustomSerializable concept"); - -TEST(BCS, Custom) { - Serializer os; - os << my_number{0xBBCCDD}; - ASSERT_EQ(os.bytes, parse_hex("0xDDCCBB00")); -} - -TEST(BCS, Vector) { - Serializer os; - os << std::vector{1}; - ASSERT_EQ(os.bytes, parse_hex("0101")); -} - -} diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 98d21a8a6f8..da1bd05e983 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -71,7 +71,7 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeTheta: case TWCoinTypeThetaFuel: case TWCoinTypeThunderCore: - case TWCoinTypeTomoChain: + case TWCoinTypeViction: case TWCoinTypeVeChain: case TWCoinTypeWanchain: case TWCoinTypeXDai: @@ -82,6 +82,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeBase: case TWCoinTypeLinea: case TWCoinTypeGreenfield: + case TWCoinTypeMantle: + case TWCoinTypeZenEON: + // end_of_evm_address_derivation_tests_marker_do_not_modify EXPECT_EQ(address, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); break; @@ -376,6 +379,13 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeSei: EXPECT_EQ(address, "sei1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z05hw42q"); break; + case TWCoinTypeInternetComputer: + EXPECT_EQ(address, "cb3aa6a0471a417fc33d8e71f1d241750dfa29b4dc8f084265ce1301fb03b65b"); + break; + case TWCoinTypeTia: + EXPECT_EQ(address, "celestia1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0g3wnkv"); + break; + // end_of_coin_address_derivation_tests_marker_do_not_modify // no default branch here, intentionally, to better notice any missing coins } } diff --git a/tests/common/HDWallet/HDWalletTests.cpp b/tests/common/HDWallet/HDWalletTests.cpp index e414a510a92..9f8dcec791e 100644 --- a/tests/common/HDWallet/HDWalletTests.cpp +++ b/tests/common/HDWallet/HDWalletTests.cpp @@ -15,7 +15,6 @@ #include "Ethereum/Address.h" #include "Ethereum/EIP2645.h" #include "Ethereum/MessageSigner.h" -#include "Ethereum/Signer.h" #include "HDWallet.h" #include "Hash.h" #include "Hedera/DER.h" @@ -478,11 +477,8 @@ TEST(HDWallet, HederaKey) { } TEST(HDWallet, FromSeedStark) { - std::string signature = "0x5a263fad6f17f23e7c7ea833d058f3656d3fe464baf13f6f5ccba9a2466ba2ce4c4a250231bcac7beb165aec4c9b049b4ba40ad8dd287dc79b92b1ffcf20cdcf1b"; - auto data = parse_hex(signature); - auto ethSignature = Ethereum::Signer::signatureDataToStructSimple(data); - auto seed = store(ethSignature.s); - ASSERT_EQ(ethSignature.s, uint256_t("34506778598894488719068064129252410649539581100963007245393949841529394744783")); + auto seed = parse_hex("4c4a250231bcac7beb165aec4c9b049b4ba40ad8dd287dc79b92b1ffcf20cdcf"); + ASSERT_EQ(load(seed), uint256_t("34506778598894488719068064129252410649539581100963007245393949841529394744783")); auto derivationPath = DerivationPath("m/2645'/579218131'/211006541'/1534045311'/1431804530'/1"); auto key = HDWallet<32>::bip32DeriveRawSeed(TWCoinTypeEthereum, seed, derivationPath); ASSERT_EQ(hex(key.bytes), "57384e99059bb1c0e51d70f0fca22d18d7191398dd39d6b9b4e0521174b2377a"); diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index ae9c3494df6..38b57cd62d1 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -7,7 +7,9 @@ #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" +#include #include "PublicKey.h" +#include "PublicKeyLegacy.h" #include @@ -221,77 +223,6 @@ TEST(PrivateKey, PrivateKeyExtendedError) { FAIL() << "Should throw Invalid empty key extension"; } -TEST(PrivateKey, getSharedKey) { - Data privKeyData = parse_hex("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); - - const Data pubKeyData = parse_hex("02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"); - EXPECT_TRUE(PublicKey::isValid(pubKeyData, TWPublicKeyTypeSECP256k1)); - PublicKey publicKey(pubKeyData, TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.isCompressed()); - - const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveSECP256k1); - - EXPECT_EQ( - "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a", - hex(derivedKeyData)); -} - -/** - * Valid test vector from Wycherproof project - * Source: https://github.com/google/wycheproof/blob/master/testvectors/ecdh_secp256k1_test.json#L31 - */ -TEST(PrivateKey, getSharedKeyWycherproof) { - // Stripped left-padded zeroes from: `00f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254` - Data privKeyData = parse_hex("f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); - - // Decoded from ASN.1 & uncompressed `3056301006072a8648ce3d020106052b8104000a03420004d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b396812ea1686e7472e9692eaf3e958e50e9500d3b4c77243db1f2acd67ba9cc4` - const Data pubKeyData = parse_hex("02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b"); - EXPECT_TRUE(PublicKey::isValid(pubKeyData, TWPublicKeyTypeSECP256k1)); - PublicKey publicKey(pubKeyData, TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.isCompressed()); - - const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveSECP256k1); - - // SHA-256 of encoded x-coordinate `02544dfae22af6af939042b1d85b71a1e49e9a5614123c4d6ad0c8af65baf87d65` - EXPECT_EQ( - "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a", - hex(derivedKeyData)); -} - -TEST(PrivateKey, getSharedKeyBidirectional) { - Data privKeyData1 = parse_hex("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData1, TWCurveSECP256k1)); - auto privateKey1 = PrivateKey(privKeyData1); - auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - - Data privKeyData2 = parse_hex("ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData2, TWCurveSECP256k1)); - auto privateKey2 = PrivateKey(privKeyData2); - auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1); - - const Data derivedKeyData1 = privateKey1.getSharedKey(publicKey2, TWCurveSECP256k1); - const Data derivedKeyData2 = privateKey2.getSharedKey(publicKey1, TWCurveSECP256k1); - - EXPECT_EQ(hex(derivedKeyData1), hex(derivedKeyData2)); -} - -TEST(PrivateKey, getSharedKeyError) { - Data privKeyData = parse_hex("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); - auto privateKey = PrivateKey(privKeyData); - - const Data pubKeyData = parse_hex("02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"); - PublicKey publicKey(pubKeyData, TWPublicKeyTypeSECP256k1); - - const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveCurve25519); - const Data expected = {}; - - EXPECT_EQ(expected, derivedKeyData); -} - TEST(PrivateKey, SignSECP256k1) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(privKeyData); @@ -338,6 +269,25 @@ TEST(PrivateKey, SignNIST256p1) { hex(actual)); } +TEST(PrivateKey, SignNIST256p1VerifyLegacy) { + for (auto i = 0; i < 1000; ++i) { + Data secret(32); + random_buffer(secret.data(), 32); + + Data msg(32); + random_buffer(msg.data(), 32); + + PrivateKey privateKey(secret); + auto signature = privateKey.sign(msg, TWCurveNIST256p1); + + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + EXPECT_TRUE(TrezorCrypto::verifyNist256p1Signature(publicKey.bytes, signature, msg)) + << "Error verifying nist256p1 signature" << std::endl + << "Private key: " << hex(secret) << std::endl + << "Message: " << hex(msg); + } +} + int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] uint8_t sig[64]) { return 1; } diff --git a/tests/common/PublicKeyLegacy.h b/tests/common/PublicKeyLegacy.h new file mode 100644 index 00000000000..41ed229526f --- /dev/null +++ b/tests/common/PublicKeyLegacy.h @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "Data.h" + +namespace TW::TrezorCrypto { + +/// Verifies a signature for the provided message by using `trezor-crypto` library (legacy implementation). +inline bool verifyNist256p1Signature(const Data& publicKey, const Data& signature, const Data& message) { + return ecdsa_verify_digest(&nist256p1, publicKey.data(), signature.data(), message.data()) == 0; +} + +} // namespace TW::TrezorCrypto diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index 4b452e24adc..7267545091d 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -189,6 +189,18 @@ TEST(PublicKeyTests, Verify) { } } +TEST(PublicKeyTests, ED25519_malleability) { + const auto publicKey = PublicKey(parse_hex("a96e02312b03116ff88a9f3e7cea40f424af43a5c6ca6c8ed4f98969faf46ade"), TWPublicKeyTypeED25519); + + const Data messageData = TW::data("Hello, world!"); + + const Data origSign = parse_hex("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07e7ed7a20a4e2fa1009db3d1443e937e6abb16ff3c3eaecb798faed7fbb40b008"); + const Data modifiedSign = parse_hex("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07d4c1707dbe450d69df7735b721e316fbabb16ff3c3eaecb798faed7fbb40b018"); + + EXPECT_TRUE(publicKey.verify(origSign, messageData)); + EXPECT_FALSE(publicKey.verify(modifiedSign, messageData)); +} + TEST(PublicKeyTests, VerifyAsDER) { const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); diff --git a/tests/common/rust/bindgen/WalletCoreRsTests.cpp b/tests/common/rust/bindgen/WalletCoreRsTests.cpp index 402fbfb6050..bd5cd298a41 100644 --- a/tests/common/rust/bindgen/WalletCoreRsTests.cpp +++ b/tests/common/rust/bindgen/WalletCoreRsTests.cpp @@ -13,15 +13,6 @@ #include "proto/Polkadot.pb.h" #include "uint256.h" -TEST(RustBindgen, MoveParseFunctionArgument) { - using namespace TW; - std::string arg = "10000000"; - auto str_result = Rust::parse_function_argument_to_bcs(arg.c_str()); - ASSERT_EQ(str_result.code, Rust::OK_CODE); - ASSERT_EQ(std::string(str_result.result), "8096980000000000"); - Rust::free_string(str_result.result); -} - TEST(RustBindgen, EthSigningMessageProto) { using namespace TW; diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp index fa80b967903..83cecb15c9a 100644 --- a/tests/interface/TWCoinTypeTests.cpp +++ b/tests/interface/TWCoinTypeTests.cpp @@ -57,7 +57,7 @@ TEST(TWCoinType, TWPurpose) { ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTezos)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTheta)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeThunderCore)); - ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTomoChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeViction)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTron)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeVeChain)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWanchain)); @@ -130,7 +130,7 @@ TEST(TWCoinType, TWPublicKeyType) { ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeTezos)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTheta)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeThunderCore)); - ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTomoChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeViction)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTron)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeVeChain)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeWanchain)); diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 279ccd82b15..8c6a38ba969 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -113,7 +113,7 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTezos)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTheta)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeThunderCore)); - ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTomoChain)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeViction)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTron)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeVeChain)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeWanchain)); diff --git a/tests/interface/TWPrivateKeyTests.cpp b/tests/interface/TWPrivateKeyTests.cpp index 35b21d96e25..8255ac2cc29 100644 --- a/tests/interface/TWPrivateKeyTests.cpp +++ b/tests/interface/TWPrivateKeyTests.cpp @@ -102,68 +102,6 @@ TEST(TWPrivateKeyTests, PublicKey) { } } -TEST(TWPrivateKeyTests, GetSharedKey) { - const auto privateKeyHex = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex).get())); - ASSERT_TRUE(privateKey.get() != nullptr); - - const auto publicKeyHex = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"; - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - EXPECT_TRUE(publicKey != nullptr); - - const auto derivedData = WRAPD(TWPrivateKeyGetSharedKey(privateKey.get(), publicKey.get(), TWCurveSECP256k1)); - ASSERT_EQ(TW::hex(*((TW::Data*)derivedData.get())), "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); -} - -/** - * Valid test vector from Wycherproof project - * Source: https://github.com/google/wycheproof/blob/master/testvectors/ecdh_secp256k1_test.json#L31 - */ -TEST(TWPrivateKeyTests, GetSharedKeyWycherproof) { - // Stripped left-padded zeroes from: `00f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254` - const auto privateKeyHex = "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex).get())); - ASSERT_TRUE(privateKey.get() != nullptr); - - // Decoded from ASN.1 & uncompressed `3056301006072a8648ce3d020106052b8104000a03420004d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b396812ea1686e7472e9692eaf3e958e50e9500d3b4c77243db1f2acd67ba9cc4` - const auto publicKeyHex = "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b"; - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - EXPECT_TRUE(publicKey != nullptr); - - // SHA-256 of encoded x-coordinate `02544dfae22af6af939042b1d85b71a1e49e9a5614123c4d6ad0c8af65baf87d65` - const auto derivedData = WRAPD(TWPrivateKeyGetSharedKey(privateKey.get(), publicKey.get(), TWCurveSECP256k1)); - ASSERT_EQ(TW::hex(*((TW::Data*)derivedData.get())), "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a"); -} - -TEST(TWPrivateKeyTests, GetSharedKeyBidirectional) { - const auto privateKeyHex1 = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"; - const auto privateKey1 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex1).get())); - ASSERT_TRUE(privateKey1.get() != nullptr); - auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey1.get(), true)); - - const auto privateKeyHex2 = "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"; - const auto privateKey2 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex2).get())); - ASSERT_TRUE(privateKey2.get() != nullptr); - auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey2.get(), true)); - - const auto derivedData1 = WRAPD(TWPrivateKeyGetSharedKey(privateKey1.get(), publicKey2.get(), TWCurveSECP256k1)); - const auto derivedData2 = WRAPD(TWPrivateKeyGetSharedKey(privateKey2.get(), publicKey1.get(), TWCurveSECP256k1)); - ASSERT_EQ(TW::hex(*((TW::Data*)derivedData1.get())), TW::hex(*((TW::Data*)derivedData2.get()))); -} - -TEST(TWPrivateKeyTests, GetSharedKeyError) { - const auto privateKeyHex = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex).get())); - ASSERT_TRUE(privateKey.get() != nullptr); - - const auto publicKeyHex = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"; - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - EXPECT_TRUE(publicKey != nullptr); - - const auto derivedData = WRAPD(TWPrivateKeyGetSharedKey(privateKey.get(), publicKey.get(), TWCurveED25519)); - EXPECT_TRUE(derivedData == nullptr); -} - TEST(TWPrivateKeyTests, Sign) { const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); diff --git a/tests/interface/TWTransactionCompilerTests.cpp b/tests/interface/TWTransactionCompilerTests.cpp index ceea527b389..a131760e7ce 100644 --- a/tests/interface/TWTransactionCompilerTests.cpp +++ b/tests/interface/TWTransactionCompilerTests.cpp @@ -123,33 +123,22 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { TEST(TWTransactionCompiler, ExternalSignatureSignEthereum) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = WRAPD(TWTransactionCompilerBuildInput( - coin, - STRING("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F").get(), // from - STRING("0x3535353535353535353535353535353535353535").get(), // to - STRING("1000000000000000000").get(), // amount - STRING("ETH").get(), // asset - STRING("").get(), // memo - STRING("").get() // chainId - )); - - // Check, by parsing - EXPECT_EQ((int)TWDataSize(txInputData0.get()), 61); Ethereum::Proto::SigningInput signingInput; - ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData0.get()), (int)TWDataSize(txInputData0.get()))); - EXPECT_EQ(hex(signingInput.chain_id()), "01"); - EXPECT_EQ(signingInput.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(signingInput.transaction().has_transfer()); - EXPECT_EQ(hex(signingInput.transaction().transfer().amount()), "0de0b6b3a7640000"); - // Set a few other values const auto nonce = store(uint256_t(11)); + const auto chainId = store(uint256_t(1)); const auto gasPrice = store(uint256_t(20000000000)); const auto gasLimit = store(uint256_t(21000)); + const auto amount = store(uint256_t(1'000'000'000'000'000'000)); + signingInput.set_nonce(nonce.data(), nonce.size()); + signingInput.set_chain_id(chainId.data(), chainId.size()); signingInput.set_gas_price(gasPrice.data(), gasPrice.size()); signingInput.set_gas_limit(gasLimit.data(), gasLimit.size()); signingInput.set_tx_mode(Ethereum::Proto::Legacy); + signingInput.set_to_address("0x3535353535353535353535353535353535353535"); + + signingInput.mutable_transaction()->mutable_transfer()->set_amount(amount.data(), amount.size()); // Serialize back, this shows how to serialize SigningInput protobuf to byte array const auto txInputDataData = data(signingInput.SerializeAsString()); diff --git a/tools/android-build b/tools/android-build index 36425032e9d..8c4b1344af8 100755 --- a/tools/android-build +++ b/tools/android-build @@ -5,7 +5,11 @@ set -e source $(dirname $0)/library -version=$(wc_read_version) +version=$(wc_read_version $1) + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi pushd android ./gradlew assembleRelease diff --git a/tools/android-release b/tools/android-release index e37796b2f6e..2b4bca2da1d 100755 --- a/tools/android-release +++ b/tools/android-release @@ -6,7 +6,11 @@ set -e source $(dirname $0)/library -version=$(wc_read_version) +version=$(wc_read_version $1) + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi echo "Building $version" diff --git a/tools/android-sdk b/tools/android-sdk new file mode 100644 index 00000000000..2ee8025fdb4 --- /dev/null +++ b/tools/android-sdk @@ -0,0 +1,50 @@ +#!/bin/bash + +export NDK_API_LEVEL=21 + +find_android_ndk() { + if [[ "$ANDROID_NDK_HOME" != "" ]]; then + >&2 echo "Use ANDROID_NDK_HOME" + elif [[ "$ANDROID_HOME" != "" ]]; then + >&2 echo "ANDROID_NDK_HOME is not set. Use ANDROID_HOME value instead" + ANDROID_NDK_HOME="$ANDROID_HOME/ndk" + else + >&2 echo "WARNING: ANDROID_HOME is not set. Use a default path" + ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk" + fi + + TEST_CLANG="aarch64-linux-android$NDK_API_LEVEL-clang" + PATH_TO_CLANG=$(find "$ANDROID_NDK_HOME" -iname $TEST_CLANG -print -quit) + + if [[ "$PATH_TO_CLANG" == "" ]]; then + >&2 echo "ERROR: cannot find NDK tools within '$ANDROID_NDK_HOME'" + exit 22 + fi + + echo $(dirname "$PATH_TO_CLANG") +} + +find_android_cmdline_tools() { + # Default version of cmdline tools is 11. + # https://github.com/android-actions/setup-android/blob/9584f05408b63719e3464df8ac85bdbe58f88a64/src/main.ts#L9 + CMDLINE_TOOLS_VERSION="11.0" + + if [[ "$ANDROID_HOME" == "" ]]; then + >&2 echo "ANDROID_HOME is not set. Use a default path" + ANDROID_HOME="$HOME/Library/Android/sdk" + fi + + # cmdline-tools could have a 'latest' version, but if it was installed 2 years ago it may not be 'latest' as of today + if [ -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then + echo "$ANDROID_HOME/cmdline-tools/latest/bin" + return 0 + fi + + if [ -x "$ANDROID_HOME/cmdline-tools/$CMDLINE_TOOLS_VERSION/bin/sdkmanager" ]; then + echo "$ANDROID_HOME/cmdline-tools/$CMDLINE_TOOLS_VERSION/bin" + return 0 + fi + + >&2 echo "ERROR: cannot find SDK cmdline tools within '$ANDROID_HOME'" + exit 22 +} diff --git a/tools/android-test b/tools/android-test index e2f2288d09f..7185d860711 100755 --- a/tools/android-test +++ b/tools/android-test @@ -4,9 +4,17 @@ set -e +source "$(dirname $0)/android-sdk" + +ANDROID_CMDTOOLS=$(find_android_cmdline_tools) + AVD_NAME="integration-tests" PORT=5556 +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi + # Make sure it builds before starting an emulator pushd android ./gradlew assembleAndroidTest @@ -17,7 +25,7 @@ SERIAL=emulator-${PORT} # We have to echo "no" because it will ask us if we want to use a custom hardware profile, and we don't. echo -e "\nCreating Android emulator...\n" -echo "no" | "$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" create avd \ +echo "no" | "$ANDROID_CMDTOOLS/avdmanager" create avd \ -n "${AVD_NAME}" \ -k "system-images;android-33;google_apis;arm64-v8a" \ -f diff --git a/tools/install-android-dependencies b/tools/install-android-dependencies index b91ad4d6520..667e8c4342d 100755 --- a/tools/install-android-dependencies +++ b/tools/install-android-dependencies @@ -2,6 +2,10 @@ set -e +source "$(dirname $0)/android-sdk" + +ANDROID_CMDTOOLS=$(find_android_cmdline_tools) + # Dokka stuff ROOT="$PWD" DOKKA_CLI_JAR=https://repo1.maven.org/maven2/org/jetbrains/dokka/dokka-cli/1.7.20/dokka-cli-1.7.20.jar @@ -16,10 +20,10 @@ https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-html-jvm/0.8.0/kotl https://repo1.maven.org/maven2/org/freemarker/freemarker/2.3.31/freemarker-2.3.31.jar ) -$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --verbose "cmake;3.18.1" "ndk;23.1.7779620" -$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-26;google_apis;x86" +$ANDROID_CMDTOOLS/sdkmanager --verbose "cmake;3.18.1" "ndk;23.1.7779620" +$ANDROID_CMDTOOLS/sdkmanager "system-images;android-30;google_apis;x86" -echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses +echo -e "y\ny\ny\ny\ny\n" | $ANDROID_CMDTOOLS/sdkmanager --licenses echo "Downloading Dokka..." DOKKA_DIR="$ROOT/build/dokka" diff --git a/tools/install-kotlin-dependencies b/tools/install-kotlin-dependencies index e837297f31e..42c24dca325 100755 --- a/tools/install-kotlin-dependencies +++ b/tools/install-kotlin-dependencies @@ -2,6 +2,9 @@ set -e -"$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --verbose "cmake;3.22.1" "ndk;25.2.9519653" +source "$(dirname $0)/android-sdk" -yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses +ANDROID_CMDTOOLS=$(find_android_cmdline_tools) + +$ANDROID_CMDTOOLS/sdkmanager --verbose "cmake;3.22.1" "ndk;25.2.9519653" +yes | $ANDROID_CMDTOOLS/sdkmanager --licenses diff --git a/tools/install-sys-dependencies-mac b/tools/install-sys-dependencies-mac index 2e7fc69a00e..9ff7f1a41fd 100755 --- a/tools/install-sys-dependencies-mac +++ b/tools/install-sys-dependencies-mac @@ -3,3 +3,12 @@ set -e brew install boost ninja xcodegen xcbeautify + +if command -v rustup &> /dev/null +then + echo "Rustup is already installed." +else + echo "Rustup is not installed. Installing it now." + brew install rustup + rustup-init -y --default-toolchain none --no-update-default-toolchain +fi diff --git a/tools/kotlin-build b/tools/kotlin-build index a64537b101f..4681910d30a 100755 --- a/tools/kotlin-build +++ b/tools/kotlin-build @@ -2,6 +2,10 @@ set -e +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi + pushd kotlin ./gradlew :wallet-core-kotlin:generateProtos ./gradlew :wallet-core-kotlin:assemble diff --git a/tools/kotlin-release b/tools/kotlin-release index ce934855911..c6e5f40afa8 100755 --- a/tools/kotlin-release +++ b/tools/kotlin-release @@ -3,11 +3,12 @@ set -e source $(dirname $0)/library -version=$(wc_read_version) +version=$(wc_read_version $1) echo "Building $version" export ANDROID_HOME="$HOME/Library/Android/sdk" +export BOOST_ROOT=$(brew --prefix boost) pushd kotlin ./gradlew :wallet-core-kotlin:generateProtos diff --git a/tools/kotlin-test b/tools/kotlin-test new file mode 100755 index 00000000000..2c97cef3d00 --- /dev/null +++ b/tools/kotlin-test @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi + +pushd kotlin +./gradlew :wallet-core-kotlin:jvmTest +popd diff --git a/tools/rust-bindgen b/tools/rust-bindgen index 02546de6b1a..0c61c54f467 100755 --- a/tools/rust-bindgen +++ b/tools/rust-bindgen @@ -2,12 +2,13 @@ set -e +source "$(dirname $0)/android-sdk" + TARGET_NAME="libwallet_core_rs.a" TARGET_XCFRAMEWORK_NAME=../swift/WalletCoreRs.xcframework BUILD_FOLDER=../rust/target CRATE="wallet-core-rs" HEADER_NAME="WalletCoreRSBindgen.h" -NDK_API_LEVEL=21 create_xc_framework() { rm -rf $TARGET_XCFRAMEWORK_NAME @@ -16,31 +17,6 @@ create_xc_framework() { cp $BUILD_FOLDER/catalyst/$TARGET_NAME $TARGET_XCFRAMEWORK_NAME/ios-arm64_x86_64-maccatalyst } -find_android_ndk() { - if [[ "$ANDROID_NDK_HOME" != "" ]]; then - echo "Use ANDROID_NDK_HOME" - elif [[ "$ANDROID_HOME" != "" ]]; then - echo "ANDROID_NDK_HOME is not set. Use ANDROID_HOME value instead" - ANDROID_NDK_HOME="$ANDROID_HOME/ndk" - else - echo "WARNING: ANDROID_HOME is not set. Use a default path" - ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk" - fi - - TEST_CLANG="aarch64-linux-android$NDK_API_LEVEL-clang" - PATH_TO_CLANG=$(find "$ANDROID_NDK_HOME" -iname $TEST_CLANG -print -quit) - - if [[ "$PATH_TO_CLANG" == "" ]]; then - echo "ERROR: cannot find NDK tools within '$ANDROID_NDK_HOME'" - return 1 - fi - - NDK_BIN_PATH=$(dirname "$PATH_TO_CLANG") - export NDK_BIN_PATH - - return 0 -} - cd rust NATIVE="false" @@ -53,13 +29,13 @@ if [[ "$1" == "native" ]] || [[ "$1" == "" ]]; then NATIVE="true" fi -# Generate bindings for mobile platforms on MacOS only. -if [[ `uname` == "Darwin" ]]; then - # Whether to generate bindings for Android. +# Whether to generate bindings for Android. if [[ "$1" == "android" ]] || [[ "$1" == "" ]]; then ANDROID="true" fi +# Generate bindings for ios platforms on MacOS only. +if [[ `uname` == "Darwin" ]]; then # Whether to generate bindings for iOS. if [[ "$1" == "ios" ]] || [[ "$1" == "" ]]; then IOS="true" @@ -82,7 +58,7 @@ if [[ "$WASM" == "true" ]]; then fi if [[ "$ANDROID" == "true" ]]; then - find_android_ndk + NDK_BIN_PATH=$(find_android_ndk) export AR="$NDK_BIN_PATH/llvm-ar" export CC_aarch64_linux_android="$NDK_BIN_PATH/aarch64-linux-android$NDK_API_LEVEL-clang" diff --git a/tools/rust-fuzz b/tools/rust-fuzz new file mode 100755 index 00000000000..5aabfcf96cd --- /dev/null +++ b/tools/rust-fuzz @@ -0,0 +1,39 @@ +#!/bin/bash +# +# This script ensures that all `cargo-fuzz` targets are compilable if the `dry` argument is given. Otherwise it does nothing. +# Prerequisite: install `cargo-fuzz` by running `cargo install cargo-fuzz`. + +set -e + +run_fuzz_target_dry() { + crate=$1 + target=$2 + + pushd rust/$crate + + echo + echo "Running '$crate::$target' fuzz test (dry)" + echo + cargo fuzz run $target -- -runs=0 + echo + + popd # rust/$crate +} + +run_fuzz_crate_dry() { + crate=$1 + + pushd rust/$crate + targets=$(cargo fuzz list) + popd # rust/$crate + + while read -r target; do + run_fuzz_target_dry $crate $target + done < <(printf '%s\n' "$targets") +} + +if [[ $1 == "dry" ]]; then + run_fuzz_crate_dry "tw_encoding" + run_fuzz_crate_dry "tw_hash" + run_fuzz_crate_dry "tw_keypair" +fi diff --git a/tools/rust-test b/tools/rust-test new file mode 100755 index 00000000000..1b20938f589 --- /dev/null +++ b/tools/rust-test @@ -0,0 +1,26 @@ +#!/bin/bash + +# To run Rust tests (home architecture): +# - run unit tests without additional flags: +# ./tools/rust-test +# +# To run Rust tests in WASM: +# - install wasm dependencies +# ./tools/install-wasm-dependencies +# - run unit tests with `wasm` flag: +# ./tools/rust-test wasm + +set -e + +pushd rust + +if [[ "$1" == "wasm" ]]; then + source ../emsdk/emsdk_env.sh + export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node + + cargo test --target wasm32-unknown-emscripten --profile wasm-test +else + cargo test --all +fi + +popd # rust diff --git a/trezor-crypto/crypto/README.md b/trezor-crypto/crypto/README.md index a71139335a2..856dbb44ac5 100644 --- a/trezor-crypto/crypto/README.md +++ b/trezor-crypto/crypto/README.md @@ -27,9 +27,9 @@ These include: - unit tests (using Check - check.sf.net; in test_check.c) - tests against OpenSSL (in test_openssl.c) - integrated Wycheproof tests -- public key convertion between Curve25519 to Ed25519 and vice versa +- public key conversion between Curve25519 to Ed25519 and vice versa -Distibuted under MIT License. +Distributed under MIT License. ## Some parts of the library come from external sources: diff --git a/trezor-crypto/crypto/ed25519-donna/README.md b/trezor-crypto/crypto/ed25519-donna/README.md index 73eadf8db7c..71b643485de 100644 --- a/trezor-crypto/crypto/ed25519-donna/README.md +++ b/trezor-crypto/crypto/ed25519-donna/README.md @@ -1,5 +1,5 @@ [ed25519](https://ed25519.cr.yp.to) is an -[Elliptic Curve Digital Signature Algortithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm), +[Elliptic Curve Digital Signature Algorithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm), developed by [Dan Bernstein](https://cr.yp.to/djb.html), [Niels Duif](https://www.nielsduif.nl), [Tanja Lange](https://hyperelliptic.org/tanja), @@ -56,7 +56,7 @@ No configuration is needed **if you are compiling against OpenSSL**. ##### Hash Options -If you are not compiling aginst OpenSSL, you will need a hash function. +If you are not compiling against OpenSSL, you will need a hash function. To use a simple/**slow** implementation of SHA-512, use `-DED25519_REFHASH` when compiling `ed25519.c`. This should never be used except to verify the code works when OpenSSL is not available. @@ -73,7 +73,7 @@ custom hash implementation in ed25519-hash-custom.h. The hash must have a 512bit ##### Random Options -If you are not compiling aginst OpenSSL, you will need a random function for batch verification. +If you are not compiling against OpenSSL, you will need a random function for batch verification. To use a custom random function, use `-DED25519_CUSTOMRANDOM` when compiling `ed25519.c` and put your custom hash implementation in ed25519-randombytes-custom.h. The random function must implement: @@ -170,7 +170,7 @@ signing due to both using the same code for the scalar multiply. #### Testing -Fuzzing against reference implemenations is now available. See [fuzz/README](fuzz/README.md). +Fuzzing against reference implementations is now available. See [fuzz/README](fuzz/README.md). Building `ed25519.c` with `-DED25519_TEST` and linking with `test.c` will run basic sanity tests and benchmark each function. `test-batch.c` has been incorporated in to `test.c`. diff --git a/trezor-crypto/crypto/scrypt.c b/trezor-crypto/crypto/scrypt.c index c1856dcbda5..4e94beab0e8 100644 --- a/trezor-crypto/crypto/scrypt.c +++ b/trezor-crypto/crypto/scrypt.c @@ -42,7 +42,7 @@ #include #include -static void blkcpy(void *, void *, size_t); +static void blkcpy(uint32_t *, const uint32_t *, size_t); static void blkxor(void *, void *, size_t); static void salsa20_8(uint32_t[16]); static void blockmix_salsa8(uint32_t *, uint32_t *, uint32_t *, size_t); @@ -50,15 +50,12 @@ static uint64_t integerify(void *, size_t); static void smix(uint8_t *, size_t, uint64_t, uint32_t *, uint32_t *); static void -blkcpy(void * dest, void * src, size_t len) +blkcpy(uint32_t * dest, const uint32_t * src, size_t len) { - size_t * D = dest; - size_t * S = src; - size_t L = len / sizeof(size_t); - size_t i; + size_t L = len / sizeof(uint32_t); - for (i = 0; i < L; i++) - D[i] = S[i]; + for (size_t i = 0; i < L; i++) + dest[i] = src[i]; } static void diff --git a/wasm/tests/Blockchain/InternetComputer.test.ts b/wasm/tests/Blockchain/InternetComputer.test.ts new file mode 100644 index 00000000000..1adf5a8fa52 --- /dev/null +++ b/wasm/tests/Blockchain/InternetComputer.test.ts @@ -0,0 +1,139 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("InternetComputer", () => { + + it("test address", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + const privateKeyBytes = HexCoding.decode("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7"); + + assert.isTrue(PrivateKey.isValid(privateKeyBytes, Curve.secp256k1)); + + const privateKey = PrivateKey.createWithData(privateKeyBytes); + const publicKey = privateKey.getPublicKeySecp256k1(false); + + assert.equal( + HexCoding.encode(publicKey.data()), + "0x048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8" + ); + + const address = AnyAddress.createWithPublicKey(publicKey, CoinType.internetComputer); + + assert.equal(address.description(), "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211"); + + privateKey.delete(); + publicKey.delete(); + address.delete(); + }); + + it("test sign", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + assert.equal(signedTransaction, "0x81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"); + }); + + it("test sign with invalid private key", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7000000"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + // Invalid private key + assert.equal(output.error, 15); + }); + + it("test sign with invalid to account identifier", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + // Invalid account identifier + assert.equal(output.error, 16); + }); + + it("test sign with invalid amount", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(0), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, 23); + assert.equal(output.errorMessage, 'Invalid input token amount'); + }); +}); \ No newline at end of file