Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,36 @@ class TestKeyStore {
val privateKey = newKeyStore.decryptPrivateKey("".toByteArray())
assertNull(privateKey)
}

@Test
fun testImportKeyEncodedEthereum() {
val privateKeyHex = "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"
val password = "password".toByteArray()
val key = StoredKey.importPrivateKeyEncoded(privateKeyHex, "name", password, CoinType.ETHEREUM)
val json = key.exportJSON()

val keyStore = StoredKey.importJSON(json)
val storedEncoded = keyStore.decryptPrivateKeyEncoded(password)

assertTrue(keyStore.hasPrivateKeyEncoded())
assertNotNull(keyStore)
assertNotNull(storedEncoded)
assertEquals(privateKeyHex, storedEncoded)
}

@Test
fun testImportKeyEncodedSolana() {
val privateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"
val password = "password".toByteArray()
val key = StoredKey.importPrivateKeyEncoded(privateKeyBase58, "name", password, CoinType.SOLANA)
val json = key.exportJSON()

val keyStore = StoredKey.importJSON(json)
val storedEncoded = keyStore.decryptPrivateKeyEncoded(password)

assertTrue(keyStore.hasPrivateKeyEncoded())
assertNotNull(keyStore)
assertNotNull(storedEncoded)
assertEquals(privateKeyBase58, storedEncoded)
}
}
37 changes: 37 additions & 0 deletions include/TrustWalletCore/TWStoredKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);

/// Imports an encoded private key.
///
/// \param privateKey Non-null encoded private key
/// \param password Non-null block of data, password of the stored key
/// \param coin the coin type
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return Nullptr if the key can't be imported, the stored key otherwise
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin);

/// Imports an encoded private key.
///
/// \param privateKey Non-null encoded private key
/// \param name The name of the stored key to import as a non-null string
/// \param password Non-null block of data, password of the stored key
/// \param coin the coin type
/// \param encryption cipher encryption mode
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return Nullptr if the key can't be imported, the stored key otherwise
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);

/// Imports an HD wallet.
///
/// \param mnemonic Non-null bip39 mnemonic
Expand Down Expand Up @@ -253,6 +275,21 @@ bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path)
TW_EXPORT_METHOD
TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);

/// Decrypts the encoded private key.
///
/// \param key Non-null pointer to a stored key
/// \param password Non-null block of data, password of the stored key
/// \return Decrypted encoded private key as a string if success, null pointer otherwise
TW_EXPORT_METHOD
TWString* _Nullable TWStoredKeyDecryptPrivateKeyEncoded(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);

/// Whether the private key is encoded.
///
/// \param key Non-null pointer to a stored key
/// \return true if the private key is encoded, false otherwise
TW_EXPORT_PROPERTY
bool TWStoredKeyHasPrivateKeyEncoded(struct TWStoredKey* _Nonnull key);

/// Decrypts the mnemonic phrase.
///
/// \param key Non-null pointer to a stored key
Expand Down
6 changes: 6 additions & 0 deletions src/Coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDer
return dispatcher->deriveAddress(coin, publicKey, derivation, addressPrefix);
}

PrivateKey TW::decodePrivateKey(TWCoinType coin, const std::string& privateKey) {
auto const* dispatcher = coinDispatcher(coin);
assert(dispatcher != nullptr);
return dispatcher->decodePrivateKey(coin, privateKey);
}

Data TW::addressToData(TWCoinType coin, const std::string& address) {
const auto* dispatcher = coinDispatcher(coin);
assert(dispatcher != nullptr);
Expand Down
3 changes: 3 additions & 0 deletions src/Coin.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDeriv
/// Derives the address for a particular coin from the public key, with given derivation and addressPrefix.
std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation = TWDerivationDefault, const PrefixVariant& addressPrefix = std::monostate());

/// Decodes a private key for a particular coin.
PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey);

/// Returns the binary representation of a string address
Data addressToData(TWCoinType coin, const std::string& address);

Expand Down
5 changes: 5 additions & 0 deletions src/CoinEntry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin) {
return TW::p2pkhPrefix(coin);
}

