Skip to content

Commit 51b8666

Browse files
committed
Filecoin support
1 parent 0786968 commit 51b8666

File tree

21 files changed

+827
-0
lines changed

21 files changed

+827
-0
lines changed

android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,6 @@ class CoinAddressDerivationTests {
8383
KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address)
8484
CARDANO -> assertEquals("Ae2tdPwUPEZJ2TwjFBMCnjz6t43pr4QVnBjCGSW8BMsttrt2WagC1D7LUWa", address)
8585
NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address)
86+
FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address)
8687
}
8788
}

coins.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,5 +1329,26 @@
13291329
"clientPublic": "http://seed1.ngd.network:10332",
13301330
"clientDocs": "https://neo.org/eco"
13311331
}
1332+
},
1333+
{
1334+
"id": "filecoin",
1335+
"name": "Filecoin",
1336+
"symbol": "FIL",
1337+
"decimals": 18,
1338+
"blockchain": "Filecoin",
1339+
"derivationPath": "m/44'/461'/0'/0/0",
1340+
"curve": "secp256k1",
1341+
"publicKeyType": "secp256k1Extended",
1342+
"explorer": {
1343+
"url": "https://??",
1344+
"txPath": "/??",
1345+
"accountPath": "/??"
1346+
},
1347+
"info": {
1348+
"url": "https://filecoin.io/",
1349+
"client": "https://github.com/filecoin-project/lotus",
1350+
"clientPublic": "",
1351+
"clientDocs": "https://docs.lotu.sh"
1352+
}
13321353
}
13331354
]

docs/coins.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ This list is generated from [./coins.json](../coins.json)
4141
| 434 | Kusama | KSM | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/kusama/info/logo.png" width="32" /> | <https://kusama.network> |
4242
| 457 | Aeternity | AE | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/aeternity/info/logo.png" width="32" /> | <https://aeternity.com> |
4343
| 459 | Kava | KAVA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/kava/info/logo.png" width="32" /> | <https://kava.io> |
44+
| 461 | Filecoin | FIL | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/filecoin/info/logo.png" width="32" /> | <https://filecoin.io/> |
4445
| 500 | Theta | THETA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/theta/info/logo.png" width="32" /> | <https://www.thetatoken.org> |
4546
| 501 | Solana | SOL | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png" width="32" /> | <https://solana.com> |
4647
| 714 | Binance | BNB | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/binance/info/logo.png" width="32" /> | <https://binance.org> |

include/TrustWalletCore/TWBlockchain.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ enum TWBlockchain {
4444
TWBlockchainPolkadot = 29,
4545
TWBlockchainCardano = 30,
4646
TWBlockchainNEO = 31,
47+
TWBlockchainFilecoin = 32,
4748
};
4849

4950
TW_EXTERN_C_END

include/TrustWalletCore/TWCoinType.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ enum TWCoinType {
7979
TWCoinTypeAlgorand = 283,
8080
TWCoinTypeKusama = 434,
8181
TWCoinTypePolkadot = 354,
82+
TWCoinTypeFilecoin = 461,
8283
};
8384

8485
/// Returns the blockchain for a coin type.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright © 2017-2020 Trust.
2+
//
3+
// This file is part of Trust. The full Trust copyright notice, including
4+
// terms governing use, modification, and redistribution, is contained in the
5+
// file LICENSE at the root of the source code distribution tree.
6+
7+
#pragma once
8+
9+
#include "TWBase.h"
10+
#include "TWData.h"
11+
#include "TWFilecoinProto.h"
12+
13+
TW_EXTERN_C_BEGIN
14+
15+
/// Helper class to sign Filecoin transactions.
16+
TW_EXPORT_CLASS
17+
struct TWFilecoinSigner;
18+
19+
/// Signs a transaction.
20+
TW_EXPORT_STATIC_METHOD
21+
TW_Filecoin_Proto_SigningOutput TWFilecoinSignerSign(TW_Filecoin_Proto_SigningInput input);
22+
23+
TW_EXTERN_C_END

src/Coin.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "EOS/Address.h"
2121
#include "Ethereum/Address.h"
2222
#include "FIO/Address.h"
23+
#include "Filecoin/Address.h"
2324
#include "Groestlcoin/Address.h"
2425
#include "Harmony/Address.h"
2526
#include "Icon/Address.h"
@@ -190,6 +191,9 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) {
190191

191192
case TWCoinTypeNEO:
192193
return NEO::Address::isValid(string);
194+
195+
case TWCoinTypeFilecoin:
196+
return Filecoin::Address::isValid(string);
193197
}
194198
}
195199

@@ -361,6 +365,9 @@ std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) {
361365

362366
case TWCoinTypeNEO:
363367
return NEO::Address(publicKey).string();
368+
369+
case TWCoinTypeFilecoin:
370+
return Filecoin::Address(publicKey).string();
364371
}
365372
}
366373

