Skip to content

Commit 745437f

Browse files
authored
Adds API to import encoded private keys (#4357)
* Adds API to import encoded private keys * Adds solana base58 decoding as well * Supports 64 byte solana key import and moves Crc * Adds API to retrieve encoded key * Adds APIs and Tests based on the discussion * Addresses review comments * Addresses review comments * Updates Android test
1 parent a75afa8 commit 745437f

File tree

21 files changed

+637
-6
lines changed

21 files changed

+637
-6
lines changed

android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,36 @@ class TestKeyStore {
9191
val privateKey = newKeyStore.decryptPrivateKey("".toByteArray())
9292
assertNull(privateKey)
9393
}
94+
95+
@Test
96+
fun testImportKeyEncodedEthereum() {
97+
val privateKeyHex = "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"
98+
val password = "password".toByteArray()
99+
val key = StoredKey.importPrivateKeyEncoded(privateKeyHex, "name", password, CoinType.ETHEREUM)
100+
val json = key.exportJSON()
101+
102+
val keyStore = StoredKey.importJSON(json)
103+
val storedEncoded = keyStore.decryptPrivateKeyEncoded(password)
104+
105+
assertTrue(keyStore.hasPrivateKeyEncoded())
106+
assertNotNull(keyStore)
107+
assertNotNull(storedEncoded)
108+
assertEquals(privateKeyHex, storedEncoded)
109+
}
110+
111+
@Test
112+
fun testImportKeyEncodedSolana() {
113+
val privateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"
114+
val password = "password".toByteArray()
115+
val key = StoredKey.importPrivateKeyEncoded(privateKeyBase58, "name", password, CoinType.SOLANA)
116+
val json = key.exportJSON()
117+
118+
val keyStore = StoredKey.importJSON(json)
119+
val storedEncoded = keyStore.decryptPrivateKeyEncoded(password)
120+
121+
assertTrue(keyStore.hasPrivateKeyEncoded())
122+
assertNotNull(keyStore)
123+
assertNotNull(storedEncoded)
124+
assertEquals(privateKeyBase58, storedEncoded)
125+
}
94126
}

include/TrustWalletCore/TWStoredKey.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,28 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva
5151
TW_EXPORT_STATIC_METHOD
5252
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);
5353

54+
/// Imports an encoded private key.
55+
///
56+
/// \param privateKey Non-null encoded private key
57+
/// \param password Non-null block of data, password of the stored key
58+
/// \param coin the coin type
59+
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
60+
/// \return Nullptr if the key can't be imported, the stored key otherwise
61+
TW_EXPORT_STATIC_METHOD
62+
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin);
63+
64+
/// Imports an encoded private key.
65+
///
66+
/// \param privateKey Non-null encoded private key
67+
/// \param name The name of the stored key to import as a non-null string
68+
/// \param password Non-null block of data, password of the stored key
69+
/// \param coin the coin type
70+
/// \param encryption cipher encryption mode
71+
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
72+
/// \return Nullptr if the key can't be imported, the stored key otherwise
73+
TW_EXPORT_STATIC_METHOD
74+
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);
75+
5476
/// Imports an HD wallet.
5577
///
5678
/// \param mnemonic Non-null bip39 mnemonic
@@ -253,6 +275,21 @@ bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path)
253275
TW_EXPORT_METHOD
254276
TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);
255277

278+
/// Decrypts the encoded private key.
279+
///
280+
/// \param key Non-null pointer to a stored key
281+
/// \param password Non-null block of data, password of the stored key
282+
/// \return Decrypted encoded private key as a string if success, null pointer otherwise
283+
TW_EXPORT_METHOD
284+
TWString* _Nullable TWStoredKeyDecryptPrivateKeyEncoded(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);
285+
286+
/// Whether the private key is encoded.
287+
///
288+
/// \param key Non-null pointer to a stored key
289+
/// \return true if the private key is encoded, false otherwise
290+
TW_EXPORT_PROPERTY
291+
bool TWStoredKeyHasPrivateKeyEncoded(struct TWStoredKey* _Nonnull key);
292+
256293
/// Decrypts the mnemonic phrase.
257294
///
258295
/// \param key Non-null pointer to a stored key