PrivateKey CoinEntry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const {
auto data = parse_hex(privateKey);
return PrivateKey(data, TW::curve(coin));
}

} // namespace TW
2 changes: 2 additions & 0 deletions src/CoinEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class CoinEntry {
virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; }
// Optional method for compiling a transaction with externally-supplied signatures & pubkeys.
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 {}
// Optional method for decoding a private key. Could throw an exception if the encoded private key is invalid.
virtual PrivateKey decodePrivateKey([[maybe_unused]] TWCoinType coin, const std::string& privateKey) const;
};

// In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement:
Expand Down
17 changes: 17 additions & 0 deletions src/Crc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,20 @@ uint32_t Crc::crc32(const Data& data) {
}
return ~c;
}

Data Crc::crc16_xmodem(const Data& data) {
uint16_t crc16 = 0x0;

for (size_t i = 0; i < data.size(); i++) {
uint8_t byte = data[i];
uint8_t lookupIndex = (crc16 >> 8) ^ byte;
crc16 = static_cast<uint16_t>((crc16 << 8) ^ crc16_xmodem_table[lookupIndex]);
crc16 &= 0xffff;
}

Data checksum(2);
checksum[0] = crc16 & 0xff;
checksum[1] = (crc16 >> 8) & 0xff;
return checksum;
}

38 changes: 38 additions & 0 deletions src/Crc.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ uint16_t crc16(uint8_t* bytes, uint32_t length);

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

/// CRC16-XModem implementation compatible with the Stellar version
// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/strkey.js#L353
// Computes the CRC16-XModem checksum of `payload` in little-endian order
Data crc16_xmodem(const Data& data);

// Table taken from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code)
// This table is used to speed up the crc calculation.
static constexpr uint32_t crc32_table[] = {
Expand Down Expand Up @@ -61,4 +66,37 @@ static constexpr uint32_t crc32_table[] = {
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};

// CRC16-XModem lookup table
static const uint16_t crc16_xmodem_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108,
0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210,
0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b,
0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee,
0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,
0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d,
0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5,
0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4,
0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13,
0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e,
0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1,
0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb,
0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0,
0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657,
0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,
0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882,
0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,
0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d,
0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
} // namespace TW::Crc
40 changes: 38 additions & 2 deletions src/Keystore/StoredKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,27 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na
return key;
}

StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption)
StoredKey StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption) {
const auto curve = TW::curve(coin);
const auto privateKey = TW::decodePrivateKey(coin, encodedPrivateKey);
StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKey.bytes, TWStoredKeyEncryptionLevelDefault, encryption, encodedPrivateKey);
const auto derivationPath = TW::derivationPath(coin);
const auto pubKeyType = TW::publicKeyType(coin);
const auto pubKey = privateKey.getPublicKey(pubKeyType);
const auto address = TW::deriveAddress(coin, privateKey);
key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), "");
return key;
}

StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption, const std::optional<std::string>& encodedStr)
: type(type), id(), name(std::move(name)), accounts() {
const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption);
payload = EncryptedPayload(password, data, encryptionParams);

if (encodedStr) {
const auto bytes = reinterpret_cast<const uint8_t*>(encodedStr->c_str());
const auto encodedData = Data(bytes, bytes + encodedStr->size());
encodedPayload = EncryptedPayload(password, encodedData, encryptionParams);
}
const char* uuid_ptr = Rust::tw_uuid_random();
id = std::make_optional<std::string>(uuid_ptr);
Rust::free_string(uuid_ptr);
Expand Down Expand Up @@ -310,6 +326,16 @@ bool StoredKey::updateAddress(TWCoinType coin) {
return addressUpdated;
}

const std::string StoredKey::decryptPrivateKeyEncoded(const Data& password) const {
if (encodedPayload) {
auto data = encodedPayload->decrypt(password);
return std::string(reinterpret_cast<const char*>(data.data()), data.size());
} else {
auto data = payload.decrypt(password);
return TW::hex(data);
}
}