src/Filecoin/Address.cpp

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright © 2017-2020 Trust.
2+
//
3+
// This file is part of Trust. The full Trust copyright notice, including
4+
// terms governing use, modification, and redistribution, is contained in the
5+
// file LICENSE at the root of the source code distribution tree.
6+
7+
#include "Address.h"
8+
9+
#include "../Base32.h"
10+
#include "../Data.h"
11+
12+
using namespace TW;
13+
using namespace TW::Filecoin;
14+
15+
static const char BASE32_ALPHABET_FILECOIN[] = "abcdefghijklmnopqrstuvwxyz234567";
16+
static constexpr size_t checksum_size = 4;
17+
18+
bool Address::isValid(const Data& data) {
19+
if (data.size() < 2) {
20+
return false;
21+
}
22+
if (!get_type(data[0])) {
23+
return false;
24+
}
25+
Type type = *get_type(data[0]);
26+
if (type == Type::ID) {
27+
// Verify varuint encoding
28+
if (data.size() > 11)
29+
return false;
30+
if (data.size() == 11 && data[10] > 0x01)
31+
return false;
32+
int i;
33+
for (i = 1; i < data.size(); i++)
34+
if ((data[i] & 0x80) == 0)
35+
break;
36+
return i == data.size() - 1;
37+
} else {
38+
return data.size() == 1 + Address::payload_size(type);
39+
}
40+
}
41+
42+
static bool isValidID(const std::string& string) {
43+
if (string.length() > 22)
44+
return false;
45+
for (int i = 2; i < string.length(); i++) {
46+
if (string[i] < '0' || string[i] > '9') {
47+
return false;
48+
}
49+
}
50+
try {
51+
size_t chars;
52+
std::stoull(string.substr(2), &chars);
53+
return chars > 0;
54+
} catch (...) {
55+
return false;
56+
}
57+
}
58+
59+
static bool isValidBase32(const std::string& string, Address::Type type) {
60+
// Check if valid Base32.
61+
uint8_t size = Address::payload_size(type);
62+
Data decoded;
63+
if (!Base32::decode(string.substr(2), decoded, BASE32_ALPHABET_FILECOIN)) {
64+
return false;
65+
}
66+
67+
// Check size
68+
if (decoded.size() != size + 4) {
69+
return false;
70+
}
71+
72+
// Extract raw address.
73+
Data address;
74+
address.push_back(static_cast<uint8_t>(type));
75+
address.insert(address.end(), decoded.data(), decoded.data() + size);
76+
77+
// Verify checksum.
78+
Data should_sum = Hash::blake2b(address, checksum_size);
79+
return std::memcmp(should_sum.data(), decoded.data() + size, checksum_size) == 0;
80+
}
81+
82+
bool Address::isValid(const std::string& string) {
83+
if (string.length() < 3) {
84+
return false;
85+
}
86+
// Only main net addresses supported.
87+
if (string[0] != PREFIX) {
88+
return false;
89+
}
90+
// Get address type.
91+
auto type = parse_type(string[1]);
92+
if (!type) {
93+
return false;
94+
}
95+
96+
// ID addresses are special, they are just numbers.
97+
return type == Type::ID ? isValidID(string) : isValidBase32(string, *type);
98+
}
99+
100+
Address::Address(const std::string& string) {
101+
if (!isValid(string))
102+
throw std::invalid_argument("Invalid address data");
103+
104+
Type type = *(parse_type(string[1]));
105+
// First byte is type
106+
bytes.push_back(static_cast<uint8_t>(type));
107+
if (type == Type::ID) {
108+
uint64_t id = std::stoull(string.substr(2));
109+
while (id >= 0x80) {
110+
bytes.push_back(((uint8_t)id) | 0x80);
111+
id >>= 7;
112+
}
113+
bytes.push_back((uint8_t)id);
114+
return;
115+
}
116+
117+
Data decoded;
118+
if (!Base32::decode(string.substr(2), decoded, BASE32_ALPHABET_FILECOIN))
119+
throw std::invalid_argument("Invalid address data");
120+
uint8_t payload_size = Address::payload_size(type);
121+
122+
bytes.insert(bytes.end(), decoded.data(), decoded.data() + payload_size);
123+
}
124+
125+
Address::Address(const Data& data) {
126+
if (!isValid(data)) {
127+
throw std::invalid_argument("Invalid address data");
128+
}
129+
bytes = data;
130+
}
131+
132+
Address::Address(const PublicKey& publicKey) {
133+
bytes.push_back(static_cast<uint8_t>(Type::SECP256K1));
134+
Data hash = Hash::blake2b(publicKey.bytes, payload_size(Type::SECP256K1));
135+
bytes.insert(bytes.end(), hash.begin(), hash.end());
136+
}
137+
138+
std::string Address::string() const {
139+
std::string s;
140+
// Main net address prefix
141+
s.push_back(PREFIX);
142+
// Address type prefix
143+
s.push_back(type_ascii(type()));
144+
145+
if (type() == Type::ID) {
146+
uint64_t id = 0;
147+
unsigned shift = 0;
148+
for (int i = 1; i < bytes.size(); i++) {
149+
if (bytes[i] < 0x80) {
150+
id |= bytes[i] << shift;
151+
break;
152+
} else {
153+
id |= ((uint64_t)(bytes[i] & 0x7F)) << shift;
154+
shift += 7;
155+
}
156+
}
157+
s.append(std::to_string(id));
158+
return s;
159+
}
160+
161+
uint8_t payload_size = Address::payload_size(type());
162+
// Base32 encoded body
163+
Data to_encode(payload_size + checksum_size);
164+
// Copy address payload without prefix
165+
std::copy(bytes.data() + 1, bytes.data() + payload_size + 1, to_encode.data());
166+
// Append Blake2b checksum
167+
Data bytes_vec;
168+
bytes_vec.assign(std::begin(bytes), std::end(bytes));
169+
Data sum = Hash::blake2b(bytes_vec, checksum_size);
170+
assert(sum.size() == checksum_size);
171+
std::copy(sum.begin(), sum.end(), to_encode.data() + payload_size);
172+
s.append(Base32::encode(to_encode, BASE32_ALPHABET_FILECOIN));
173+
174+
return s;
175+
}

