925 lines
29 KiB
C++
925 lines
29 KiB
C++
// Copyright (c) 2011-2016 The Cryptonote developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "WalletSerialization.h"
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
|
|
#include "Common/MemoryInputStream.h"
|
|
#include "Common/StdInputStream.h"
|
|
#include "Common/StdOutputStream.h"
|
|
#include "CryptoNoteCore/CryptoNoteSerialization.h"
|
|
#include "CryptoNoteCore/CryptoNoteTools.h"
|
|
|
|
#include "Serialization/BinaryOutputStreamSerializer.h"
|
|
#include "Serialization/BinaryInputStreamSerializer.h"
|
|
#include "Serialization/SerializationOverloads.h"
|
|
|
|
#include "Wallet/WalletErrors.h"
|
|
#include "WalletLegacy/KeysStorage.h"
|
|
#include "WalletLegacy/WalletLegacySerialization.h"
|
|
|
|
using namespace Common;
|
|
using namespace Crypto;
|
|
|
|
namespace {
|
|
|
|
//DO NOT CHANGE IT
|
|
struct WalletRecordDto {
|
|
PublicKey spendPublicKey;
|
|
SecretKey spendSecretKey;
|
|
uint64_t pendingBalance = 0;
|
|
uint64_t actualBalance = 0;
|
|
uint64_t creationTimestamp = 0;
|
|
};
|
|
|
|
//DO NOT CHANGE IT
|
|
struct ObsoleteSpentOutputDto {
|
|
uint64_t amount;
|
|
Hash transactionHash;
|
|
uint32_t outputInTransaction;
|
|
uint64_t walletIndex;
|
|
Crypto::Hash spendingTransactionHash;
|
|
};
|
|
|
|
//DO NOT CHANGE IT
|
|
struct ObsoleteChangeDto {
|
|
Hash txHash;
|
|
uint64_t amount;
|
|
};
|
|
|
|
//DO NOT CHANGE IT
|
|
struct UnlockTransactionJobDto {
|
|
uint32_t blockHeight;
|
|
Hash transactionHash;
|
|
uint64_t walletIndex;
|
|
};
|
|
|
|
//DO NOT CHANGE IT
|
|
struct WalletTransactionDto {
|
|
WalletTransactionDto() {}
|
|
|
|
WalletTransactionDto(const CryptoNote::WalletTransaction& wallet) {
|
|
state = wallet.state;
|
|
timestamp = wallet.timestamp;
|
|
blockHeight = wallet.blockHeight;
|
|
hash = wallet.hash;
|
|
totalAmount = wallet.totalAmount;
|
|
fee = wallet.fee;
|
|
creationTime = wallet.creationTime;
|
|
unlockTime = wallet.unlockTime;
|
|
extra = wallet.extra;
|
|
}
|
|
|
|
CryptoNote::WalletTransactionState state;
|
|
uint64_t timestamp;
|
|
uint32_t blockHeight;
|
|
Hash hash;
|
|
int64_t totalAmount;
|
|
uint64_t fee;
|
|
uint64_t creationTime;
|
|
uint64_t unlockTime;
|
|
std::string extra;
|
|
};
|
|
|
|
//DO NOT CHANGE IT
|
|
struct WalletTransferDto {
|
|
WalletTransferDto(uint32_t version) : version(version) {}
|
|
WalletTransferDto(const CryptoNote::WalletTransfer& tr, uint32_t version) : WalletTransferDto(version) {
|
|
address = tr.address;
|
|
amount = tr.amount;
|
|
type = static_cast<uint8_t>(tr.type);
|
|
}
|
|
|
|
std::string address;
|
|
uint64_t amount;
|
|
uint8_t type;
|
|
|
|
uint32_t version;
|
|
};
|
|
|
|
void serialize(WalletRecordDto& value, CryptoNote::ISerializer& serializer) {
|
|
serializer(value.spendPublicKey, "spend_public_key");
|
|
serializer(value.spendSecretKey, "spend_secret_key");
|
|
serializer(value.pendingBalance, "pending_balance");
|
|
serializer(value.actualBalance, "actual_balance");
|
|
serializer(value.creationTimestamp, "creation_timestamp");
|
|
}
|
|
|
|
void serialize(ObsoleteSpentOutputDto& value, CryptoNote::ISerializer& serializer) {
|
|
serializer(value.amount, "amount");
|
|
serializer(value.transactionHash, "transaction_hash");
|
|
serializer(value.outputInTransaction, "output_in_transaction");
|
|
serializer(value.walletIndex, "wallet_index");
|
|
serializer(value.spendingTransactionHash, "spending_transaction_hash");
|
|
}
|
|
|
|
void serialize(ObsoleteChangeDto& value, CryptoNote::ISerializer& serializer) {
|
|
serializer(value.txHash, "transaction_hash");
|
|
serializer(value.amount, "amount");
|
|
}
|
|
|
|
void serialize(UnlockTransactionJobDto& value, CryptoNote::ISerializer& serializer) {
|
|
serializer(value.blockHeight, "block_height");
|
|
serializer(value.transactionHash, "transaction_hash");
|
|
serializer(value.walletIndex, "wallet_index");
|
|
}
|
|
|
|
void serialize(WalletTransactionDto& value, CryptoNote::ISerializer& serializer) {
|
|
typedef std::underlying_type<CryptoNote::WalletTransactionState>::type StateType;
|
|
|
|
StateType state = static_cast<StateType>(value.state);
|
|
serializer(state, "state");
|
|
value.state = static_cast<CryptoNote::WalletTransactionState>(state);
|
|
|
|
serializer(value.timestamp, "timestamp");
|
|
CryptoNote::serializeBlockHeight(serializer, value.blockHeight, "block_height");
|
|
serializer(value.hash, "hash");
|
|
serializer(value.totalAmount, "total_amount");
|
|
serializer(value.fee, "fee");
|
|
serializer(value.creationTime, "creation_time");
|
|
serializer(value.unlockTime, "unlock_time");
|
|
serializer(value.extra, "extra");
|
|
}
|
|
|
|
void serialize(WalletTransferDto& value, CryptoNote::ISerializer& serializer) {
|
|
serializer(value.address, "address");
|
|
serializer(value.amount, "amount");
|
|
|
|
if (value.version > 2) {
|
|
serializer(value.type, "type");
|
|
}
|
|
}
|
|
|
|
template <typename Object>
|
|
std::string serialize(Object& obj, const std::string& name) {
|
|
std::stringstream stream;
|
|
StdOutputStream output(stream);
|
|
CryptoNote::BinaryOutputStreamSerializer s(output);
|
|
|
|
s(obj, Common::StringView(name));
|
|
|
|
stream.flush();
|
|
return stream.str();
|
|
}
|
|
|
|
std::string encrypt(const std::string& plain, CryptoNote::CryptoContext& cryptoContext) {
|
|
std::string cipher;
|
|
cipher.resize(plain.size());
|
|
|
|
Crypto::chacha8(plain.data(), plain.size(), cryptoContext.key, cryptoContext.iv, &cipher[0]);
|
|
|
|
return cipher;
|
|
}
|
|
|
|
void addToStream(const std::string& cipher, const std::string& name, Common::IOutputStream& destination) {
|
|
CryptoNote::BinaryOutputStreamSerializer s(destination);
|
|
s(const_cast<std::string& >(cipher), name);
|
|
}
|
|
|
|
template<typename Object>
|
|
void serializeEncrypted(Object& obj, const std::string& name, CryptoNote::CryptoContext& cryptoContext, Common::IOutputStream& destination) {
|
|
std::string plain = serialize(obj, name);
|
|
std::string cipher = encrypt(plain, cryptoContext);
|
|
|
|
addToStream(cipher, name, destination);
|
|
}
|
|
|
|
std::string readCipher(Common::IInputStream& source, const std::string& name) {
|
|
std::string cipher;
|
|
CryptoNote::BinaryInputStreamSerializer s(source);
|
|
s(cipher, name);
|
|
|
|
return cipher;
|
|
}
|
|
|
|
std::string decrypt(const std::string& cipher, CryptoNote::CryptoContext& cryptoContext) {
|
|
std::string plain;
|
|
plain.resize(cipher.size());
|
|
|
|
Crypto::chacha8(cipher.data(), cipher.size(), cryptoContext.key, cryptoContext.iv, &plain[0]);
|
|
return plain;
|
|
}
|
|
|
|
template<typename Object>
|
|
void deserialize(Object& obj, const std::string& name, const std::string& plain) {
|
|
MemoryInputStream stream(plain.data(), plain.size());
|
|
CryptoNote::BinaryInputStreamSerializer s(stream);
|
|
s(obj, Common::StringView(name));
|
|
}
|
|
|
|
template<typename Object>
|
|
void deserializeEncrypted(Object& obj, const std::string& name, CryptoNote::CryptoContext& cryptoContext, Common::IInputStream& source) {
|
|
std::string cipher = readCipher(source, name);
|
|
std::string plain = decrypt(cipher, cryptoContext);
|
|
|
|
deserialize(obj, name, plain);
|
|
}
|
|
|
|
bool verifyKeys(const SecretKey& sec, const PublicKey& expected_pub) {
|
|
PublicKey pub;
|
|
bool r = Crypto::secret_key_to_public_key(sec, pub);
|
|
|
|
return r && expected_pub == pub;
|
|
}
|
|
|
|
void throwIfKeysMissmatch(const SecretKey& sec, const PublicKey& expected_pub) {
|
|
if (!verifyKeys(sec, expected_pub))
|
|
throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD));
|
|
}
|
|
|
|
CryptoNote::WalletTransaction convert(const CryptoNote::WalletLegacyTransaction& tx) {
|
|
CryptoNote::WalletTransaction mtx;
|
|
|
|
mtx.state = CryptoNote::WalletTransactionState::SUCCEEDED;
|
|
mtx.timestamp = tx.timestamp;
|
|
mtx.blockHeight = tx.blockHeight;
|
|
mtx.hash = tx.hash;
|
|
mtx.totalAmount = tx.totalAmount;
|
|
mtx.fee = tx.fee;
|
|
mtx.creationTime = tx.sentTime;
|
|
mtx.unlockTime = tx.unlockTime;
|
|
mtx.extra = tx.extra;
|
|
mtx.isBase = tx.isCoinbase;
|
|
|
|
return mtx;
|
|
}
|
|
|
|
CryptoNote::WalletTransfer convert(const CryptoNote::WalletLegacyTransfer& tr) {
|
|
CryptoNote::WalletTransfer mtr;
|
|
|
|
mtr.address = tr.address;
|
|
mtr.amount = tr.amount;
|
|
|
|
return mtr;
|
|
}
|
|
|
|
}
|
|
|
|
namespace CryptoNote {
|
|
|
|
const uint32_t WalletSerializer::SERIALIZATION_VERSION = 5;
|
|
|
|
void CryptoContext::incIv() {
|
|
uint64_t * i = reinterpret_cast<uint64_t *>(&iv.data[0]);
|
|
(*i)++;
|
|
}
|
|
|
|
WalletSerializer::WalletSerializer(
|
|
ITransfersObserver& transfersObserver,
|
|
PublicKey& viewPublicKey,
|
|
SecretKey& viewSecretKey,
|
|
uint64_t& actualBalance,
|
|
uint64_t& pendingBalance,
|
|
WalletsContainer& walletsContainer,
|
|
TransfersSyncronizer& synchronizer,
|
|
UnlockTransactionJobs& unlockTransactions,
|
|
WalletTransactions& transactions,
|
|
WalletTransfers& transfers,
|
|
uint32_t transactionSoftLockTime,
|
|
UncommitedTransactions& uncommitedTransactions
|
|
) :
|
|
m_transfersObserver(transfersObserver),
|
|
m_viewPublicKey(viewPublicKey),
|
|
m_viewSecretKey(viewSecretKey),
|
|
m_actualBalance(actualBalance),
|
|
m_pendingBalance(pendingBalance),
|
|
m_walletsContainer(walletsContainer),
|
|
m_synchronizer(synchronizer),
|
|
m_unlockTransactions(unlockTransactions),
|
|
m_transactions(transactions),
|
|
m_transfers(transfers),
|
|
m_transactionSoftLockTime(transactionSoftLockTime),
|
|
uncommitedTransactions(uncommitedTransactions)
|
|
{ }
|
|
|
|
void WalletSerializer::save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache) {
|
|
CryptoContext cryptoContext = generateCryptoContext(password);
|
|
|
|
CryptoNote::BinaryOutputStreamSerializer s(destination);
|
|
s.beginObject("wallet");
|
|
|
|
saveVersion(destination);
|
|
saveIv(destination, cryptoContext.iv);
|
|
|
|
saveKeys(destination, cryptoContext);
|
|
saveWallets(destination, saveCache, cryptoContext);
|
|
saveFlags(saveDetails, saveCache, destination, cryptoContext);
|
|
|
|
if (saveDetails) {
|
|
saveTransactions(destination, cryptoContext);
|
|
saveTransfers(destination, cryptoContext);
|
|
}
|
|
|
|
if (saveCache) {
|
|
saveBalances(destination, saveCache, cryptoContext);
|
|
saveTransfersSynchronizer(destination, cryptoContext);
|
|
saveUnlockTransactionsJobs(destination, cryptoContext);
|
|
saveUncommitedTransactions(destination, cryptoContext);
|
|
}
|
|
|
|
s.endObject();
|
|
}
|
|
|
|
CryptoContext WalletSerializer::generateCryptoContext(const std::string& password) {
|
|
CryptoContext context;
|
|
|
|
Crypto::cn_context c;
|
|
Crypto::generate_chacha8_key(c, password, context.key);
|
|
|
|
context.iv = Crypto::rand<Crypto::chacha8_iv>();
|
|
|
|
return context;
|
|
}
|
|
|
|
void WalletSerializer::saveVersion(Common::IOutputStream& destination) {
|
|
uint32_t version = SERIALIZATION_VERSION;
|
|
|
|
BinaryOutputStreamSerializer s(destination);
|
|
s(version, "version");
|
|
}
|
|
|
|
void WalletSerializer::saveIv(Common::IOutputStream& destination, Crypto::chacha8_iv& iv) {
|
|
BinaryOutputStreamSerializer s(destination);
|
|
s.binary(reinterpret_cast<void *>(&iv.data), sizeof(iv.data), "chacha_iv");
|
|
}
|
|
|
|
void WalletSerializer::saveKeys(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
savePublicKey(destination, cryptoContext);
|
|
saveSecretKey(destination, cryptoContext);
|
|
}
|
|
|
|
void WalletSerializer::savePublicKey(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
serializeEncrypted(m_viewPublicKey, "public_key", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::saveSecretKey(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
serializeEncrypted(m_viewSecretKey, "secret_key", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::saveFlags(bool saveDetails, bool saveCache, Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
serializeEncrypted(saveDetails, "details", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
serializeEncrypted(saveCache, "cache", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::saveWallets(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext) {
|
|
auto& index = m_walletsContainer.get<RandomAccessIndex>();
|
|
|
|
uint64_t count = index.size();
|
|
serializeEncrypted(count, "wallets_count", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
for (const auto& w: index) {
|
|
WalletRecordDto dto;
|
|
dto.spendPublicKey = w.spendPublicKey;
|
|
dto.spendSecretKey = w.spendSecretKey;
|
|
dto.pendingBalance = saveCache ? w.pendingBalance : 0;
|
|
dto.actualBalance = saveCache ? w.actualBalance : 0;
|
|
dto.creationTimestamp = static_cast<uint64_t>(w.creationTimestamp);
|
|
|
|
serializeEncrypted(dto, "", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::saveBalances(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext) {
|
|
uint64_t actual = saveCache ? m_actualBalance : 0;
|
|
uint64_t pending = saveCache ? m_pendingBalance : 0;
|
|
|
|
serializeEncrypted(actual, "actual_balance", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
serializeEncrypted(pending, "pending_balance", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::saveTransfersSynchronizer(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
std::stringstream stream;
|
|
m_synchronizer.save(stream);
|
|
stream.flush();
|
|
|
|
std::string plain = stream.str();
|
|
serializeEncrypted(plain, "transfers_synchronizer", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::saveUnlockTransactionsJobs(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
auto& index = m_unlockTransactions.get<TransactionHashIndex>();
|
|
auto& wallets = m_walletsContainer.get<TransfersContainerIndex>();
|
|
|
|
uint64_t jobsCount = index.size();
|
|
serializeEncrypted(jobsCount, "unlock_transactions_jobs_count", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
for (const auto& j: index) {
|
|
auto containerIt = wallets.find(j.container);
|
|
assert(containerIt != wallets.end());
|
|
|
|
auto rndIt = m_walletsContainer.project<RandomAccessIndex>(containerIt);
|
|
assert(rndIt != m_walletsContainer.get<RandomAccessIndex>().end());
|
|
|
|
uint64_t walletIndex = std::distance(m_walletsContainer.get<RandomAccessIndex>().begin(), rndIt);
|
|
|
|
UnlockTransactionJobDto dto;
|
|
dto.blockHeight = j.blockHeight;
|
|
dto.transactionHash = j.transactionHash;
|
|
dto.walletIndex = walletIndex;
|
|
|
|
serializeEncrypted(dto, "", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::saveUncommitedTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
serializeEncrypted(uncommitedTransactions, "uncommited_transactions", cryptoContext, destination);
|
|
}
|
|
|
|
void WalletSerializer::saveTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
uint64_t count = m_transactions.size();
|
|
serializeEncrypted(count, "transactions_count", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
for (const auto& tx: m_transactions) {
|
|
WalletTransactionDto dto(tx);
|
|
serializeEncrypted(dto, "", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::saveTransfers(Common::IOutputStream& destination, CryptoContext& cryptoContext) {
|
|
uint64_t count = m_transfers.size();
|
|
serializeEncrypted(count, "transfers_count", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
for (const auto& kv: m_transfers) {
|
|
uint64_t txId = kv.first;
|
|
|
|
WalletTransferDto tr(kv.second, SERIALIZATION_VERSION);
|
|
|
|
serializeEncrypted(txId, "transaction_id", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
|
|
serializeEncrypted(tr, "transfer", cryptoContext, destination);
|
|
cryptoContext.incIv();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::load(const std::string& password, Common::IInputStream& source) {
|
|
CryptoNote::BinaryInputStreamSerializer s(source);
|
|
s.beginObject("wallet");
|
|
|
|
uint32_t version = loadVersion(source);
|
|
|
|
if (version > SERIALIZATION_VERSION) {
|
|
throw std::system_error(make_error_code(error::WRONG_VERSION));
|
|
} else if (version != 1) {
|
|
loadWallet(source, password, version);
|
|
} else {
|
|
loadWalletV1(source, password);
|
|
}
|
|
|
|
s.endObject();
|
|
}
|
|
|
|
void WalletSerializer::loadWallet(Common::IInputStream& source, const std::string& password, uint32_t version) {
|
|
CryptoNote::CryptoContext cryptoContext;
|
|
|
|
bool details = false;
|
|
bool cache = false;
|
|
|
|
loadIv(source, cryptoContext.iv);
|
|
generateKey(password, cryptoContext.key);
|
|
|
|
loadKeys(source, cryptoContext);
|
|
checkKeys();
|
|
|
|
loadWallets(source, cryptoContext);
|
|
subscribeWallets();
|
|
|
|
loadFlags(details, cache, source, cryptoContext);
|
|
|
|
if (details) {
|
|
loadTransactions(source, cryptoContext);
|
|
loadTransfers(source, cryptoContext, version);
|
|
}
|
|
|
|
if (version < 5) {
|
|
updateTransfersSign();
|
|
cache = false;
|
|
}
|
|
|
|
if (cache) {
|
|
loadBalances(source, cryptoContext);
|
|
loadTransfersSynchronizer(source, cryptoContext);
|
|
if (version < 5) {
|
|
loadObsoleteSpentOutputs(source, cryptoContext);
|
|
}
|
|
|
|
loadUnlockTransactionsJobs(source, cryptoContext);
|
|
|
|
if (version < 5) {
|
|
loadObsoleteChange(source, cryptoContext);
|
|
}
|
|
|
|
if (version > 3) {
|
|
loadUncommitedTransactions(source, cryptoContext);
|
|
|
|
if (version >= 5) {
|
|
initTransactionPool();
|
|
}
|
|
}
|
|
} else {
|
|
resetCachedBalance();
|
|
}
|
|
|
|
if (details && cache) {
|
|
updateTransactionsBaseStatus();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadWalletV1(Common::IInputStream& source, const std::string& password) {
|
|
CryptoNote::CryptoContext cryptoContext;
|
|
|
|
CryptoNote::BinaryInputStreamSerializer encrypted(source);
|
|
|
|
encrypted(cryptoContext.iv, "iv");
|
|
generateKey(password, cryptoContext.key);
|
|
|
|
std::string cipher;
|
|
encrypted(cipher, "data");
|
|
|
|
std::string plain = decrypt(cipher, cryptoContext);
|
|
|
|
MemoryInputStream decryptedStream(plain.data(), plain.size());
|
|
CryptoNote::BinaryInputStreamSerializer serializer(decryptedStream);
|
|
|
|
loadWalletV1Keys(serializer);
|
|
checkKeys();
|
|
|
|
subscribeWallets();
|
|
|
|
bool detailsSaved;
|
|
serializer(detailsSaved, "has_details");
|
|
|
|
if (detailsSaved) {
|
|
loadWalletV1Details(serializer);
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& serializer) {
|
|
CryptoNote::KeysStorage keys;
|
|
keys.serialize(serializer, "keys");
|
|
|
|
m_viewPublicKey = keys.viewPublicKey;
|
|
m_viewSecretKey = keys.viewSecretKey;
|
|
|
|
WalletRecord wallet;
|
|
wallet.spendPublicKey = keys.spendPublicKey;
|
|
wallet.spendSecretKey = keys.spendSecretKey;
|
|
wallet.actualBalance = 0;
|
|
wallet.pendingBalance = 0;
|
|
wallet.creationTimestamp = static_cast<time_t>(keys.creationTimestamp);
|
|
|
|
m_walletsContainer.get<RandomAccessIndex>().push_back(wallet);
|
|
}
|
|
|
|
void WalletSerializer::loadWalletV1Details(CryptoNote::BinaryInputStreamSerializer& serializer) {
|
|
std::vector<WalletLegacyTransaction> txs;
|
|
std::vector<WalletLegacyTransfer> trs;
|
|
|
|
serializer(txs, "transactions");
|
|
serializer(trs, "transfers");
|
|
|
|
addWalletV1Details(txs, trs);
|
|
}
|
|
|
|
uint32_t WalletSerializer::loadVersion(Common::IInputStream& source) {
|
|
CryptoNote::BinaryInputStreamSerializer s(source);
|
|
|
|
uint32_t version = std::numeric_limits<uint32_t>::max();
|
|
s(version, "version");
|
|
|
|
return version;
|
|
}
|
|
|
|
void WalletSerializer::loadIv(Common::IInputStream& source, Crypto::chacha8_iv& iv) {
|
|
CryptoNote::BinaryInputStreamSerializer s(source);
|
|
|
|
s.binary(static_cast<void *>(&iv.data), sizeof(iv.data), "chacha_iv");
|
|
}
|
|
|
|
void WalletSerializer::generateKey(const std::string& password, Crypto::chacha8_key& key) {
|
|
Crypto::cn_context context;
|
|
Crypto::generate_chacha8_key(context, password, key);
|
|
}
|
|
|
|
void WalletSerializer::loadKeys(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
loadPublicKey(source, cryptoContext);
|
|
loadSecretKey(source, cryptoContext);
|
|
}
|
|
|
|
void WalletSerializer::loadPublicKey(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
deserializeEncrypted(m_viewPublicKey, "public_key", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::loadSecretKey(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
deserializeEncrypted(m_viewSecretKey, "secret_key", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::checkKeys() {
|
|
throwIfKeysMissmatch(m_viewSecretKey, m_viewPublicKey);
|
|
}
|
|
|
|
void WalletSerializer::loadFlags(bool& details, bool& cache, Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
deserializeEncrypted(details, "details", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
deserializeEncrypted(cache, "cache", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::loadWallets(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
auto& index = m_walletsContainer.get<RandomAccessIndex>();
|
|
|
|
uint64_t count = 0;
|
|
deserializeEncrypted(count, "wallets_count", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
bool isTrackingMode;
|
|
|
|
for (uint64_t i = 0; i < count; ++i) {
|
|
WalletRecordDto dto;
|
|
deserializeEncrypted(dto, "", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
if (i == 0) {
|
|
isTrackingMode = dto.spendSecretKey == NULL_SECRET_KEY;
|
|
} else if ((isTrackingMode && dto.spendSecretKey != NULL_SECRET_KEY) || (!isTrackingMode && dto.spendSecretKey == NULL_SECRET_KEY)) {
|
|
throw std::system_error(make_error_code(error::BAD_ADDRESS), "All addresses must be whether tracking or not");
|
|
}
|
|
|
|
if (dto.spendSecretKey != NULL_SECRET_KEY) {
|
|
Crypto::PublicKey restoredPublicKey;
|
|
bool r = Crypto::secret_key_to_public_key(dto.spendSecretKey, restoredPublicKey);
|
|
|
|
if (!r || dto.spendPublicKey != restoredPublicKey) {
|
|
throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Restored spend public key doesn't correspond to secret key");
|
|
}
|
|
} else {
|
|
if (!Crypto::check_key(dto.spendPublicKey)) {
|
|
throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Public spend key is incorrect");
|
|
}
|
|
}
|
|
|
|
WalletRecord wallet;
|
|
wallet.spendPublicKey = dto.spendPublicKey;
|
|
wallet.spendSecretKey = dto.spendSecretKey;
|
|
wallet.actualBalance = dto.actualBalance;
|
|
wallet.pendingBalance = dto.pendingBalance;
|
|
wallet.creationTimestamp = static_cast<time_t>(dto.creationTimestamp);
|
|
wallet.container = reinterpret_cast<CryptoNote::ITransfersContainer*>(i); //dirty hack. container field must be unique
|
|
|
|
index.push_back(wallet);
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::subscribeWallets() {
|
|
auto& index = m_walletsContainer.get<RandomAccessIndex>();
|
|
|
|
for (auto it = index.begin(); it != index.end(); ++it) {
|
|
const auto& wallet = *it;
|
|
|
|
AccountSubscription sub;
|
|
sub.keys.address.viewPublicKey = m_viewPublicKey;
|
|
sub.keys.address.spendPublicKey = wallet.spendPublicKey;
|
|
sub.keys.viewSecretKey = m_viewSecretKey;
|
|
sub.keys.spendSecretKey = wallet.spendSecretKey;
|
|
sub.transactionSpendableAge = m_transactionSoftLockTime;
|
|
sub.syncStart.height = 0;
|
|
sub.syncStart.timestamp = std::max(static_cast<uint64_t>(wallet.creationTimestamp), ACCOUNT_CREATE_TIME_ACCURACY) - ACCOUNT_CREATE_TIME_ACCURACY;
|
|
|
|
auto& subscription = m_synchronizer.addSubscription(sub);
|
|
bool r = index.modify(it, [&subscription] (WalletRecord& rec) { rec.container = &subscription.getContainer(); });
|
|
assert(r);
|
|
|
|
subscription.addObserver(&m_transfersObserver);
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadBalances(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
deserializeEncrypted(m_actualBalance, "actual_balance", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
deserializeEncrypted(m_pendingBalance, "pending_balance", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
}
|
|
|
|
void WalletSerializer::loadTransfersSynchronizer(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
std::string deciphered;
|
|
deserializeEncrypted(deciphered, "transfers_synchronizer", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
std::stringstream stream(deciphered);
|
|
deciphered.clear();
|
|
|
|
m_synchronizer.load(stream);
|
|
}
|
|
|
|
void WalletSerializer::loadObsoleteSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
uint64_t count = 0;
|
|
deserializeEncrypted(count, "spent_outputs_count", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
for (uint64_t i = 0; i < count; ++i) {
|
|
ObsoleteSpentOutputDto dto;
|
|
deserializeEncrypted(dto, "", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadUnlockTransactionsJobs(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
auto& index = m_unlockTransactions.get<TransactionHashIndex>();
|
|
auto& walletsIndex = m_walletsContainer.get<RandomAccessIndex>();
|
|
const uint64_t walletsSize = walletsIndex.size();
|
|
|
|
uint64_t jobsCount = 0;
|
|
deserializeEncrypted(jobsCount, "unlock_transactions_jobs_count", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
for (uint64_t i = 0; i < jobsCount; ++i) {
|
|
UnlockTransactionJobDto dto;
|
|
deserializeEncrypted(dto, "", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
assert(dto.walletIndex < walletsSize);
|
|
|
|
UnlockTransactionJob job;
|
|
job.blockHeight = dto.blockHeight;
|
|
job.transactionHash = dto.transactionHash;
|
|
job.container = walletsIndex[dto.walletIndex].container;
|
|
|
|
index.insert(std::move(job));
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadObsoleteChange(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
uint64_t count = 0;
|
|
deserializeEncrypted(count, "changes_count", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
for (uint64_t i = 0; i < count; i++) {
|
|
ObsoleteChangeDto dto;
|
|
deserializeEncrypted(dto, "", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadUncommitedTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
deserializeEncrypted(uncommitedTransactions, "uncommited_transactions", cryptoContext, source);
|
|
}
|
|
|
|
void WalletSerializer::initTransactionPool() {
|
|
std::unordered_set<Crypto::Hash> uncommitedTransactionsSet;
|
|
std::transform(uncommitedTransactions.begin(), uncommitedTransactions.end(), std::inserter(uncommitedTransactionsSet, uncommitedTransactionsSet.end()),
|
|
[](const UncommitedTransactions::value_type& pair) {
|
|
return getObjectHash(pair.second);
|
|
});
|
|
m_synchronizer.initTransactionPool(uncommitedTransactionsSet);
|
|
}
|
|
|
|
void WalletSerializer::resetCachedBalance() {
|
|
for (auto it = m_walletsContainer.begin(); it != m_walletsContainer.end(); ++it) {
|
|
m_walletsContainer.modify(it, [](WalletRecord& wallet) {
|
|
wallet.actualBalance = 0;
|
|
wallet.pendingBalance = 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
// can't do it in loadTransactions, TransfersContainer is not yet loaded
|
|
void WalletSerializer::updateTransactionsBaseStatus() {
|
|
auto& transactions = m_transactions.get<RandomAccessIndex>();
|
|
auto begin = std::begin(transactions);
|
|
auto end = std::end(transactions);
|
|
for (; begin != end; ++begin) {
|
|
transactions.modify(begin, [this](WalletTransaction& tx) {
|
|
auto& wallets = m_walletsContainer.get<RandomAccessIndex>();
|
|
TransactionInformation txInfo;
|
|
auto it = std::find_if(std::begin(wallets), std::end(wallets), [&](const WalletRecord& rec) {
|
|
assert(rec.container != nullptr);
|
|
return rec.container->getTransactionInformation(tx.hash, txInfo);
|
|
});
|
|
|
|
tx.isBase = it != std::end(wallets) && txInfo.totalAmountIn == 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::updateTransfersSign() {
|
|
auto it = m_transfers.begin();
|
|
while (it != m_transfers.end()) {
|
|
if (it->second.amount < 0) {
|
|
it->second.amount = -it->second.amount;
|
|
++it;
|
|
} else {
|
|
it = m_transfers.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) {
|
|
uint64_t count = 0;
|
|
deserializeEncrypted(count, "transactions_count", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
m_transactions.get<RandomAccessIndex>().reserve(count);
|
|
|
|
for (uint64_t i = 0; i < count; ++i) {
|
|
WalletTransactionDto dto;
|
|
deserializeEncrypted(dto, "", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
WalletTransaction tx;
|
|
tx.state = dto.state;
|
|
tx.timestamp = dto.timestamp;
|
|
tx.blockHeight = dto.blockHeight;
|
|
tx.hash = dto.hash;
|
|
tx.totalAmount = dto.totalAmount;
|
|
tx.fee = dto.fee;
|
|
tx.creationTime = dto.creationTime;
|
|
tx.unlockTime = dto.unlockTime;
|
|
tx.extra = dto.extra;
|
|
tx.isBase = false;
|
|
|
|
m_transactions.get<RandomAccessIndex>().push_back(std::move(tx));
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::loadTransfers(Common::IInputStream& source, CryptoContext& cryptoContext, uint32_t version) {
|
|
uint64_t count = 0;
|
|
deserializeEncrypted(count, "transfers_count", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
m_transfers.reserve(count);
|
|
|
|
for (uint64_t i = 0; i < count; ++i) {
|
|
uint64_t txId = 0;
|
|
deserializeEncrypted(txId, "transaction_id", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
WalletTransferDto dto(version);
|
|
deserializeEncrypted(dto, "transfer", cryptoContext, source);
|
|
cryptoContext.incIv();
|
|
|
|
WalletTransfer tr;
|
|
tr.address = dto.address;
|
|
tr.amount = dto.amount;
|
|
|
|
if (version > 2) {
|
|
tr.type = static_cast<WalletTransferType>(dto.type);
|
|
} else {
|
|
tr.type = WalletTransferType::USUAL;
|
|
}
|
|
|
|
m_transfers.push_back(std::make_pair(txId, tr));
|
|
}
|
|
}
|
|
|
|
void WalletSerializer::addWalletV1Details(const std::vector<WalletLegacyTransaction>& txs, const std::vector<WalletLegacyTransfer>& trs) {
|
|
size_t txId = 0;
|
|
m_transfers.reserve(trs.size());
|
|
|
|
for (const auto& tx: txs) {
|
|
WalletTransaction mtx = convert(tx);
|
|
m_transactions.get<RandomAccessIndex>().push_back(std::move(mtx));
|
|
|
|
if (tx.firstTransferId != WALLET_LEGACY_INVALID_TRANSFER_ID && tx.transferCount != 0) {
|
|
size_t firstTr = tx.firstTransferId;
|
|
size_t lastTr = firstTr + tx.transferCount;
|
|
|
|
if (lastTr > trs.size()) {
|
|
throw std::system_error(make_error_code(error::INTERNAL_WALLET_ERROR));
|
|
}
|
|
|
|
for (; firstTr < lastTr; firstTr++) {
|
|
WalletTransfer tr = convert(trs[firstTr]);
|
|
m_transfers.push_back(std::make_pair(txId, tr));
|
|
}
|
|
}
|
|
|
|
txId++;
|
|
}
|
|
}
|
|
|
|
} //namespace CryptoNote
|