Skip to content

Commit b0baaf3

Browse files
authored
Filecoin support (#811)
1 parent d0e8f1c commit b0baaf3

File tree

22 files changed

+900
-0
lines changed

22 files changed

+900
-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("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", address)
8585
NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address)
86+
FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address)
8687
}
8788
}

coins.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,5 +1330,28 @@
13301330
"clientPublic": "http://seed1.ngd.network:10332",
13311331
"clientDocs": "https://neo.org/eco"
13321332
}
1333+
},
1334+
{
1335+
"id": "filecoin",
1336+
"name": "Filecoin",
1337+
"symbol": "FIL",
1338+
"decimals": 18,
1339+
"blockchain": "Filecoin",
1340+
"derivationPath": "m/44'/461'/0'/0/0",
1341+
"curve": "secp256k1",
1342+
"publicKeyType": "secp256k1Extended",
1343+
"explorer": {
1344+
"url": "https://filscan.io",
1345+
"txPath": "/#/message/detail?cid=",
1346+
"accountPath": "/#/address/detail?address=",
1347+
"sampleTx": "bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe",
1348+
"sampleAccount": "t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i"
1349+
},
1350+
"info": {
1351+
"url": "https://filecoin.io/",
1352+
"client": "https://github.com/filecoin-project/lotus",
1353+
"clientPublic": "",
1354+
"clientDocs": "https://docs.lotu.sh"
1355+
}
13331356
}
13341357
]

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

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 <string>
14+
#include <vector>
15+
16+
namespace TW::Filecoin {
17+
18+
class Address {
19+
public:
20+
enum class Type : uint8_t {
21+
ID = 0,
22+
SECP256K1 = 1,
23+
ACTOR = 2,
24+
BLS = 3,
25+
Invalid,
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 getType(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 Type getType(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 Type::Invalid;
69+
}
70+
}
71+
72+
/// Attempts to get the type by ASCII.
73+
static Type parseType(char c) {
74+
if (c >= '0' && c <= '3') {
75+
return static_cast<Type>(c - '0');
76+
} else {
77+
return Type::Invalid;
78+
}
79+
}
80+
81+
/// Returns ASCII character of type
82+
static char typeAscii(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 payloadSize(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)