From 50df64e91cc5aa431db24120df2f1550b88baba0 Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Mon, 9 Sep 2019 00:04:59 -0700 Subject: [PATCH] Add Harmony (#626) * [project] Add Harmony blockchain definition * [harmony-one] Add C Interface * [harmony-one] Add swift integration tests * [harmony-one] Add Kotlin code with init stubbed out code * [harmony-one] Use RLP impl from ETH * [harmony-one] Stub out Swift tests * [harmony-one] Wrong package name kotlin * [harmony-one] Add C++ tests * [harmony-one] VC changes to coins.json * [harmony-one] RLP encoding tested & works * [harmony-one] Test signer & hash of transaction * [harmony-one] Loose ends of successful signing * [harmony-one] Have signer test passing in C++ * [harmony-one] Docs update * [harmony-one] Swift test code signs transaction correctly * [harmony-one] Test Kotlin signing * [harmony-one] Remove unnecessary AddressChecksum code * [harmony-one] Use bech32 format for address * [harmony-one] Use bech32 address in android tests * [harmony-one] Use bech32 address in ios tests * [harmony-one] Remove duplicated string literal in Kotlin code * [harmony-one] No tabs in coins.json * [harmony-one] Factor out LOCAL_NET, add RLP encoded test in swift, remove explicit nonmainnet enumeration * [harmony-one] Rename variable in tests, avoid lint issue in swift * Update src/Harmony-One/Address.cpp Co-Authored-By: hewig <360470+hewigovens@users.noreply.github.com> * Update src/Harmony-One/Signer.cpp Co-Authored-By: hewig <360470+hewigovens@users.noreply.github.com> * [harmony-one] Remove Harmony-One name * [harmony-one] Update Data used in Unit tests * [harmony-one] Update Swift test * [harmony-one] Update Kotlin code * [harmony-one] Test output.encoded in android * [harmony-one] Use load wrapper in Signer --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../blockchains/harmony/TestHarmonyAddress.kt | 42 +++++ .../harmony/TestHarmonyTransactionSigner.kt | 41 +++++ coins.json | 18 ++ docs/coins.md | 1 + include/TrustWalletCore/TWBlockchain.h | 1 + include/TrustWalletCore/TWCoinType.h | 11 +- include/TrustWalletCore/TWHarmonyAddress.h | 54 ++++++ include/TrustWalletCore/TWHarmonyChainID.h | 18 ++ include/TrustWalletCore/TWHarmonyProto.h | 12 ++ include/TrustWalletCore/TWHarmonySigner.h | 23 +++ src/Coin.cpp | 47 ++--- src/Harmony/Address.cpp | 69 ++++++++ src/Harmony/Address.h | 55 ++++++ src/Harmony/Signer.cpp | 92 ++++++++++ src/Harmony/Signer.h | 62 +++++++ src/Harmony/Transaction.cpp | 9 + src/Harmony/Transaction.h | 46 +++++ src/interface/TWHarmonyAddress.cpp | 64 +++++++ src/interface/TWHarmonySigner.cpp | 25 +++ src/proto/Harmony.proto | 47 +++++ .../Sources/Addresses/CoinType+Address.swift | 2 + swift/Tests/Blockchains/HarmonyTests.swift | 33 ++++ swift/Tests/CoinAddressDerivationTests.swift | 3 + tests/Harmony/AddressTests.cpp | 42 +++++ tests/Harmony/SignerTests.cpp | 160 ++++++++++++++++++ tests/interface/TWCoinTypeConfigTests.cpp | 19 ++- tests/interface/TWHarmonySignerTests.cpp | 71 ++++++++ 28 files changed, 1042 insertions(+), 26 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt create mode 100644 include/TrustWalletCore/TWHarmonyAddress.h create mode 100644 include/TrustWalletCore/TWHarmonyChainID.h create mode 100644 include/TrustWalletCore/TWHarmonyProto.h create mode 100644 include/TrustWalletCore/TWHarmonySigner.h create mode 100644 src/Harmony/Address.cpp create mode 100644 src/Harmony/Address.h create mode 100644 src/Harmony/Signer.cpp create mode 100644 src/Harmony/Signer.h create mode 100644 src/Harmony/Transaction.cpp create mode 100644 src/Harmony/Transaction.h create mode 100644 src/interface/TWHarmonyAddress.cpp create mode 100644 src/interface/TWHarmonySigner.cpp create mode 100644 src/proto/Harmony.proto create mode 100644 swift/Tests/Blockchains/HarmonyTests.swift create mode 100644 tests/Harmony/AddressTests.cpp create mode 100644 tests/Harmony/SignerTests.cpp create mode 100644 tests/interface/TWHarmonySignerTests.cpp 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 211a1d543c2..c941eba8f46 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 @@ -82,6 +82,7 @@ class CoinAddressDerivationTests { TERRA -> assertEquals("terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug", address) MONACOIN -> assertEquals("M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja", address) FIO -> assertEquals("FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3", address) + HARMONY -> assertEquals("one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09", address) SOLANA -> assertEquals("2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt new file mode 100644 index 00000000000..ab021f4216d --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt @@ -0,0 +1,42 @@ +package com.trustwallet.core.app.blockchains.harmony + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.Base58 +import wallet.core.jni.PrivateKey +import wallet.core.jni.PublicKey +import wallet.core.jni.PublicKeyType +import wallet.core.jni.HarmonyAddress + +class TestHarmonyAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + val targetAddress = "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe" + + @Test + fun testAddressFromPrivateKey() { + val key = PrivateKey(Base58.decodeNoCheck("GGzxJ4QmKCXH2juK89RVAmvFAfdUfUARCvxEsBM356vX")) + val pubkey = key.getPublicKeySecp256k1(false) + val address = HarmonyAddress(pubkey) + assertEquals(address.description(), targetAddress) + } + + @Test + fun testAddressFromPublicKey() { + val pubkey = PublicKey( + Base58.decodeNoCheck("RKjfnr3wMojEruvXZuvNDmL7UfLUiyU3vsBGoZ4k2qY8YzoEJDHLmXDWid9K6YDuGJ2u1fZ8E8JXDjk3KUuDXtwz"), + PublicKeyType.SECP256K1EXTENDED + ) + val address = HarmonyAddress(pubkey) + assertEquals(address.description(), targetAddress) + } + + @Test + fun testAddressFromString() { + val address = HarmonyAddress(targetAddress) + assertEquals(address.description(), targetAddress) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt new file mode 100644 index 00000000000..e40df4c9377 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt @@ -0,0 +1,41 @@ +package com.trustwallet.core.app.blockchains.harmony + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.PrivateKey +import wallet.core.jni.HarmonySigner +import wallet.core.jni.proto.Harmony +import com.trustwallet.core.app.utils.Numeric + +class TestHarmonyTransactionSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testHarmonyTransactionSigning() { + val signingInput = Harmony.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xb578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e".toHexByteArray()).data()) + toAddress = "one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc" + chainId = ByteString.copyFrom("0x02".toHexByteArray()) + nonce = ByteString.copyFrom("0x9".toHexByteArray()) + gasPrice = ByteString.copyFrom("0x0".toHexByteArray()) + gasLimit = ByteString.copyFrom("0x5208".toHexByteArray()) + fromShardId = ByteString.copyFrom("0x1".toHexByteArray()) + toShardId = ByteString.copyFrom("0x0".toHexByteArray()) + amount = ByteString.copyFrom("0x4c53ecdc18a60000".toHexByteArray()) + } + val sign: Harmony.SigningOutput = HarmonySigner.sign(signingInput.build()) + val e1 = "0xf86909808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a600008028a0325aed6caa01a5235b" + val e2 = "7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372da06c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b" + + assertEquals(Numeric.toHexString(sign.encoded.toByteArray()), e1 + e2) + assertEquals(Numeric.toHexString(sign.v.toByteArray()), "0x28") + assertEquals(Numeric.toHexString(sign.r.toByteArray()), "0x325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d") + assertEquals(Numeric.toHexString(sign.s.toByteArray()), "0x6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b") + } +} diff --git a/coins.json b/coins.json index 5692609c467..e0d12d1eee3 100644 --- a/coins.json +++ b/coins.json @@ -1066,6 +1066,24 @@ "clientDocs": "https://fio.foundation" } }, + { + "id": "harmony", + "name": "Harmony", + "symbol": "ONE", + "decimals": 18, + "blockchain": "Harmony", + "hrp": "one", + "derivationPath": "m/44'/1023'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": "https://explorer.harmony.one/#/tx/", + "info": { + "url": "https://harmony.one", + "client": "https://github.com/harmony-one/go-sdk", + "clientPublic": "https://github.com/harmony-one/go-sdk", + "clientDocs": "https://app.gitbook.com/@harmony-one/s/sdk-wiki" + } + }, { "id": "solana", "name": "Solana", diff --git a/docs/coins.md b/docs/coins.md index 46ad31b1f12..371c6439bd6 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -49,6 +49,7 @@ This list is generated from [./coins.json](../coins.json) | 888 | NEO | NEO | | | | 889 | TomoChain | TOMO | | | | 1001 | Thunder Token | TT | | | +| 1023 | Harmony | ONE | | | | 1024 | Ontology | ONT | | | | 1729 | Tezos | XTZ | | | | 2017 | Kin | KIN | | | diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index b14824e1613..a8d6f516724 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -41,6 +41,7 @@ enum TWBlockchain { TWBlockchainNebulas = 27, TWBlockchainFIO = 28, TWBlockchainSolana = 29, + TWBlockchainHarmony = 30, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index d12b8bd7361..a165844492a 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -9,11 +9,11 @@ #include "TWBase.h" #include "TWBlockchain.h" #include "TWCurve.h" +#include "TWHDVersion.h" +#include "TWHRP.h" #include "TWPrivateKey.h" #include "TWPurpose.h" #include "TWString.h" -#include "TWHDVersion.h" -#include "TWHRP.h" TW_EXTERN_C_BEGIN @@ -79,6 +79,7 @@ enum TWCoinType { TWCoinTypeRavencoin = 175, TWCoinTypeWaves = 5741564, TWCoinTypeTerra = 330, + TWCoinTypeHarmony = 1023, }; /// Returns the blockchain for a coin type. @@ -111,11 +112,13 @@ TWString *_Nonnull TWCoinTypeDerivationPath(enum TWCoinType coin); /// Derives the address for a particular coin from the private key. TW_EXPORT_METHOD -TWString *_Nonnull TWCoinTypeDeriveAddress(enum TWCoinType coin, struct TWPrivateKey *_Nonnull privateKey); +TWString *_Nonnull TWCoinTypeDeriveAddress(enum TWCoinType coin, + struct TWPrivateKey *_Nonnull privateKey); /// Derives the address for a particular coin from the public key. TW_EXPORT_METHOD -TWString *_Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, struct TWPublicKey *_Nonnull publicKey); +TWString *_Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, + struct TWPublicKey *_Nonnull publicKey); /// HRP for this coin type TW_EXPORT_PROPERTY diff --git a/include/TrustWalletCore/TWHarmonyAddress.h b/include/TrustWalletCore/TWHarmonyAddress.h new file mode 100644 index 00000000000..950d4dcf2c1 --- /dev/null +++ b/include/TrustWalletCore/TWHarmonyAddress.h @@ -0,0 +1,54 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +struct TWPublicKey; + +/// Represents an Harmony address. +TW_EXPORT_CLASS +struct TWHarmonyAddress; + +/// Compares two addresses for equality. +TW_EXPORT_STATIC_METHOD +bool TWHarmonyAddressEqual(struct TWHarmonyAddress *_Nonnull lhs, + struct TWHarmonyAddress *_Nonnull rhs); + +/// Determines if the string is a valid address. +TW_EXPORT_STATIC_METHOD +bool TWHarmonyAddressIsValidString(TWString *_Nonnull string); + +/// Creates an address from a string representaion. +TW_EXPORT_STATIC_METHOD +struct TWHarmonyAddress *_Nullable TWHarmonyAddressCreateWithString(TWString *_Nonnull string); + +/// Creates an address from a key hash. +TW_EXPORT_STATIC_METHOD +struct TWHarmonyAddress *_Nullable TWHarmonyAddressCreateWithKeyHash(TWData *_Nonnull keyHash); + +/// Creates an address from a public key. +TW_EXPORT_STATIC_METHOD +struct TWHarmonyAddress *_Nonnull TWHarmonyAddressCreateWithPublicKey( + struct TWPublicKey *_Nonnull publicKey); + +TW_EXPORT_METHOD +void TWHarmonyAddressDelete(struct TWHarmonyAddress *_Nonnull address); + +/// Returns the address string representation. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWHarmonyAddressDescription(struct TWHarmonyAddress *_Nonnull address); + +/// Returns the key hash. +TW_EXPORT_PROPERTY +TWData *_Nonnull TWHarmonyAddressKeyHash(struct TWHarmonyAddress *_Nonnull address); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWHarmonyChainID.h b/include/TrustWalletCore/TWHarmonyChainID.h new file mode 100644 index 00000000000..bf223e9a90e --- /dev/null +++ b/include/TrustWalletCore/TWHarmonyChainID.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Chain identifier for Harmony blockchain. +TW_EXPORT_ENUM(uint32_t) +enum TWHarmonyChainID { + TWHarmonyChainIDMainNet = 0x1, +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWHarmonyProto.h b/include/TrustWalletCore/TWHarmonyProto.h new file mode 100644 index 00000000000..f6cd5702ff2 --- /dev/null +++ b/include/TrustWalletCore/TWHarmonyProto.h @@ -0,0 +1,12 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 "TWData.h" + +typedef TWData *_Nonnull TW_Harmony_Proto_SigningInput; +typedef TWData *_Nonnull TW_Harmony_Proto_SigningOutput; diff --git a/include/TrustWalletCore/TWHarmonySigner.h b/include/TrustWalletCore/TWHarmonySigner.h new file mode 100644 index 00000000000..1b752185cb6 --- /dev/null +++ b/include/TrustWalletCore/TWHarmonySigner.h @@ -0,0 +1,23 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWHarmonyProto.h" + +TW_EXTERN_C_BEGIN + +/// Helper class to sign Harmony transactions. +TW_EXPORT_CLASS +struct TWHarmonySigner; + +/// Signs a transaction. +TW_EXPORT_STATIC_METHOD +TW_Harmony_Proto_SigningOutput TWHarmonySignerSign(TW_Harmony_Proto_SigningInput input); + +TW_EXTERN_C_END diff --git a/src/Coin.cpp b/src/Coin.cpp index 444df909fc6..564b6b0867f 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -6,38 +6,39 @@ #include "Coin.h" +#include "ARK/Address.h" #include "Aeternity/Address.h" #include "Aion/Address.h" #include "Bitcoin/Address.h" -#include "Bitcoin/SegwitAddress.h" #include "Bitcoin/CashAddress.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bravo/Address.h" +#include "Cosmos/Address.h" #include "Decred/Address.h" +#include "EOS/Address.h" #include "Ethereum/Address.h" +#include "FIO/Address.h" #include "Groestlcoin/Address.h" +#include "Harmony/Address.h" #include "IOST/Account.h" #include "Icon/Address.h" -#include "Nano/Address.h" +#include "IoTeX/Address.h" #include "NEO/Address.h" +#include "Nano/Address.h" +#include "Nebulas/Address.h" #include "Nimiq/Address.h" #include "Ontology/Address.h" #include "Ripple/Address.h" +#include "Semux/Address.h" +#include "Solana/Address.h" +#include "Steem/Address.h" #include "Stellar/Address.h" -#include "Cosmos/Address.h" #include "Tezos/Address.h" #include "Tron/Address.h" #include "Wanchain/Address.h" +#include "Waves/Address.h" #include "Zcash/TAddress.h" -#include "Bravo/Address.h" -#include "Steem/Address.h" -#include "EOS/Address.h" -#include "IoTeX/Address.h" #include "Zilliqa/Address.h" -#include "Semux/Address.h" -#include "ARK/Address.h" -#include "Waves/Address.h" -#include "Nebulas/Address.h" -#include "FIO/Address.h" -#include "Solana/Address.h" #include @@ -46,7 +47,7 @@ using namespace TW; -bool TW::validateAddress(TWCoinType coin, const std::string& string) { +bool TW::validateAddress(TWCoinType coin, const std::string &string) { auto p2pkh = TW::p2pkhPrefix(coin); auto p2sh = TW::p2shPrefix(coin); auto hrp = stringForHRP(TW::hrp(coin)); @@ -131,7 +132,8 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { return Ripple::Address::isValid(string); case TWCoinTypeSteem: - return Bravo::Address::isValid(string, { TW::Steem::MainnetPrefix, TW::Steem::TestnetPrefix }); + return Bravo::Address::isValid(string, + {TW::Steem::MainnetPrefix, TW::Steem::TestnetPrefix}); case TWCoinTypeStellar: case TWCoinTypeKin: @@ -145,7 +147,8 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { case TWCoinTypeZelcash: case TWCoinTypeZcash: - return Zcash::TAddress::isValid(string, {{Zcash::TAddress::staticPrefix, p2pkh}, {Zcash::TAddress::staticPrefix, p2sh}}); + return Zcash::TAddress::isValid(string, {{Zcash::TAddress::staticPrefix, p2pkh}, + {Zcash::TAddress::staticPrefix, p2sh}}); case TWCoinTypeZilliqa: return Zilliqa::isValidAddress(string); @@ -168,17 +171,20 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { case TWCoinTypeNebulas: return Nebulas::Address::isValid(string); + case TWCoinTypeHarmony: + return Harmony::Address::isValid(string).first; + case TWCoinTypeSolana: return Solana::Address::isValid(string); } } -std::string TW::deriveAddress(TWCoinType coin, const PrivateKey& privateKey) { +std::string TW::deriveAddress(TWCoinType coin, const PrivateKey &privateKey) { auto keyType = TW::publicKeyType(coin); return TW::deriveAddress(coin, privateKey.getPublicKey(keyType)); } -std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) { +std::string TW::deriveAddress(TWCoinType coin, const PublicKey &publicKey) { auto p2pkh = TW::p2pkhPrefix(coin); auto hrp = stringForHRP(TW::hrp(coin)); @@ -297,10 +303,13 @@ std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) { case TWCoinTypeWaves: return Waves::Address(publicKey).string(); - + case TWCoinTypeNebulas: return Nebulas::Address(publicKey).string(); + case TWCoinTypeHarmony: + return Harmony::Address(publicKey).string(); + case TWCoinTypeSolana: return Solana::Address(publicKey).string(); } diff --git a/src/Harmony/Address.cpp b/src/Harmony/Address.cpp new file mode 100644 index 00000000000..6f23c947565 --- /dev/null +++ b/src/Harmony/Address.cpp @@ -0,0 +1,69 @@ + +// Copyright © 2017 Pieter Wuille +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "../Bech32.h" +#include "../Hash.h" +#include "../HexCoding.h" + +#include +#include + +using namespace TW::Harmony; + +std::pair> Address::isValid(const std::string &addr) { + if (addr.size() != 42) { + return {false, {}}; + } + const auto [first, second] = Bech32::decode(addr); + if (first.size() == 0 || second.size() == 0 || first != HRP_HARMONY) { + return {false, {}}; + } + return {true, second}; +} + +bool Address::isValid(const Data &data) { + return data.size() == Address::size; +} + +Address::Address(const std::string &addr) { + const auto [success, payload] = isValid(addr); + if (!success) { + throw std::invalid_argument("address not in Harmony bech32 format"); + } + Data converted; + Bech32::convertBits<5, 8, false>(converted, payload); + std::copy(converted.begin(), converted.end(), bytes.begin()); +} + +Address::Address(const Data &data) { + if (!isValid(data)) { + throw std::invalid_argument("invalid address data"); + } + std::copy(data.begin(), data.end(), bytes.begin()); +} + +Address::Address(const PublicKey &publicKey) { + if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { + throw std::invalid_argument("address may only be an extended SECP256k1 public key"); + } + const auto data = publicKey.hash( + {}, static_cast(Hash::keccak256), true); + std::copy(data.end() - Address::size, data.end(), bytes.begin()); +} + +std::string Address::string() const { + Data converted; + Bech32::convertBits<8, 5, false>(converted, std::vector(bytes.begin(), bytes.end())); + return Bech32::encode(HRP_HARMONY, converted); +} + +std::string Address::hexDump() const { + return hex(bytes); +} diff --git a/src/Harmony/Address.h b/src/Harmony/Address.h new file mode 100644 index 00000000000..1ad12aec577 --- /dev/null +++ b/src/Harmony/Address.h @@ -0,0 +1,55 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust 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 +#include +#include + +namespace TW::Harmony { + +class Address { + public: + /// Number of bytes in an address. + static constexpr size_t size = 20; + + std::array bytes; + + /// Determines whether a collection of bytes makes a valid address. + static bool isValid(const Data &data); + + /// Determines whether a string makes a valid address, nonempty data payload if valid + static std::pair> isValid(const std::string &string); + + /// Initializes an address with a string representation. + explicit Address(const std::string &string); + + /// Initializes an address with a collection of bytes. + explicit Address(const Data &data); + + /// Initializes an address with a public key. + explicit Address(const PublicKey &publicKey); + + /// Returns a bech32 representation of the address. + std::string string() const; + + /// Provide hex representation of address + std::string hexDump() const; +}; + +inline bool operator==(const Address &lhs, const Address &rhs) { + return lhs.bytes == rhs.bytes; +} + +} // namespace TW::Harmony + +/// Wrapper for C interface. +struct TWHarmonyAddress { + TW::Harmony::Address impl; +}; diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp new file mode 100644 index 00000000000..426475d7e40 --- /dev/null +++ b/src/Harmony/Signer.cpp @@ -0,0 +1,92 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "../Ethereum/RLP.h" +#include "../HexCoding.h" + +using namespace TW; +using namespace TW::Harmony; + +std::tuple Signer::values(const uint256_t &chainID, + const Data &signature) noexcept { + auto r = load(Data(signature.begin(), signature.begin() + 32)); + auto s = load(Data(signature.begin() + 32, signature.begin() + 64)); + auto v = load(Data(signature.begin() + 64, signature.begin() + 65)); + v += 35 + chainID + chainID; + return std::make_tuple(r, s, v); +} + +std::tuple +Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept { + auto signature = privateKey.sign(hash, TWCurveSECP256k1); + return values(chainID, signature); +} + +Proto::SigningOutput Signer::sign(const TW::Harmony::Proto::SigningInput &input) noexcept { + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto transaction = Transaction( + /* nonce: */ load(input.nonce()), + /* gasPrice: */ load(input.gas_price()), + /* gasLimit: */ load(input.gas_limit()), + /* fromShardID */ load(input.from_shard_id()), + /* toShardID */ load(input.to_shard_id()), + /* to: */ Address(input.to_address()), + /* amount: */ load(input.amount()), + /* payload: */ Data(input.payload().begin(), input.payload().end())); + + auto signer = Signer(uint256_t(load(input.chain_id()))); + signer.sign(key, transaction); + auto protoOutput = Proto::SigningOutput(); + auto encoded = signer.rlpNoHash(transaction, true); + protoOutput.set_encoded(encoded.data(), encoded.size()); + auto v = store(transaction.v); + protoOutput.set_v(v.data(), v.size()); + auto r = store(transaction.r); + protoOutput.set_r(r.data(), r.size()); + auto s = store(transaction.s); + protoOutput.set_s(s.data(), s.size()); + return protoOutput; +} + +void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept { + auto hash = this->hash(transaction); + auto tuple = Signer::sign(chainID, privateKey, hash); + transaction.r = std::get<0>(tuple); + transaction.s = std::get<1>(tuple); + transaction.v = std::get<2>(tuple); +} + +Data Signer::rlpNoHash(const Transaction &transaction, const bool include_vrs) const noexcept { + auto encoded = Data(); + using namespace TW::Ethereum; + append(encoded, RLP::encode(transaction.nonce)); + append(encoded, RLP::encode(transaction.gasPrice)); + append(encoded, RLP::encode(transaction.gasLimit)); + append(encoded, RLP::encode(transaction.fromShardID)); + append(encoded, RLP::encode(transaction.toShardID)); + append(encoded, RLP::encode(transaction.to.bytes)); + append(encoded, RLP::encode(transaction.amount)); + append(encoded, RLP::encode(transaction.payload)); + if (include_vrs) { + append(encoded, RLP::encode(transaction.v)); + append(encoded, RLP::encode(transaction.r)); + append(encoded, RLP::encode(transaction.s)); + } else { + append(encoded, RLP::encode(chainID)); + append(encoded, RLP::encode(0)); + append(encoded, RLP::encode(0)); + } + return RLP::encodeList(encoded); +} + +std::string Signer::txnAsRLPHex(Transaction &transaction) const noexcept { + return TW::hex(rlpNoHash(transaction, false)); +} + +Data Signer::hash(const Transaction &transaction) const noexcept { + return Hash::keccak256(rlpNoHash(transaction, false)); +} diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h new file mode 100644 index 00000000000..d0d6d72f358 --- /dev/null +++ b/src/Harmony/Signer.h @@ -0,0 +1,62 @@ +// Copyright © 2017-2019 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Transaction.h" +#include "../Data.h" +#include "../Hash.h" +#include "../PrivateKey.h" +#include "../proto/Harmony.pb.h" + +#include +#include +#include +#include + +namespace TW::Harmony { + +/// Helper class that performs Harmony transaction signing. +class Signer { + public: + uint256_t chainID; + + /// Initializes a signer with a chain identifier. + explicit Signer(uint256_t chainID) : chainID(std::move(chainID)) {} + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput &input) noexcept; + + /// Signs the given transaction. + void sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept; + + /// Signs a hash with the given private key for the given chain identifier. + /// + /// @returns the r, s, and v values of the transaction signature + static std::tuple + sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept; + + /// R, S, and V values for the given chain identifier and signature. + /// + /// @returns the r, s, and v values of the transaction signature + static std::tuple values(const uint256_t &chainID, + const Data &signature) noexcept; + + std::string txnAsRLPHex(Transaction &transaction) const noexcept; + + protected: + /// Computes the transaction hash. + Data hash(const Transaction &transaction) const noexcept; + // Plain rlp encoding before hashing + Data rlpNoHash(const Transaction &transaction, const bool) const noexcept; +}; + +} // namespace TW::Harmony + +/// Wrapper for C interface. +struct TWHarmonySigner { + TW::Harmony::Signer impl; +}; diff --git a/src/Harmony/Transaction.cpp b/src/Harmony/Transaction.cpp new file mode 100644 index 00000000000..e58ede33e93 --- /dev/null +++ b/src/Harmony/Transaction.cpp @@ -0,0 +1,9 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Transaction.h" + +using namespace TW::Harmony; diff --git a/src/Harmony/Transaction.h b/src/Harmony/Transaction.h new file mode 100644 index 00000000000..eb3a96b65e7 --- /dev/null +++ b/src/Harmony/Transaction.h @@ -0,0 +1,46 @@ +// Copyright © 2017-2019 Trust. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include +#include +#include + +#include "Address.h" +#include "../uint256.h" + +namespace TW::Harmony { + +class Transaction { + public: + uint256_t nonce; + uint256_t gasPrice; + uint256_t gasLimit; + uint256_t fromShardID; + uint256_t toShardID; + Address to; + uint256_t amount; + std::vector payload; + + // Signature values + uint256_t v = uint256_t(); + uint256_t r = uint256_t(); + uint256_t s = uint256_t(); + + Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t fromShardID, + uint256_t toShardID, Address to, uint256_t amount, Data payload) + : nonce(std::move(nonce)) + , gasPrice(std::move(gasPrice)) + , gasLimit(std::move(gasLimit)) + , fromShardID(std::move(fromShardID)) + , toShardID(std::move(toShardID)) + , to(std::move(to)) + , amount(std::move(amount)) + , payload(std::move(payload)) {} +}; + +} // namespace TW::Harmony diff --git a/src/interface/TWHarmonyAddress.cpp b/src/interface/TWHarmonyAddress.cpp new file mode 100644 index 00000000000..f0b02f978fc --- /dev/null +++ b/src/interface/TWHarmonyAddress.cpp @@ -0,0 +1,64 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include + +#include "../Data.h" +#include "../Harmony/Address.h" + +#include +#include + +#include +#include +#include + +using namespace TW; +using namespace TW::Harmony; + +bool TWHarmonyAddressEqual(struct TWHarmonyAddress *_Nonnull lhs, + struct TWHarmonyAddress *_Nonnull rhs) { + return lhs->impl == rhs->impl; +} + +bool TWHarmonyAddressIsValidString(TWString *_Nonnull string) { + auto s = reinterpret_cast(string); + return Address::isValid(*s).first; +} + +struct TWHarmonyAddress *_Nullable TWHarmonyAddressCreateWithString(TWString *_Nonnull string) { + auto s = reinterpret_cast(string); + if (!Address::isValid(*s).first) { + return nullptr; + } + return new TWHarmonyAddress{Address(*s)}; +} + +struct TWHarmonyAddress *_Nullable TWHarmonyAddressCreateWithKeyHash(TWData *_Nonnull keyHash) { + auto d = reinterpret_cast(keyHash); + if (!Address::isValid(*d)) { + return nullptr; + } + return new TWHarmonyAddress{Address(*d)}; +} + +struct TWHarmonyAddress *_Nonnull TWHarmonyAddressCreateWithPublicKey( + struct TWPublicKey *_Nonnull publicKey) { + return new TWHarmonyAddress{Address(publicKey->impl)}; +} + +void TWHarmonyAddressDelete(struct TWHarmonyAddress *_Nonnull address) { + delete address; +} + +TWString *_Nonnull TWHarmonyAddressDescription(struct TWHarmonyAddress *_Nonnull address) { + const auto string = address->impl.string(); + return TWStringCreateWithUTF8Bytes(string.c_str()); +} + +TWData *_Nonnull TWHarmonyAddressKeyHash(struct TWHarmonyAddress *_Nonnull address) { + return TWDataCreateWithBytes(address->impl.bytes.data(), address->impl.bytes.size()); +} diff --git a/src/interface/TWHarmonySigner.cpp b/src/interface/TWHarmonySigner.cpp new file mode 100644 index 00000000000..c1abbb59881 --- /dev/null +++ b/src/interface/TWHarmonySigner.cpp @@ -0,0 +1,25 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright 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 "../Harmony/Signer.h" +#include "../proto/Harmony.pb.h" +#include "../uint256.h" + +using namespace TW; +using namespace TW::Harmony; + +TW_Harmony_Proto_SigningOutput TWHarmonySignerSign(TW_Harmony_Proto_SigningInput data) { + Proto::SigningInput input; + input.ParseFromArray(TWDataBytes(data), static_cast(TWDataSize(data))); + auto signer = Signer(load(input.chain_id())); + auto protoOutput = signer.sign(input); + + auto serialized = protoOutput.SerializeAsString(); + return TWDataCreateWithBytes(reinterpret_cast(serialized.data()), + serialized.size()); +} diff --git a/src/proto/Harmony.proto b/src/proto/Harmony.proto new file mode 100644 index 00000000000..0f43e134ea8 --- /dev/null +++ b/src/proto/Harmony.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package TW.Harmony.Proto; +option java_package = "wallet.core.jni.proto"; + +// Input data necessary to create a signed transaction. +message SigningInput { + // Chain identifier (256-bit number) + bytes chain_id = 1; + + // Nonce (256-bit number) + bytes nonce = 2; + + // Gas price (256-bit number) + bytes gas_price = 3; + + // Gas limit (256-bit number) + bytes gas_limit = 4; + + // Recipient's address. + string to_address = 5; + + // Amount to send in wei (256-bit number) + bytes amount = 6; + + // Optional payload + bytes payload = 7; + + // Private key. + bytes private_key = 8; + + // From shard ID (256-bit number) + bytes from_shard_id = 9; + + // To Shard ID (256-bit number) + bytes to_shard_id = 10; +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; + + bytes v = 2; + bytes r = 3; + bytes s = 4; +} diff --git a/swift/Sources/Addresses/CoinType+Address.swift b/swift/Sources/Addresses/CoinType+Address.swift index 2701b37e429..d5cf4426e07 100644 --- a/swift/Sources/Addresses/CoinType+Address.swift +++ b/swift/Sources/Addresses/CoinType+Address.swift @@ -93,6 +93,8 @@ public extension CoinType { return WavesAddress(string: string) case .aeternity: return AeternityAddress(string: string) + case .harmony: + return HarmonyAddress(string: string) case .solana: return SolanaAddress(string: string) } diff --git a/swift/Tests/Blockchains/HarmonyTests.swift b/swift/Tests/Blockchains/HarmonyTests.swift new file mode 100644 index 00000000000..8bde129fd69 --- /dev/null +++ b/swift/Tests/Blockchains/HarmonyTests.swift @@ -0,0 +1,33 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +import XCTest +import TrustWalletCore + +class HarmonyTests: XCTestCase { + func testSigner() { + let localNet = "0x02" + let input = TW_Harmony_Proto_SigningInput.with { + $0.chainID = Data(hexString: localNet)! + $0.nonce = Data(hexString: "0x9")! + $0.gasPrice = Data(hexString: "0x")! + $0.gasLimit = Data(hexString: "0x5208")! + $0.fromShardID = Data(hexString: "0x1")! + $0.toShardID = Data(hexString: "0x0")! + $0.payload = Data(hexString: "0x")! + $0.toAddress = "one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc" + $0.amount = Data(hexString: "0x4c53ecdc18a60000")! + $0.privateKey = Data(hexString: "0xb578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")! + } + let output = HarmonySigner.sign(input: input) + let e1 = "f86909808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a600008028a0325aed6caa01a5235b" + let e2 = "7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372da06c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b" + XCTAssertEqual(output.encoded.hexString, e1 + e2) + XCTAssertEqual(output.v.hexString, "28") + XCTAssertEqual(output.r.hexString, "325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d") + XCTAssertEqual(output.s.hexString, "6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b") + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index e1860ccad99..67be3f41505 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -187,6 +187,9 @@ class CoinAddressDerivationTests: XCTestCase { case .fio: let expectedResult = "FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + case .harmony: + let expectedResult = "one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09" + AssetCoinDerivation(coin, expectedResult, derivedAddress, address) case .solana: let expectedResult = "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) diff --git a/tests/Harmony/AddressTests.cpp b/tests/Harmony/AddressTests.cpp new file mode 100644 index 00000000000..26e72f38c89 --- /dev/null +++ b/tests/Harmony/AddressTests.cpp @@ -0,0 +1,42 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Harmony/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +using namespace TW; +using namespace TW::Harmony; + +TEST(HarmonyAddress, FromString) { + const auto sender = Address("one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); + const auto receiver = Address("one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p"); + + ASSERT_EQ(sender.hexDump(), "ed1ebe4fd1f73f86388f231997859ca42c07da5d"); + ASSERT_EQ(receiver.hexDump(), "587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2"); +} + +TEST(HarmonyAddress, FromData) { + const auto address = Address(parse_hex("0x587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2")); + const auto address_2 = Address(parse_hex("0xed1ebe4fd1f73f86388f231997859ca42c07da5d")); + ASSERT_EQ(address.string(), "one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p"); + ASSERT_EQ(address_2.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); +} + +TEST(HarmonyAddress, InvalidHarmonyAddress) { + ASSERT_FALSE(Address::isValid("one1a50tun737ulcvwy0yvve0pe").first); + ASSERT_FALSE(Address::isValid("oe1tp7xdd9ewwnmyvws96au0ee7e7mz6f8hjqr3g3p").first); +} + +TEST(HarmonyAddress, FromPrivateKey) { + const auto privateKey = + PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); +} diff --git a/tests/Harmony/SignerTests.cpp b/tests/Harmony/SignerTests.cpp new file mode 100644 index 00000000000..68903aaa307 --- /dev/null +++ b/tests/Harmony/SignerTests.cpp @@ -0,0 +1,160 @@ +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include +#include + +#include "Ethereum/RLP.h" +#include "Harmony/Address.h" +#include "Harmony/Signer.h" +#include "HexCoding.h" +#include "proto/Harmony.pb.h" + +namespace TW::Harmony { + +using namespace boost::multiprecision; + +class SignerExposed : public Signer { + public: + SignerExposed(uint256_t chainID) : Signer(chainID) {} + using Signer::hash; +}; + +static uint256_t MAIN_NET = TWHarmonyChainIDMainNet; + +static uint256_t LOCAL_NET = 0x2; + +static uint256_t TEST_AMOUNT = uint256_t("0x4c53ecdc18a60000"); + +static auto TEST_RECEIVER = Address("one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc"); + +static auto TEST_TRANSACTION = Transaction(/* nonce: */ 0x9, + /* gasPrice: */ 0x0, + /* gasLimit: */ 0x5208, + /* fromShardID */ 0x1, + /* toShardID */ 0x0, + /* to: */ TEST_RECEIVER, + /* amount: */ TEST_AMOUNT, + /* payload: */ {}); + +TEST(HarmonySigner, RLPEncodingAndHashAssumeLocalNet) { + auto rlpUnhashedShouldBe = "e909808252080180946a87346f3ba9958d08d09484a" + "2b7fdbbe42b0df6884c53ecdc18a6000080028080"; + auto rlpHashedShouldBe = "610238ad72e4492af494f49bf5d92" + "13626a0ee5adb8256bb2558e990ee4da8f0"; + auto signer = SignerExposed(LOCAL_NET); + auto rlpHex = signer.txnAsRLPHex(TEST_TRANSACTION); + auto hash = signer.hash(TEST_TRANSACTION); + + ASSERT_EQ(rlpHex, rlpUnhashedShouldBe); + ASSERT_EQ(hex(hash), rlpHashedShouldBe); +} + +TEST(HarmonySigner, SignAssumeLocalNet) { + auto key = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto signer = SignerExposed(LOCAL_NET); + + uint256_t v("0x28"); + uint256_t r("0x325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d"); + uint256_t s("0x6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b"); + + auto transaction = Transaction(TEST_TRANSACTION); + + signer.sign(key, transaction); + + ASSERT_EQ(transaction.v, v); + ASSERT_EQ(transaction.r, r); + ASSERT_EQ(transaction.s, s); +} + +TEST(HarmonySigner, SignProtoBufAssumeLocalNet) { + auto input = Proto::SigningInput(); + input.set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + input.set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(LOCAL_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0x9")); + input.set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + input.set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + input.set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + input.set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + input.set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + input.set_amount(value.data(), value.size()); + + auto proto_output = TW::Harmony::Signer::sign(input); + + auto shouldBeV = "28"; + auto shouldBeR = "325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d"; + auto shouldBeS = "6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b"; + + ASSERT_EQ(hex(proto_output.v()), shouldBeV); + ASSERT_EQ(hex(proto_output.r()), shouldBeR); + ASSERT_EQ(hex(proto_output.s()), shouldBeS); +} + +TEST(HarmonySigner, SignOverProtoBufAssumeMainNet) { + auto input = Proto::SigningInput(); + input.set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + input.set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + input.set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + input.set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + input.set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + input.set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + input.set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + input.set_amount(value.data(), value.size()); + + auto proto_output = TW::Harmony::Signer::sign(input); + + auto expectEncoded = "f8690a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a" + "600008026a074acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b1543045053667" + "08a0616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + auto v = "26"; + auto r = "74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708"; + auto s = "616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} +} // namespace TW::Harmony diff --git a/tests/interface/TWCoinTypeConfigTests.cpp b/tests/interface/TWCoinTypeConfigTests.cpp index 53a4d2b0629..791201e958e 100644 --- a/tests/interface/TWCoinTypeConfigTests.cpp +++ b/tests/interface/TWCoinTypeConfigTests.cpp @@ -146,6 +146,9 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeConfigurationGetSymbol) { auto fio = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFIO)); assertStringsEqual(fio, "FIO"); + auto hmy = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeHarmony)); + assertStringsEqual(hmy, "ONE"); + auto solana = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSolana)); assertStringsEqual(solana, "SOL"); } @@ -198,6 +201,7 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeConfigurationGetDecimals) { ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTerra), 6); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMonacoin), 8); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFIO), 9); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeHarmony), 18); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSolana), 13); } @@ -254,9 +258,8 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeConfigurationGetTransactionURL) { auto bnb = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBinance, txId)); assertStringsEqual(bnb, "https://explorer.binance.org/tx/123"); - auto zecTxId = TWStringCreateWithUTF8Bytes("d831fda3a9e74d14cd151d035ab77cf0a71eea6c0e4aa0d5c1de54851c3c1d9e"); - auto zec = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcash, zecTxId)); - assertStringsEqual(zec, "https://chain.so/tx/ZEC/d831fda3a9e74d14cd151d035ab77cf0a71eea6c0e4aa0d5c1de54851c3c1d9e"); + auto zec = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcash, txId)); + assertStringsEqual(zec, "https://chain.so/tx/ZEC/123"); auto xtz = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTezos, txId)); assertStringsEqual(xtz, "https://tzscan.io/123"); @@ -348,6 +351,9 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeConfigurationGetTransactionURL) { auto mona = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMonacoin, txId)); assertStringsEqual(mona, "https://blockbook.electrum-mona.org/tx/123"); + auto harmony = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeHarmony, txId)); + assertStringsEqual(harmony, "https://explorer.harmony.one/#/tx/123"); + auto solana = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSolana, txId)); assertStringsEqual(solana, "https://explorer.solana.com/tx/123"); } @@ -491,6 +497,9 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeConfigurationGetID) { auto fio = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFIO)); assertStringsEqual(fio, "fio"); + auto hmy = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeHarmony)); + assertStringsEqual(hmy, "harmony"); + auto solana = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSolana)); assertStringsEqual(solana, "solana"); } @@ -634,6 +643,9 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeConfigurationGetName) { auto fio = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFIO)); assertStringsEqual(fio, "FIO"); + auto hmy = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeHarmony)); + assertStringsEqual(hmy, "Harmony"); + auto solana = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSolana)); assertStringsEqual(solana, "Solana"); } @@ -658,6 +670,7 @@ TEST(TWCoinTypeConfiguration, TWCoinTypeBlockchain) { ASSERT_EQ(TWBlockchainAeternity, TWCoinTypeBlockchain(TWCoinTypeAeternity)); ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTerra)); ASSERT_EQ(TWBlockchainFIO, TWCoinTypeBlockchain(TWCoinTypeFIO)); + ASSERT_EQ(TWBlockchainHarmony, TWCoinTypeBlockchain(TWCoinTypeHarmony)); ASSERT_EQ(TWBlockchainSolana, TWCoinTypeBlockchain(TWCoinTypeSolana)); } diff --git a/tests/interface/TWHarmonySignerTests.cpp b/tests/interface/TWHarmonySignerTests.cpp new file mode 100644 index 00000000000..d0c634e2455 --- /dev/null +++ b/tests/interface/TWHarmonySignerTests.cpp @@ -0,0 +1,71 @@ + +// Copyright © 2017-2019 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Data.h" +#include "Harmony/Transaction.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "TWTestUtilities.h" +#include "proto/Harmony.pb.h" +#include "uint256.h" +#include + +#include + +using namespace TW; +using namespace Harmony; + +static auto TEST_RECEIVER = Address("one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k"); + +static uint256_t LOCAL_NET = 0x2; + +TEST(TWHarmonySigner, Sign) { + Proto::SigningInput input; + + input.set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")); + auto payload = parse_hex(""); + input.set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(LOCAL_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0x1")); + input.set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + input.set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + input.set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + input.set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + input.set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x6bfc8da5ee8220000")); + input.set_amount(value.data(), value.size()); + + auto inputData = input.SerializeAsString(); + auto inputTWData = TWDataCreateWithBytes((const byte *)inputData.data(), inputData.size()); + auto outputTWData = TWHarmonySignerSign(inputTWData); + + auto output = Proto::SigningOutput(); + output.ParseFromArray(TWDataBytes(outputTWData), TWDataSize(outputTWData)); + + auto shouldBeV = "28"; + auto shouldBeR = "84cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5c"; + auto shouldBeS = "643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"; + + ASSERT_EQ(hex(output.v()), shouldBeV); + ASSERT_EQ(hex(output.r()), shouldBeR); + ASSERT_EQ(hex(output.s()), shouldBeS); +}