Skip to content

Commit

Permalink
Add Hedera support (trustwallet#2680)
Browse files Browse the repository at this point in the history
  • Loading branch information
Milerius authored Nov 8, 2022
1 parent cc07e5f commit b59b97c
Show file tree
Hide file tree
Showing 41 changed files with 3,050 additions and 12 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ endif ()
if (NOT ANDROID AND TW_UNITY_BUILD)
set_target_properties(TrustWalletCore PROPERTIES UNITY_BUILD ON)

file(GLOB_RECURSE PROTOBUF_SOURCE_FILES CONFIGURE_DEPENDS src/Cosmos/Protobuf/*.pb.cc src/proto/*.pb.cc)
file(GLOB_RECURSE PROTOBUF_SOURCE_FILES CONFIGURE_DEPENDS src/Cosmos/Protobuf/*.pb.cc src/Hedera/Protobuf/*.pb.cc src/proto/*.pb.cc)
foreach(file ${PROTOBUF_SOURCE_FILES})
set_property(SOURCE ${file} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON)
endforeach()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,6 @@ class CoinAddressDerivationTests {
NERVOS -> assertEquals("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3", address)
EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address)
APTOS -> assertEquals("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address)
HEDERA -> assertEquals("0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5", address)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright © 2017-2022 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package com.trustwallet.core.app.blockchains.hedera

import com.trustwallet.core.app.utils.toHex
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.jni.*

class TestHederaAddress {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testAddress() {
val any = AnyAddress("0.0.1377988", CoinType.HEDERA)
assertEquals(any.coin(), CoinType.HEDERA)
assertEquals(any.description(), "0.0.1377988")

Assert.assertFalse(
AnyAddress.isValid(
"0.0.a",
CoinType.HEDERA
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright © 2017-2022 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package com.trustwallet.core.app.blockchains.hedera

import com.google.protobuf.ByteString
import com.trustwallet.core.app.utils.Numeric
import com.trustwallet.core.app.utils.toHexByteArray
import com.trustwallet.core.app.utils.toHexBytes
import com.trustwallet.core.app.utils.toHexBytesInByteString
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.*
import wallet.core.jni.proto.Hedera

class TestHederaSigner {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun HederaTransactionSimpleTransferSigning() {
// Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1
val key =
"e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8".toHexBytesInByteString()

val transfer = Hedera.TransferMessage
.newBuilder()
.setAmount(100000000)
.setFrom("0.0.48694347")
.setTo("0.0.48462050")
.build()

val timestamp = Hedera.Timestamp
.newBuilder()
.setSeconds(1667222879)
.setNanos(749068449)
.build()

val transactionID = Hedera.TransactionID
.newBuilder()
.setTransactionValidStart(timestamp)
.setAccountID("0.0.48694347")
.build()

val body = Hedera.TransactionBody
.newBuilder()
.setMemo("")
.setTransfer(transfer)
.setTransactionID(transactionID)
.setNodeAccountID("0.0.9")
.setTransactionFee(100000000)
.setTransactionValidDuration(120)
.build()

val signingInput = Hedera.SigningInput
.newBuilder()
.setPrivateKey(key)
.setBody(body).build()

val result = AnySigner.sign(signingInput, CoinType.HEDERA, Hedera.SigningOutput.parser())
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())),
"0a440a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40612589c3b15f1e3ed6084b5a3a5b1b81751578cac8d6c922f31731b3982a5bac80a22558b2197276f5bae49b62503a4d39448ceddbc5ef3ba9bee4c0f302f70c"
)
}
}
2 changes: 1 addition & 1 deletion codegen/lib/templates/newcoin/Address.h.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#pragma once

#include "Data.h"
#include "../PublicKey.h"
#include "PublicKey.h"

#include <string>

Expand Down
2 changes: 1 addition & 1 deletion codegen/lib/templates/newcoin/Entry.h.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace TW::<%= format_name(coin) %> {
/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file
class Entry final : public CoinEntry {
public:
virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const;
virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, const PrefixVariant& addressPrefix) const;
virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const;
virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const;
// normalizeAddress(): implement this if needed, e.g. Ethereum address is EIP55 checksummed
Expand Down
1 change: 1 addition & 0 deletions docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ This list is generated from [./registry.json](../registry.json)
| 1815 | Cardano | ADA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/cardano/info/logo.png" width="32" /> | <https://www.cardano.org> |
| 2301 | Qtum | QTUM | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/qtum/info/logo.png" width="32" /> | <https://qtum.org> |
| 2718 | Nebulas | NAS | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/nebulas/info/logo.png" width="32" /> | <https://nebulas.io> |
| 3030 | Hedera | HBAR | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/hedera/info/logo.png" width="32" /> | <https://hedera.com/> |
| 6060 | GoChain | GO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/gochain/info/logo.png" width="32" /> | <https://gochain.io> |
| 8964 | NULS | NULS | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/nuls/info/logo.png" width="32" /> | <https://nuls.io> |
| 18000 | Meter | MTR | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/meter/info/logo.png" width="32" /> | <https://meter.io/> |
Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWBlockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ enum TWBlockchain {
TWBlockchainNervos = 41,
TWBlockchainEverscale = 42,
TWBlockchainAptos = 43, // Aptos
TWBlockchainHedera = 44, // Hedera
};

TW_EXTERN_C_END
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWCoinType.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ enum TWCoinType {
TWCoinTypeNervos = 309,
TWCoinTypeEverscale = 396,
TWCoinTypeAptos = 637,
TWCoinTypeHedera = 3030,
};

/// Returns the blockchain for a coin type.
Expand Down
41 changes: 34 additions & 7 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,7 @@
"documentation": "https://eth.wiki/json-rpc/API"
},
"deprecated": true,
"testFolderName" : "Binance"
"testFolderName": "Binance"
},
{
"id": "smartchain",
Expand Down Expand Up @@ -1977,7 +1977,7 @@
"rpc": "https://bsc-dataseed1.binance.org",
"documentation": "https://eth.wiki/json-rpc/API"
},
"testFolderName" : "Binance"
"testFolderName": "Binance"
},
{
"id": "polygon",
Expand Down Expand Up @@ -2157,7 +2157,7 @@
"rpc": "https://http-mainnet-node.huobichain.com",
"documentation": "https://eth.wiki/json-rpc/API"
},
"testFolderName" : "ECO"
"testFolderName": "ECO"
},
{
"id": "avalanchec",
Expand Down Expand Up @@ -2188,7 +2188,7 @@
"clientPublic": "https://api.avax.network/ext/bc/C/rpc",
"clientDocs": "https://docs.avax.network/"
},
"testFolderName" : "Avalanche"
"testFolderName": "Avalanche"
},
{
"id": "xdai",
Expand Down Expand Up @@ -2436,7 +2436,7 @@
"clientPublic": "https://evm-cronos.crypto.org",
"clientDocs": "https://eth.wiki/json-rpc/API"
},
"testFolderName" : "Cronos"
"testFolderName": "Cronos"
},
{
"id": "kavaevm",
Expand Down Expand Up @@ -2495,7 +2495,7 @@
"rpc": "https://smartbch.fountainhead.cash/mainnet",
"documentation": "https://github.com/smartbch/docs/blob/main/developers-guide/jsonrpc.md"
},
"testFolderName" : "Bitcoin"
"testFolderName": "Bitcoin"
},
{
"id": "kcc",
Expand Down Expand Up @@ -2526,7 +2526,7 @@
"rpc": "https://rpc-mainnet.kcc.network",
"documentation": "https://docs.kcc.io/#/en-us/"
},
"testFolderName" : "KuCoinCommunityChain"
"testFolderName": "KuCoinCommunityChain"
},
{
"id": "boba",
Expand Down Expand Up @@ -2821,5 +2821,32 @@
"rpc": "https://exchainrpc.okex.org",
"documentation": "https://okc-docs.readthedocs.io/en/latest"
}
},
{
"id": "hedera",
"name": "Hedera",
"coinId": 3030,
"symbol": "HBAR",
"decimals": 8,
"blockchain": "Hedera",
"derivation": [
{
"path": "m/44'/3030'/0'/0'/0"
}
],
"curve": "ed25519",
"publicKeyType": "ed25519",
"explorer": {
"url": "https://hashscan.io/mainnet",
"txPath": "/transaction/",
"accountPath": "/account/",
"sampleTx": "0.0.19790-1666769504-858851231",
"sampleAccount": "0.0.19790"
},
"info": {
"url": "https://hedera.com/",
"source": "https://github.com/hashgraph",
"documentation": "https://docs.hedera.com"
}
}
]
3 changes: 3 additions & 0 deletions src/Coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include "XRP/Entry.h"
#include "Zcash/Entry.h"
#include "Zilliqa/Entry.h"
#include "Hedera/Entry.h"
// end_of_coin_includes_marker_do_not_modify

using namespace TW;
Expand Down Expand Up @@ -103,6 +104,7 @@ Zcash::Entry zcashDP;
Zilliqa::Entry zilliqaDP;
Nervos::Entry NervosDP;
Everscale::Entry EverscaleDP;
Hedera::Entry HederaDP;
// end_of_coin_dipatcher_declarations_marker_do_not_modify

CoinEntry* coinDispatcher(TWCoinType coinType) {
Expand Down Expand Up @@ -153,6 +155,7 @@ CoinEntry* coinDispatcher(TWCoinType coinType) {
case TWBlockchainNervos: entry = &NervosDP; break;
case TWBlockchainEverscale: entry = &EverscaleDP; break;
case TWBlockchainAptos: entry = &AptosDP; break;
case TWBlockchainHedera: entry = &HederaDP; break;
// end_of_coin_dipatcher_switch_marker_do_not_modify

default: entry = nullptr; break;
Expand Down
6 changes: 6 additions & 0 deletions src/Data.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ inline void append(Data& data, const Data& suffix) {
data.insert(data.end(), suffix.begin(), suffix.end());
}

inline Data concat(const Data& data, const Data& suffix) {
Data out = data;
append(out, suffix);
return out;
}

inline void append(Data& data, const byte suffix) {
data.push_back(suffix);
}
Expand Down
90 changes: 90 additions & 0 deletions src/Hedera/Address.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright © 2017-2022 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Address.h"
#include "HexCoding.h"
#include "DER.h"
#include "algorithm/string.hpp"

#include <charconv>
#include <regex>

namespace TW::Hedera::internal {
static const std::regex gEntityIDRegex{"(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([a-z]{5}))?$"};
}

namespace TW::Hedera {


Alias::Alias(std::optional<PublicKey> alias) noexcept : mPubKey(std::move(alias)) {

}

std::string Alias::string() const noexcept {
std::string pubkeyBytes = "";
if (mPubKey.has_value()) {
pubkeyBytes = hex(mPubKey.value().bytes);
}
return gHederaDerPrefixPublic + pubkeyBytes;
}

bool Address::isValid(const std::string& string) {
using namespace internal;
std::smatch match;
auto isValid = std::regex_match(string, match, gEntityIDRegex);
if (!isValid) {
auto parts = TW::ssplit(string, '.');
if (parts.size() != 3) {
return false;
}
auto isNumberFunctor = [](std::string_view input) {
return input.find_first_not_of("0123456789") == std::string::npos;
};
if (!isNumberFunctor(parts[0]) || !isNumberFunctor(parts[1])) {
return false;
}
isValid = hasDerPrefix(parts[2]);
}
return isValid;
}

Address::Address(const std::string& string) {
if (!isValid(string)) {
throw std::invalid_argument("Invalid address string");
}

auto toInt = [](std::string_view s) -> std::optional<std::size_t> {
if (std::size_t value = 0; std::from_chars(s.begin(), s.end(), value).ec == std::errc{}) {
return value;
} else {
return std::nullopt;
}
};

// When creating an Address by string - we assume to only sent to 0.0.1 format, alias is internal.
auto parts = TW::ssplit(string, '.');
mShard = *toInt(parts[0]);
mRealm = *toInt(parts[1]);
mNum = *toInt(parts[2]);
}

Address::Address(const PublicKey& publicKey)
: Address(0, 0, 0, publicKey) {
}

std::string Address::string() const {
std::string out = std::to_string(mShard) + "." + std::to_string(mRealm) + ".";
if (mAlias.mPubKey.has_value()) {
return out + mAlias.string();
}
return out + std::to_string(mNum);
}

Address::Address(std::size_t shard, std::size_t realm, std::size_t num, std::optional<PublicKey> alias)
: mShard(shard), mRealm(realm), mNum(num), mAlias(std::move(alias)) {
}

} // namespace TW::Hedera
Loading

0 comments on commit b59b97c

Please sign in to comment.