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);
+}