// -----------------
// Encoding/Decoding
// -----------------
Expand All @@ -327,6 +353,7 @@ static const auto type = "type";
static const auto name = "name";
static const auto id = "id";
static const auto crypto = "crypto";
static const auto encodedCrypto = "encodedCrypto";
static const auto activeAccounts = "activeAccounts";
static const auto version = "version";
static const auto coin = "coin";
Expand Down Expand Up @@ -367,6 +394,12 @@ void StoredKey::loadJson(const nlohmann::json& json) {
throw DecryptionError::invalidKeyFile;
}

if (json.count(CodingKeys::SK::encodedCrypto) != 0) {
encodedPayload = EncryptedPayload(json[CodingKeys::SK::encodedCrypto]);
} else {
encodedPayload = std::nullopt;
}

if (json.count(CodingKeys::SK::activeAccounts) != 0 &&
json[CodingKeys::SK::activeAccounts].is_array()) {
for (auto& accountJSON : json[CodingKeys::SK::activeAccounts]) {
Expand Down Expand Up @@ -404,6 +437,9 @@ nlohmann::json StoredKey::json() const {

j[CodingKeys::SK::name] = name;
j[CodingKeys::SK::crypto] = payload.json();
if (encodedPayload) {
j[CodingKeys::SK::encodedCrypto] = encodedPayload->json();
}

nlohmann::json accountsJSON = nlohmann::json::array();
for (const auto& account : accounts) {
Expand Down
23 changes: 22 additions & 1 deletion src/Keystore/StoredKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class StoredKey {
/// Encrypted payload.
EncryptedPayload payload;

/// Optional encoded payload. Used when an encoded private key is imported.
std::optional<EncryptedPayload> encodedPayload;

/// Active accounts. Address should be unique.
std::vector<Account> accounts;

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

/// Create a new StoredKey, with the given name and encoded private key, and also add the default address for the given coin..
/// @throws std::invalid_argument if encodedPrivateKey is not a valid private key
static StoredKey createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr);

/// Create a StoredKey from a JSON object.
static StoredKey createWithJson(const nlohmann::json& json);

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

/// Decrypts the encoded private key.
///
/// \returns the decoded private key.
/// \throws DecryptionError
const std::string decryptPrivateKeyEncoded(const Data& password) const;

private:
/// Default constructor, private
StoredKey() : type(StoredKeyType::mnemonicPhrase) {}

/// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data.
/// This constructor will encrypt the provided data with default encryption
/// parameters.
StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr);
StoredKey(
StoredKeyType type,
std::string name,
const Data& password,
const Data& data,
TWStoredKeyEncryptionLevel encryptionLevel,
TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr,
const std::optional<std::string>& encodedStr = std::nullopt
);

/// Find default account for coin, if exists. If multiple exist, default is returned.
/// Optional wallet is needed to derive default address
Expand Down
23 changes: 22 additions & 1 deletion src/Solana/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// Copyright © 2017 Trust Wallet.

#include "Entry.h"

#include "Base58.h"
#include "Coin.h"
#include "HexCoding.h"
#include "proto/Solana.pb.h"

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

PrivateKey Entry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const {
auto data = Base58::decode(privateKey);
if (data.size() == 64) {
const auto privateKeyData = subData(data, 0, 32);
const auto publicKeyData = subData(data, 32, 32);
auto privKey = PrivateKey(privateKeyData, TW::curve(coin));
auto publicKey = privKey.getPublicKey(TWPublicKeyType::TWPublicKeyTypeED25519);
if (publicKey.bytes != publicKeyData) {
throw std::invalid_argument("Invalid private key");
}
return privKey;
} else if (data.size() == 32) {
return PrivateKey(data, TW::curve(coin));
} else {
auto hexData = parse_hex(privateKey);
return PrivateKey(hexData, TW::curve(coin));
}
}

} // namespace TW::Solana
1 change: 1 addition & 0 deletions src/Solana/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Entry final : public Rust::RustCoinEntryWithSignJSON {
public:
bool supportsJSONSigning() const override { return true; }
std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override;
PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey) const override;
};

} // namespace TW::Solana
Loading
Loading