src/Coin.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@ std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDer
300300
return dispatcher->deriveAddress(coin, publicKey, derivation, addressPrefix);
301301
}
302302

303+
PrivateKey TW::decodePrivateKey(TWCoinType coin, const std::string& privateKey) {
304+
auto const* dispatcher = coinDispatcher(coin);
305+
assert(dispatcher != nullptr);
306+
return dispatcher->decodePrivateKey(coin, privateKey);
307+
}
308+
303309
Data TW::addressToData(TWCoinType coin, const std::string& address) {
304310
const auto* dispatcher = coinDispatcher(coin);
305311
assert(dispatcher != nullptr);

src/Coin.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDeriv
7979
/// Derives the address for a particular coin from the public key, with given derivation and addressPrefix.
8080
std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation = TWDerivationDefault, const PrefixVariant& addressPrefix = std::monostate());
8181

82+
/// Decodes a private key for a particular coin.
83+
PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey);
84+
8285
/// Returns the binary representation of a string address
8386
Data addressToData(TWCoinType coin, const std::string& address);
8487

src/CoinEntry.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin) {
3030
return TW::p2pkhPrefix(coin);
3131
}
3232

33+
PrivateKey CoinEntry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const {
34+
auto data = parse_hex(privateKey);
35+
return PrivateKey(data, TW::curve(coin));
36+
}
37+
3338
} // namespace TW

src/CoinEntry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class CoinEntry {
6868
virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; }
6969
// Optional method for compiling a transaction with externally-supplied signatures & pubkeys.
7070
virtual void compile([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector<Data>& signatures, [[maybe_unused]] const std::vector<PublicKey>& publicKeys, [[maybe_unused]] Data& dataOut) const {}
71+
// Optional method for decoding a private key. Could throw an exception if the encoded private key is invalid.
72+
virtual PrivateKey decodePrivateKey([[maybe_unused]] TWCoinType coin, const std::string& privateKey) const;
7173
};
7274

7375
// In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement:

src/Crc.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,20 @@ uint32_t Crc::crc32(const Data& data) {
3838
}
3939
return ~c;
4040
}
41+
42+
Data Crc::crc16_xmodem(const Data& data) {
43+
uint16_t crc16 = 0x0;
44+
45+
for (size_t i = 0; i < data.size(); i++) {
46+
uint8_t byte = data[i];
47+
uint8_t lookupIndex = (crc16 >> 8) ^ byte;
48+
crc16 = static_cast<uint16_t>((crc16 << 8) ^ crc16_xmodem_table[lookupIndex]);
49+
crc16 &= 0xffff;
50+
}
51+
52+
Data checksum(2);
53+
checksum[0] = crc16 & 0xff;
54+
checksum[1] = (crc16 >> 8) & 0xff;
55+
return checksum;
56+
}
57+

src/Crc.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ uint16_t crc16(uint8_t* bytes, uint32_t length);
1515

1616
uint32_t crc32(const TW::Data& data);
1717

