1
// Copyright © 2017-2020 Trust Wallet.
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 "StoredKey.h"
8

9
#include "Coin.h"
10
#include "HDWallet.h"
11
#include "PrivateKey.h"
12

13
#define BOOST_UUID_RANDOM_PROVIDER_FORCE_POSIX 1
14

15
#include <boost/lexical_cast.hpp>
16
#include <boost/uuid/uuid.hpp>
17
#include <boost/uuid/uuid_generators.hpp>
18
#include <boost/uuid/uuid_io.hpp>
19
#include <nlohmann/json.hpp>
20

21
#include <fstream>
22
#include <iostream>
23
#include <sstream>
24
#include <stdexcept>
25
#include <cassert>
26

27
using namespace TW;
28
using namespace TW::Keystore;
29

30 1
StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic) {
31 1
    if (!HDWallet::isValid(mnemonic)) {
32 1
        throw std::invalid_argument("Invalid mnemonic");
33
    }
34
    
35 1
    Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end());
36 1
    StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData);
37 1
    return key;
38
}
39

40 1
StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password) {
41 1
    const auto wallet = TW::HDWallet(128, "");
42 1
    const auto& mnemonic = wallet.mnemonic;
43 1
    assert(HDWallet::isValid(mnemonic));
44 1
    Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end());
45 1
    StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData);
46 1
    return key;
47
}
48

49 1
StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin) {
50 1
    StoredKey key = createWithMnemonic(name, password, mnemonic);
51

52 1
    const auto wallet = HDWallet(mnemonic, "");
53 1
    const auto derivationPath = TW::derivationPath(coin);
54 1
    const auto address = TW::deriveAddress(coin, wallet.getKey(coin, derivationPath));
55 1
    const auto extendedKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TW::xpubVersion(coin));
56 1
    key.accounts.emplace_back(address, coin, derivationPath, extendedKey);
57

58 1
    return key;
59
}
60

61 1
StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData) {
62 1
    StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKeyData);
63 1
    return key;
64
}
65

66 1
StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData) {
67 1
    const auto curve = TW::curve(coin);
68 1
    if (!PrivateKey::isValid(privateKeyData, curve)) {
69 1
        throw std::invalid_argument("Invalid private key data");
70
    }
71

72 1
    StoredKey key = createWithPrivateKey(name, password, privateKeyData);
73

74 1
    const auto derivationPath = TW::derivationPath(coin);
75 1
    const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData));
76 1
    key.accounts.emplace_back(address, coin, derivationPath);
77

78 1
    return key;
79
}
80

81 1
StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data)
82 1
    : type(type), id(), name(std::move(name)), payload(password, data), accounts() {
83 1
    boost::uuids::random_generator gen;
84 1
    id = boost::lexical_cast<std::string>(gen());
85
}
86

87 1
const HDWallet StoredKey::wallet(const Data& password) const {
88 1
    if (type != StoredKeyType::mnemonicPhrase) {
89 1
        throw std::invalid_argument("Invalid account requested.");
90
    }
91 1
    const auto data = payload.decrypt(password);
92 1
    const auto mnemonic = std::string(reinterpret_cast<const char*>(data.data()), data.size());
93 1
    return HDWallet(mnemonic, "");
94
}
95

96 1
std::optional<const Account> StoredKey::account(TWCoinType coin) const {
97 1
    for (auto& account : accounts) {
98 1
        if (account.coin == coin) {
99 1
            return account;
100
        }
101
    }
102 1
    return std::nullopt;
103
}
104

105 1
std::optional<const Account> StoredKey::account(TWCoinType coin, const HDWallet* wallet) {
106 1
    if (wallet == nullptr) {
107 1
        return account(coin);
108
    }
109 1
    assert(wallet != nullptr);
110

111 1
    for (auto& account : accounts) {
112 1
        if (account.coin == coin) {
113 1
            if (account.address.empty()) {
114 0
                account.address = wallet->deriveAddress(coin);
115
            }
116 1
            return account;
117
        }
118
    }
119

120 1
    const auto derivationPath = TW::derivationPath(coin);
121 1
    const auto address = wallet->deriveAddress(coin);
122
    
123 1
    const auto version = TW::xpubVersion(coin);
124 1
    const auto extendedPublicKey = wallet->getExtendedPublicKey(derivationPath.purpose(), coin, version);
125

126 1
    accounts.emplace_back(address, coin, derivationPath, extendedPublicKey);
127 1
    return accounts.back();
128
}
129

130 1
void StoredKey::addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) {
131 1
    accounts.emplace_back(address, coin, derivationPath, extetndedPublicKey);
132
}
133

134 1
void StoredKey::removeAccount(TWCoinType coin) {
135 1
    accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool {
136 1
        return account.coin == coin;
137 1
    }), accounts.end());
138
}
139

140 1
const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) {
141 1
    switch (type) {
142
    case StoredKeyType::mnemonicPhrase: {
143 1
        const auto wallet = this->wallet(password);
144 1
        const auto account = this->account(coin, &wallet);
145 1
        return wallet.getKey(coin, account->derivationPath);
146
    }
147
    case StoredKeyType::privateKey:
148 1
        return PrivateKey(payload.decrypt(password));
149
    }
150
}
151

