Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Elrond blockchain #929

Merged
merged 12 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ class CoinAddressDerivationTests {
CARDANO -> assertEquals("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", address)
NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address)
FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address)
ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package com.trustwallet.core.app.blockchains.elrond

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

class TestElrondAddress {

init {
System.loadLibrary("TrustWalletCore")
}

private val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"
private var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"
private var alicePubKeyHex = "0xfd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"

@Test
fun testAddressFromPrivateKey() {
val key = PrivateKey(aliceSeedHex.toHexByteArray())
val pubKey = key.publicKeyEd25519
val address = AnyAddress(pubKey, CoinType.ELROND)

assertEquals(alicePubKeyHex, pubKey.data().toHex())
assertEquals(aliceBech32, address.description())
}

@Test
fun testAddressFromPublicKey() {
val pubKey = PublicKey(alicePubKeyHex.toHexByteArray(), PublicKeyType.ED25519)
val address = AnyAddress(pubKey, CoinType.ELROND)

assertEquals(aliceBech32, address.description())
}

@Test
fun testAddressFromString() {
val address = AnyAddress(aliceBech32, CoinType.ELROND)

assertEquals(aliceBech32, address.description())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package com.trustwallet.core.app.blockchains.elrond

import com.google.protobuf.ByteString
import com.trustwallet.core.app.utils.toHexByteArray
import junit.framework.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.CoinType
import wallet.core.jni.PrivateKey
import wallet.core.jni.proto.Elrond

class TestElrondSigner {

init {
System.loadLibrary("TrustWalletCore")
}

val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"
var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"
var alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"

val bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"
var bobSeedHex = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e"
var bobPubKeyHex = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36"

@Test
fun signTransaction() {
val transaction = Elrond.TransactionMessage.newBuilder()
.setNonce(0)
.setValue("0")
.setSender(aliceBech32)
.setReceiver(bobBech32)
.setGasPrice(200000000000000)
.setGasLimit(500000000)
.setData("foo")
.build()

val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data())

val signingInput = Elrond.SigningInput.newBuilder()
.setPrivateKey(privateKey)
.setTransaction(transaction)
.build()

val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser())
val expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"

assertEquals(expectedSignature, output.signature)
assertEquals("""{"nonce":0,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"$expectedSignature"}""", output.encoded)
}
}
22 changes: 22 additions & 0 deletions coins.json
Original file line number Diff line number Diff line change
Expand Up @@ -1330,5 +1330,27 @@
"clientPublic": "",
"clientDocs": "https://docs.lotu.sh"
}
},
{
"id": "elrond",
"name": "Elrond",
"symbol": "ERD",
"decimals": 18,
"blockchain": "ElrondNetwork",
"derivationPath": "m/44'/508'/0'/0'/0'",
"curve": "ed25519",
"publicKeyType": "ed25519",
"hrp": "erd",
"explorer": {
"url": "https://explorer.elrond.com",
"txPath": "/transactions/",
"accountPath": "/address/"
},
"info": {
"url": "https://elrond.com/",
"client": "https://github.com/ElrondNetwork/elrond-go",
"clientPublic": "https://api.elrond.com",
"clientDocs": "https://docs.elrond.com"
}
}
]
1 change: 1 addition & 0 deletions docs/coins.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ This list is generated from [./coins.json](../coins.json)
| 461 | Filecoin | FIL | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/filecoin/info/logo.png" width="32" /> | <https://filecoin.io/> |
| 500 | Theta | THETA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/theta/info/logo.png" width="32" /> | <https://www.thetatoken.org> |
| 501 | Solana | SOL | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png" width="32" /> | <https://solana.com> |
| 508 | Elrond | ERD | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/elrond/info/logo.png" width="32" /> | <https://elrond.com/> |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logo.png is 404

Copy link
Contributor Author

@andreibancioiu andreibancioiu Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, we did not open a pull request against the repository trustwallet/assets yet, but we are bootstrapping this flow as well.

| 714 | Binance | BNB | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/binance/info/logo.png" width="32" /> | <https://binance.org> |
| 818 | VeChain | VET | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/vechain/info/logo.png" width="32" /> | <https://vechain.org> |
| 820 | Callisto | CLO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/callisto/info/logo.png" width="32" /> | <https://callisto.network> |
Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWBlockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ enum TWBlockchain {
TWBlockchainCardano = 30,
TWBlockchainNEO = 31,
TWBlockchainFilecoin = 32,
TWBlockchainElrondNetwork = 33,
};

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 @@ -79,6 +79,7 @@ enum TWCoinType {
TWCoinTypeKusama = 434,
TWCoinTypePolkadot = 354,
TWCoinTypeFilecoin = 461,
TWCoinTypeElrond = 508,
};

/// Returns the blockchain for a coin type.
Expand Down
2 changes: 2 additions & 0 deletions src/Coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "Waves/Entry.h"
#include "Zcash/Entry.h"
#include "Zilliqa/Entry.h"
#include "Elrond/Entry.h"
// end_of_coin_includes_marker_do_not_modify

using namespace TW;
Expand Down Expand Up @@ -100,6 +101,7 @@ void setupDispatchers() {
new Waves::Entry(),
new Zcash::Entry(),
new Zilliqa::Entry(),
new Elrond::Entry(),
}; // end_of_coin_entries_marker_do_not_modify