18+
/// CRC16-XModem implementation compatible with the Stellar version
19+
// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/strkey.js#L353
20+
// Computes the CRC16-XModem checksum of `payload` in little-endian order
21+
Data crc16_xmodem(const Data& data);
22+
1823
// Table taken from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code)
1924
// This table is used to speed up the crc calculation.
2025
static constexpr uint32_t crc32_table[] = {
@@ -61,4 +66,37 @@ static constexpr uint32_t crc32_table[] = {
6166
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
6267
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
6368
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
69+
70+
// CRC16-XModem lookup table
71+
static const uint16_t crc16_xmodem_table[256] = {
72+
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108,
73+
0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210,
74+
0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b,
75+
0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
76+
0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee,
77+
0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,
78+
0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d,
79+
0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
80+
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5,
81+
0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
82+
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4,
83+
0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
84+
0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13,
85+
0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
86+
0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e,
87+
0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
88+
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1,
89+
0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb,
90+
0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0,
91+
0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
92+
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657,
93+
0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,
94+
0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882,
95+
0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
96+
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
97+
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,
98+
0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d,
99+
0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
100+
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
101+
};
64102
} // namespace TW::Crc

src/Keystore/StoredKey.cpp

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,27 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na
6767
return key;
6868
}
6969

70-
StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption)
70+
StoredKey StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption) {
71+
const auto curve = TW::curve(coin);
72+
const auto privateKey = TW::decodePrivateKey(coin, encodedPrivateKey);
73+
StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKey.bytes, TWStoredKeyEncryptionLevelDefault, encryption, encodedPrivateKey);
74+
const auto derivationPath = TW::derivationPath(coin);
75+
const auto pubKeyType = TW::publicKeyType(coin);
76+
const auto pubKey = privateKey.getPublicKey(pubKeyType);
77+
const auto address = TW::deriveAddress(coin, privateKey);
78+
key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), "");
79+
return key;
80+
}
81+
82+
StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption, const std::optional<std::string>& encodedStr)
7183
: type(type), id(), name(std::move(name)), accounts() {
7284
const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption);
7385
payload = EncryptedPayload(password, data, encryptionParams);
74-
86+
if (encodedStr) {
87+
const auto bytes = reinterpret_cast<const uint8_t*>(encodedStr->c_str());
88+
const auto encodedData = Data(bytes, bytes + encodedStr->size());
89+
encodedPayload = EncryptedPayload(password, encodedData, encryptionParams);
90+
}
7591
const char* uuid_ptr = Rust::tw_uuid_random();
7692
id = std::make_optional<std::string>(uuid_ptr);
7793
Rust::free_string(uuid_ptr);
@@ -310,6 +326,16 @@ bool StoredKey::updateAddress(TWCoinType coin) {
310326
return addressUpdated;
311327
}
312328

329+
const std::string StoredKey::decryptPrivateKeyEncoded(const Data& password) const {
330+
if (encodedPayload) {
331+
auto data = encodedPayload->decrypt(password);
332+
return std::string(reinterpret_cast<const char*>(data.data()), data.size());
333+
} else {
334+
auto data = payload.decrypt(password);
335+
return TW::hex(data);
336+
}
337+
}
338+
313339
// -----------------
314340
// Encoding/Decoding
315341
// -----------------
@@ -327,6 +353,7 @@ static const auto type = "type";
327353
static const auto name = "name";
328354
static const auto id = "id";
329355
static const auto crypto = "crypto";
356+
static const auto encodedCrypto = "encodedCrypto";
330357
static const auto activeAccounts = "activeAccounts";
331358
static const auto version = "version";
332359
static const auto coin = "coin";
@@ -367,6 +394,12 @@ void StoredKey::loadJson(const nlohmann::json& json) {
367394
throw DecryptionError::invalidKeyFile;
368395
}
369396