152 1
void StoredKey::fixAddresses(const Data& password) {
153 1
    switch (type) {
154
        case StoredKeyType::mnemonicPhrase: {
155 1
                const auto wallet = this->wallet(password);
156 1
                for (auto& account : accounts) {
157 1
                    if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) {
158 1
                        continue;
159
                    }
160 1
                    const auto& derivationPath = account.derivationPath;
161 1
                    const auto key = wallet.getKey(account.coin, derivationPath);
162 1
                    account.address = TW::deriveAddress(account.coin, key);
163
                }
164
            }
165 1
            break;
166

167
        case StoredKeyType::privateKey: {
168 1
                auto key = PrivateKey(payload.decrypt(password));
169 1
                for (auto& account : accounts) {
170 1
                    if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) {
171 1
                        continue;
172
                    }
173 1
                    account.address = TW::deriveAddress(account.coin, key);
174
                }
175
            }
176 1
            break;
177
    }
178
}
179

180

181
// -----------------
182
// Encoding/Decoding
183
// -----------------
184

185 1
StoredKey StoredKey::createWithJson(const nlohmann::json& json) {
186 1
    StoredKey storedKey;
187 1
    storedKey.loadJson(json);
188 1
    return storedKey;
189
}
190

191
namespace CodingKeys {
192
    static const auto address = "address";
193
    static const auto type = "type";
194
    static const auto name = "name";
195
    static const auto id = "id";
196
    static const auto crypto = "crypto";
197
    static const auto activeAccounts = "activeAccounts";
198
    static const auto version = "version";
199
    static const auto coin = "coin";
200
} // namespace CodingKeys
201

202
namespace UppercaseCodingKeys {
203
    static const auto crypto = "Crypto";
204
} // namespace UppercaseCodingKeys
205

206
namespace TypeString {
207
    static const auto privateKey = "private-key";
208
    static const auto mnemonic = "mnemonic";
209
} // namespace TypeString
210

211 1
void StoredKey::loadJson(const nlohmann::json& json) {
212 1
    if (json.count(CodingKeys::type) != 0 &&
213 1
        json[CodingKeys::type].get<std::string>() == TypeString::mnemonic) {
214 1
        type = StoredKeyType::mnemonicPhrase;
215 1
    } else {
216 1
        type = StoredKeyType::privateKey;
217
    }
218

219 1
    if (json.count(CodingKeys::name) != 0) {
220 1
        name = json[CodingKeys::name].get<std::string>();
221
    }
222

223 1
    if (json.count(CodingKeys::id) != 0) {
224 1
        id = json[CodingKeys::id].get<std::string>();
225
    }
226

227 1
    if (json.count(CodingKeys::crypto) != 0) {
228 1
        payload = EncryptionParameters(json[CodingKeys::crypto]);
229 1
    } else if (json.count(UppercaseCodingKeys::crypto) != 0) {
230
        // Workaround for myEtherWallet files
231 1
        payload = EncryptionParameters(json[UppercaseCodingKeys::crypto]);
232 1
    } else {
233 0
        throw DecryptionError::invalidKeyFile;
234
    }
235

236 1
    if (json.count(CodingKeys::activeAccounts) != 0 &&
237 1
        json[CodingKeys::activeAccounts].is_array()) {
238 1
        for (auto& accountJSON : json[CodingKeys::activeAccounts]) {
239 1
            accounts.emplace_back(accountJSON);
240
        }
241
    }
242

243 1
    if (accounts.empty() && json.count(CodingKeys::address) != 0 && json[CodingKeys::address].is_string()) {
244 1
        TWCoinType coin = TWCoinTypeEthereum;
245 1
        if (json.count(CodingKeys::coin) != 0) {
246 1
            coin = json[CodingKeys::coin].get<TWCoinType>();
247
        }
248 1
        auto address = json[CodingKeys::address].get<std::string>();
249 1
        accounts.emplace_back(address, coin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0));
250
    }
251
}
252

253 1
nlohmann::json StoredKey::json() const {
254 1
    nlohmann::json j;
255 1
    j[CodingKeys::version] = 3;
256

257 1
    switch (type) {
258
    case StoredKeyType::privateKey:
259 1
        j[CodingKeys::type] = TypeString::privateKey;
260 1
        break;
261
    case StoredKeyType::mnemonicPhrase:
262 1
        j[CodingKeys::type] = TypeString::mnemonic;
263 1
        break;
264
    }
265

266 1
    if (id) {
267 1
        j[CodingKeys::id] = *id;
268
    }
269

270 1
    j[CodingKeys::name] = name;
271 1
    j[CodingKeys::crypto] = payload.json();
272

273 1
    nlohmann::json accountsJSON = nlohmann::json::array();
274 1
    for (const auto& account : accounts) {
275 1
        accountsJSON.push_back(account.json());
276
    }
277 1
    j[CodingKeys::activeAccounts] = accountsJSON;
278

279 1
    return j;
280
}
281

282
// File operations
283

284 1
void StoredKey::store(const std::string& path) {
285 1
    auto stream = std::ofstream(path);
286 1
    stream << json();
287
}
288

289 1
StoredKey StoredKey::load(const std::string& path) {
290 1
    std::ifstream stream(path);
291 1
    if (!stream.is_open()) {
292 1
        throw std::invalid_argument("Can't open file");
293
    }
294 1
    nlohmann::json j;
295 1
    stream >> j;
296

297 1
    return createWithJson(j);
298
}

Read our documentation on viewing source code .

Loading