dispatchMap.clear();
Expand Down
17 changes: 17 additions & 0 deletions src/Elrond/Address.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include <TrustWalletCore/TWHRP.h>

#include "Address.h"

using namespace TW::Elrond;

const std::string Address::hrp = HRP_ELROND;

bool Address::isValid(const std::string& string) {
return Bech32Address::isValid(string, hrp);
}
38 changes: 38 additions & 0 deletions src/Elrond/Address.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#pragma once

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

#include <string>

namespace TW::Elrond {

class Address : public Bech32Address {
public:
// The human-readable part of the address, as defined in "coins.json"
static const std::string hrp; // HRP_ELROND

/// Determines whether a string makes a valid address.
static bool isValid(const std::string& string);

Address() : Bech32Address(hrp) {}

/// Initializes an address with a key hash.
Address(Data keyHash) : Bech32Address(hrp, keyHash) {}

/// Initializes an address with a public key.
Address(const PublicKey& publicKey) : Bech32Address(hrp, publicKey.bytes) {}

static bool decode(const std::string& addr, Address& obj_out) {
return Bech32Address::decode(addr, obj_out, hrp);
}
};

} // namespace TW::Elrond
31 changes: 31 additions & 0 deletions src/Elrond/Entry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Entry.h"

#include "Address.h"
#include "Signer.h"

using namespace TW::Elrond;
using namespace std;

// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc.

bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const {
return Address::isValid(address);
}

string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const {
return Address(publicKey).string();
}

void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const {
signTemplate<Signer, Proto::SigningInput>(dataIn, dataOut);
}

string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const {
return Signer::signJSON(json, key);
}
25 changes: 25 additions & 0 deletions src/Elrond/Entry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#pragma once

#include "../CoinEntry.h"

namespace TW::Elrond {

/// Entry point for implementation of Elrond coin.
/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file
class Entry: public CoinEntry {
public:
virtual std::vector<TWCoinType> coinTypes() const { return {TWCoinTypeElrond}; }
virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const;
virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const;
virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const;
virtual bool supportsJSONSigning() const { return true; }
virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const;
};

} // namespace TW::Elrond
67 changes: 67 additions & 0 deletions src/Elrond/Serialization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Serialization.h"

#include "../Elrond/Address.h"
#include "../proto/Elrond.pb.h"
#include "Base64.h"
#include "PrivateKey.h"

using namespace TW;

std::map<string, int> fields_order {
{"nonce", 1},
{"value", 2},
{"receiver", 3},
{"sender", 4},
{"gasPrice", 5},
{"gasLimit", 6},
{"data", 7},
{"signature", 8}
};

struct FieldsSorter {
bool operator() (const string& lhs, const string& rhs) const {
return fields_order[lhs] < fields_order[rhs];
}
};

template<class Key, class T, class Compare, class Allocator>
using sorted_map = std::map<Key, T, FieldsSorter, Allocator>;
using sorted_json = nlohmann::basic_json<sorted_map>;

string Elrond::serializeTransaction(const Proto::TransactionMessage& message) {
sorted_json payload {
{"nonce", json(message.nonce())},
{"value", json(message.value())},
{"receiver", json(message.receiver())},
{"sender", json(message.sender())},
{"gasPrice", json(message.gas_price())},
{"gasLimit", json(message.gas_limit())},
};

if (!message.data().empty()) {
payload["data"] = json(TW::Base64::encode(TW::data(message.data())));
andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
}

return payload.dump();
}

string Elrond::serializeSignedTransaction(const Proto::TransactionMessage& message, string signature) {
sorted_json payload {
{"nonce", json(message.nonce())},
{"value", json(message.value())},
{"receiver", json(message.receiver())},
{"sender", json(message.sender())},
{"gasPrice", json(message.gas_price())},
{"gasLimit", json(message.gas_limit())},
{"data", json(message.data())},
Comment on lines +56 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks duplicate code in serializeTransactionToSignableString, also, data is not checked?

Copy link
Contributor Author

@andreibancioiu andreibancioiu Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is duplication, indeed - a bit closer to accidental duplication though, and we would like to keep serializeTransaction (called serializeTransactionToSignableString before review) and serializeSignedTransaction separately.

Indeed, the data field is applied a special treatment in the first serialization method whose output is meant to be signed - and we have a more constrained procedure here such as: omit the data field if empty, apply base64 on it. On the other hand, these constraints do not apply to obtaining the actual message that should be dispatched to the blockchain.

Is it acceptable on your side if we keep these as they are?

{"signature", json(signature)},
};

return payload.dump();
}
21 changes: 21 additions & 0 deletions src/Elrond/Serialization.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright © 2017-2020 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#pragma once

#include "../proto/Elrond.pb.h"
#include "Data.h"
#include <nlohmann/json.hpp>

using string = std::string;
using json = nlohmann::json;

namespace TW::Elrond {

string serializeTransaction(const Proto::TransactionMessage& message);
string serializeSignedTransaction(const Proto::TransactionMessage& message, string encodedSignature);

} // namespace
Loading