397+
if (json.count(CodingKeys::SK::encodedCrypto) != 0) {
398+
encodedPayload = EncryptedPayload(json[CodingKeys::SK::encodedCrypto]);
399+
} else {
400+
encodedPayload = std::nullopt;
401+
}
402+
370403
if (json.count(CodingKeys::SK::activeAccounts) != 0 &&
371404
json[CodingKeys::SK::activeAccounts].is_array()) {
372405
for (auto& accountJSON : json[CodingKeys::SK::activeAccounts]) {
@@ -404,6 +437,9 @@ nlohmann::json StoredKey::json() const {
404437

405438
j[CodingKeys::SK::name] = name;
406439
j[CodingKeys::SK::crypto] = payload.json();
440+
if (encodedPayload) {
441+
j[CodingKeys::SK::encodedCrypto] = encodedPayload->json();
442+
}
407443

408444
nlohmann::json accountsJSON = nlohmann::json::array();
409445
for (const auto& account : accounts) {

src/Keystore/StoredKey.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class StoredKey {
3939
/// Encrypted payload.
4040
EncryptedPayload payload;
4141

42+
/// Optional encoded payload. Used when an encoded private key is imported.
43+
std::optional<EncryptedPayload> encodedPayload;
44+
4245
/// Active accounts. Address should be unique.
4346
std::vector<Account> accounts;
4447

@@ -62,6 +65,10 @@ class StoredKey {
6265
/// @throws std::invalid_argument if privateKeyData is not a valid private key
6366
static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr);
6467

68+
/// Create a new StoredKey, with the given name and encoded private key, and also add the default address for the given coin..
69+
/// @throws std::invalid_argument if encodedPrivateKey is not a valid private key
70+
static StoredKey createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr);
71+
6572
/// Create a StoredKey from a JSON object.
6673
static StoredKey createWithJson(const nlohmann::json& json);
6774

@@ -150,14 +157,28 @@ class StoredKey {
150157
/// In case of multiple accounts, all of them will be updated.
151158
bool updateAddress(TWCoinType coin);
152159

160+
/// Decrypts the encoded private key.
161+
///
162+
/// \returns the decoded private key.
163+
/// \throws DecryptionError
164+
const std::string decryptPrivateKeyEncoded(const Data& password) const;
165+
153166
private:
154167
/// Default constructor, private
155168
StoredKey() : type(StoredKeyType::mnemonicPhrase) {}
156169

157170
/// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data.
158171
/// This constructor will encrypt the provided data with default encryption
159172
/// parameters.
160-
StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr);
173+
StoredKey(
174+
StoredKeyType type,
175+
std::string name,
176+
const Data& password,
177+
const Data& data,
178+
TWStoredKeyEncryptionLevel encryptionLevel,
179+
TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr,
180+
const std::optional<std::string>& encodedStr = std::nullopt
181+
);
161182

162183
/// Find default account for coin, if exists. If multiple exist, default is returned.
163184
/// Optional wallet is needed to derive default address

src/Solana/Entry.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// Copyright © 2017 Trust Wallet.
44

55
#include "Entry.h"
6-
6+
#include "Base58.h"
7+
#include "Coin.h"
8+
#include "HexCoding.h"
79
#include "proto/Solana.pb.h"
810

911
using namespace TW;
@@ -20,4 +22,23 @@ string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key
2022
);
2123
}
2224

25+
PrivateKey Entry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const {
26+
auto data = Base58::decode(privateKey);
27+
if (data.size() == 64) {
28+
const auto privateKeyData = subData(data, 0, 32);
29+
const auto publicKeyData = subData(data, 32, 32);
30+
auto privKey = PrivateKey(privateKeyData, TW::curve(coin));
31+
auto publicKey = privKey.getPublicKey(TWPublicKeyType::TWPublicKeyTypeED25519);
32+
if (publicKey.bytes != publicKeyData) {
33+
throw std::invalid_argument("Invalid private key");
34+
}
35+
return privKey;
36+
} else if (data.size() == 32) {
37+
return PrivateKey(data, TW::curve(coin));
38+
} else {
39+
auto hexData = parse_hex(privateKey);
40+
return PrivateKey(hexData, TW::curve(coin));
41+
}
42+
}
43+
2344
} // namespace TW::Solana

src/Solana/Entry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Entry final : public Rust::RustCoinEntryWithSignJSON {
1515
public:
1616
bool supportsJSONSigning() const override { return true; }
1717
std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override;
18+
PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey) const override;
1819
};
1920

2021
} // namespace TW::Solana

0 commit comments

Comments
 (0)