src/Filecoin/Address.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright © 2017-2020 Trust.
2+
//
3+
// This file is part of Trust. The full Trust copyright notice, including
4+
// terms governing use, modification, and redistribution, is contained in the
5+
// file LICENSE at the root of the source code distribution tree.
6+
7+
#pragma once
8+
9+
#include "../PublicKey.h"
10+
11+
#include <array>
12+
#include <cstdint>
13+
#include <optional>
14+
#include <string>
15+
#include <vector>
16+
17+
namespace TW::Filecoin {
18+
19+
class Address {
20+
public:
21+
enum class Type : uint8_t {
22+
ID = 0,
23+
SECP256K1 = 1,
24+
ACTOR = 2,
25+
BLS = 3,
26+
};
27+
28+
/// Address data with address type prefix.
29+
Data bytes;
30+
31+
/// Determines whether a collection of bytes makes a valid address.
32+
static bool isValid(const Data& data);
33+
34+
/// Determines whether a string makes a valid encoded address.
35+
static bool isValid(const std::string& string);
36+
37+
/// Initializes an address with a string representation.
38+
explicit Address(const std::string& string);
39+
40+
/// Initializes an address with a collection of bytes.
41+
explicit Address(const Data& data);
42+
43+
/// Initializes an address with a secp256k1 public key.
44+
explicit Address(const PublicKey& publicKey);
45+
46+
/// Returns a string representation of the address.
47+
[[nodiscard]] std::string string() const;
48+
49+
/// Returns the type of an address.
50+
Type type() const { return *get_type(bytes[0]); }
51+
52+
/// Address prefix
53+
static constexpr char PREFIX = 'f';
54+
55+
public:
56+
/// Attempts to get the type by number.
57+
static std::optional<Type> get_type(uint8_t raw) {
58+
switch (raw) {
59+
case 0:
60+
return Type::ID;
61+
case 1:
62+
return Type::SECP256K1;
63+
case 2:
64+
return Type::ACTOR;
65+
case 3:
66+
return Type::BLS;
67+
default:
68+
return {};
69+
}
70+
}
71+
72+
/// Attempts to get the type by ASCII.
73+
static std::optional<Type> parse_type(char c) {
74+
if (c >= '0' && c <= '3') {
75+
return static_cast<Type>(c - '0');
76+
} else {
77+
return {};
78+
}
79+
}
80+
81+
/// Returns ASCII character of type
82+
static char type_ascii(Type t) { return '0' + static_cast<char>(t); }
83+
84+
// Returns the payload size (excluding any prefixes) of an address type.
85+
// If the payload size is undefined/variable (e.g. ID)
86+
// or the type is unknown, it returns zero.
87+
static uint8_t payload_size(Type t) {
88+
switch (t) {
89+
case Type::SECP256K1:
90+
case Type::ACTOR:
91+
return 20;
92+
case Type::BLS:
93+
return 48;
94+
default:
95+
return 0;
96+
}
97+
}
98+
};
99+
100+
inline bool operator==(const Address& lhs, const Address& rhs) {
101+
return lhs.bytes == rhs.bytes;
102+
}
103+
104+
} // namespace TW::Filecoin

0 commit comments

Comments
 (0)