mirror of
https://github.com/wownero/lwsf-wow.git
synced 2026-10-17 07:21:28 +02:00
Added some more components, but still incomplete and only partially builds
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lee *!* Clagett
|
||||
Copyright (c) 2024, The Monero Project
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+19
-13
@@ -425,22 +425,28 @@ struct WalletListener
|
||||
*/
|
||||
struct Wallet
|
||||
{
|
||||
enum class Device {
|
||||
Software = 0,
|
||||
Ledger = 1,
|
||||
Trezor = 2
|
||||
enum Device {
|
||||
Device_Software = 0,
|
||||
Device_Ledger = 1,
|
||||
Device_Trezor = 2
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
Ok,
|
||||
Error,
|
||||
Critical
|
||||
enum Status {
|
||||
Status_Ok,
|
||||
Status_Error,
|
||||
Status_Critical
|
||||
};
|
||||
|
||||
enum class ConnectionStatus {
|
||||
Disconnected,
|
||||
Connected,
|
||||
WrongVersion
|
||||
enum ConnectionStatus {
|
||||
ConnectionStatus_Disconnected,
|
||||
ConnectionStatus_Connected,
|
||||
ConnectionStatus_WrongVersion
|
||||
};
|
||||
|
||||
enum BackgroundSyncType {
|
||||
BackgroundSync_Off = 0,
|
||||
BackgroundSync_ReusePassword = 1,
|
||||
BackgroundSync_CustomPassword = 2
|
||||
};
|
||||
|
||||
virtual ~Wallet() = 0;
|
||||
@@ -938,7 +944,7 @@ struct Wallet
|
||||
virtual std::shared_ptr<AddressBook> addressBook() = 0;
|
||||
virtual std::shared_ptr<Subaddress> subaddress() = 0;
|
||||
virtual std::shared_ptr<SubaddressAccount> subaddressAccount() = 0;
|
||||
virtual void setListener(WalletListener *) = 0;
|
||||
virtual void setListener(std::shared_ptr<WalletListener>) = 0;
|
||||
/*!
|
||||
* \brief defaultMixin - returns number of mixins used in transactions
|
||||
* \return
|
||||
|
||||
+20
-2
@@ -29,8 +29,26 @@
|
||||
include_directories(.)
|
||||
|
||||
# For both the server and admin utility.
|
||||
set(monero-lwsf_sources backend.cpp rpc.cpp transaction_history.cpp transaction_info.cpp wallet.cpp wallet_manager.cpp)
|
||||
set(monero-lwsf_headers backend.h lwsf_config.h rpc.h transaction_history.h transaction_info.h wallet.h)
|
||||
set(monero-lwsf_sources
|
||||
backend.cpp
|
||||
error.cpp
|
||||
pending_transaction.cpp
|
||||
rpc.cpp
|
||||
transaction_history.cpp
|
||||
transaction_info.cpp
|
||||
wallet.cpp
|
||||
wallet_manager.cpp
|
||||
)
|
||||
set(monero-lwsf_headers
|
||||
backend.h
|
||||
error.h
|
||||
lwsf_config.h
|
||||
pending_transaction.h
|
||||
rpc.h
|
||||
transaction_history.h
|
||||
transaction_info.h
|
||||
wallet.h
|
||||
)
|
||||
|
||||
add_executable(monero-lwsf dummy_main.cpp ${monero-lwsf_sources} ${monero-lwsf_headers})
|
||||
target_link_libraries(monero-lwsf
|
||||
|
||||
+207
-96
@@ -31,8 +31,10 @@
|
||||
#include <boost/thread/lock_guard.hpp>
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src
|
||||
#include "lwsf_config.h"
|
||||
#include "ringct/rctOps.h"
|
||||
#include "wire.h"
|
||||
#include "wire/adapted/crypto.h"
|
||||
#include "wire/adapted/pair.h"
|
||||
#include "wire/msgpack.h"
|
||||
#include "wire/wrapper/trusted_array.h"
|
||||
|
||||
@@ -81,24 +83,14 @@ namespace lwsf { namespace internal { namespace backend
|
||||
WIRE_FIELD(amount),
|
||||
WIRE_FIELD(recipient),
|
||||
WIRE_FIELD(index),
|
||||
WIRE_FIELD(output_pub),
|
||||
WIRE_FIELD(rct_mask)
|
||||
WIRE_OPTIONAL_FIELD(rct_mask),
|
||||
WIRE_FIELD(tx_pub)
|
||||
);
|
||||
}
|
||||
}
|
||||
static void read_bytes(wire::reader& source, std::pair<crypto::public_key, transfer_in>& dest)
|
||||
{
|
||||
read_bytes(source, dest.second);
|
||||
dest.first = dest.second.output_pub;
|
||||
}
|
||||
static void write_bytes(wire::writer& dest, const std::pair<const crypto::public_key, transfer_in>& source)
|
||||
{
|
||||
write_bytes(dest, source.second);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
} // anonymous
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename F, typename T>
|
||||
void map_transaction(F& format, T& self)
|
||||
{
|
||||
@@ -113,27 +105,29 @@ namespace
|
||||
WIRE_OPTIONAL_FIELD(height),
|
||||
WIRE_FIELD(unlock_time),
|
||||
WIRE_FIELD(direction),
|
||||
WIRE_OPTIONAL_FIELD(tx_secret),
|
||||
WIRE_FIELD(id),
|
||||
WIRE_FIELD(prefix),
|
||||
WIRE_FIELD(coinbase)
|
||||
);
|
||||
}
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
static void read_bytes(wire::reader& source, std::pair<crypto::hash, std::shared_ptr<transaction>>& dest)
|
||||
{
|
||||
if (!dest.second)
|
||||
dest.second = std::make_shared<transaction>(transaction{});
|
||||
read_bytes(source, *dest.second);
|
||||
dest.first = dest.second->id;
|
||||
}
|
||||
static void write_bytes(wire::writer& dest, const std::pair<const crypto::hash, std::shared_ptr<transaction>>& source)
|
||||
{
|
||||
if (!source.second)
|
||||
WIRE_DLOG_THROW(wire::error::schema::object, "Unexpected nullptr");
|
||||
write_bytes(dest, *source.second);
|
||||
}
|
||||
namespace {
|
||||
static void read_bytes(wire::reader& source, std::pair<crypto::hash, std::shared_ptr<transaction>>& dest)
|
||||
{
|
||||
if (!dest.second)
|
||||
dest.second = std::make_shared<transaction>();
|
||||
read_bytes(source, *dest.second);
|
||||
dest.first = dest.second->id;
|
||||
}
|
||||
static void write_bytes(wire::writer& dest, const std::pair<crypto::hash, std::shared_ptr<transaction>>& source)
|
||||
{
|
||||
if (!source.second)
|
||||
WIRE_DLOG_THROW(wire::error::schema::object, "Unexpected nullptr");
|
||||
write_bytes(dest, *source.second);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename F, typename T>
|
||||
void map_keypair(F& format, T& self)
|
||||
{
|
||||
@@ -148,12 +142,64 @@ namespace {
|
||||
wire::field("txes", wire::trusted_array(std::ref(self.txes))),
|
||||
WIRE_FIELD(type),
|
||||
WIRE_FIELD(view),
|
||||
WIRE_FIELD(spend)
|
||||
WIRE_FIELD(spend),
|
||||
WIRE_FIELD(generated_locally)
|
||||
);
|
||||
}
|
||||
|
||||
void new_tx(const account& self, transaction& out, const rpc::transaction& source)
|
||||
|
||||
rct::key get_mask(const crypto::secret_key& view_key, const rpc::output& source)
|
||||
{
|
||||
crypto::key_derivation derived{};
|
||||
if (!crypto::generate_key_derivation(source.tx_pub_key, view_key, derived))
|
||||
throw std::runtime_error{"generate_key_derivation failure"};
|
||||
|
||||
crypto::secret_key scalar{};
|
||||
crypto::derivation_to_scalar(derived, source.index, scalar);
|
||||
|
||||
rct::ecdhTuple commitment{source.rct.mask};
|
||||
rct::ecdhDecode(commitment, rct::sk2rct(scalar), source.rct.type == rpc::ringct::format::recompute);
|
||||
|
||||
return commitment.mask;
|
||||
}
|
||||
|
||||
void update_output(transfer_in& out, const rpc::output& source, const crypto::secret_key& view_key)
|
||||
{
|
||||
out.global_index = std::uint64_t(source.global_index);
|
||||
out.amount = std::uint64_t(source.amount);
|
||||
out.recipient = source.recipient.value_or(rpc::address_meta{});
|
||||
out.index = source.index;
|
||||
out.tx_pub = source.tx_pub_key;
|
||||
|
||||
switch (source.rct.type)
|
||||
{
|
||||
default:
|
||||
throw std::runtime_error{"Unexpected ringct mask type"};
|
||||
case rpc::ringct::format::none:
|
||||
out.rct_mask = std::nullopt;
|
||||
break;
|
||||
case rpc::ringct::format::encrypted:
|
||||
case rpc::ringct::format::recompute:
|
||||
out.rct_mask = get_mask(view_key, source);
|
||||
break;
|
||||
case rpc::ringct::format::unencrypted:
|
||||
out.rct_mask = source.rct.mask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void update_spend(transfer_out& out, const rpc::transaction_spend& source)
|
||||
{
|
||||
out.amount = std::uint64_t(source.amount);
|
||||
out.sender = source.sender.value_or(rpc::address_meta{});
|
||||
out.tx_pub = source.tx_pub_key;
|
||||
}
|
||||
|
||||
void update_tx(const account& self, transaction& out, const rpc::transaction& source)
|
||||
{
|
||||
/* Let `receives` re-populate in `merge_output`. This works because the
|
||||
server supplies all info - there is no local info to keep. */
|
||||
out.receives.clear();
|
||||
|
||||
out.id = source.tx_hash;
|
||||
out.timestamp = source.timestamp;
|
||||
out.fee = std::uint64_t(source.fee.value_or(rpc::uint64_string(0)));
|
||||
@@ -178,7 +224,9 @@ namespace {
|
||||
crypto::generate_key_image(output_pub, output_secret, image);
|
||||
if (image == spend.key_image)
|
||||
{
|
||||
out.spends.emplace_back(std::uint64_t(spend.amount), spend.tx_pub_key);
|
||||
/* Frontend will typically know about spend before backend. So only
|
||||
merge and never erase spends. */
|
||||
update_spend(out.spends.try_emplace(image).first->second, spend);
|
||||
total_spent += std::uint64_t(spend.amount);
|
||||
}
|
||||
}
|
||||
@@ -195,46 +243,95 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
void merge_output(std::shared_ptr<transaction>& out, const rpc::output& output)
|
||||
void merge_output(const std::shared_ptr<transaction>& out, const rpc::output& source, const crypto::secret_key& view_key)
|
||||
{
|
||||
if (!out)
|
||||
throw std::logic_error{"nullptr transaction in merge_output"};
|
||||
|
||||
out->prefix = output.tx_prefix_hash;
|
||||
const auto receive = out->receives.lower_bound(output.public_key);
|
||||
if (receive == out->receives.end() || receive->first != output.public_key)
|
||||
{
|
||||
out->receives.emplace_hint(receive, output.public_key, transfer_in{output});
|
||||
}
|
||||
out->prefix = source.tx_prefix_hash;
|
||||
update_output(out->receives.try_emplace(source.public_key).first->second, source, view_key);
|
||||
}
|
||||
|
||||
bool merge_response(wallet& self, const rpc::get_address_txs& source)
|
||||
std::vector<std::shared_ptr<transaction>> merge_response(wallet& self, const rpc::get_address_txs& source)
|
||||
{
|
||||
bool updated_wallet = false;
|
||||
std::vector<std::shared_ptr<transaction>> out;
|
||||
|
||||
self.scan_height = source.scanned_block_height;
|
||||
self.blockchain_height = source.blockchain_height;
|
||||
self.primary.restore_height = source.start_height;
|
||||
|
||||
self.primary.txes.reserve(source.transactions.size());
|
||||
for (const auto& tx : source.transactions)
|
||||
/* Backend server could remove or modify txes (rescan or bug fix); the
|
||||
easiest way to handle this is to start a new copy of the txes. This is
|
||||
what the existing (JS) MyMonero frontend does. This has the benefit
|
||||
of allowing `shared_ptr<transaction>` objects to be "given away" to
|
||||
other parts of the frontend without a mutex. */
|
||||
|
||||
const auto existing_txes = std::move(self.primary.txes);
|
||||
self.primary.txes.clear();
|
||||
self.primary.txes.reserve(
|
||||
std::max(existing_txes.size(), source.transactions.size())
|
||||
);
|
||||
|
||||
/* The frontend will know about the spend first, iff the frontend was
|
||||
used to perform the spend. We copy _all_ transactions that have a
|
||||
spend secret, even if the backend doesn't acknowledge it, otherwise the
|
||||
secret information will be lost in many situations. If the spend
|
||||
never gets confirmed, this will just sit in the transaction list. */
|
||||
for (const auto& tx : existing_txes)
|
||||
{
|
||||
auto iter = self.primary.txes.lower_bound(tx.tx_hash);
|
||||
if (iter == self.primary.txes.end() || iter->first != tx.tx_hash)
|
||||
if (tx.second)
|
||||
{
|
||||
updated_wallet = true;
|
||||
new_tx(self.primary, *self.primary.txes.emplace_hint(iter, tx.tx_hash, std::make_shared<transaction>(transaction{}))->second, tx);
|
||||
bool copy = false;
|
||||
for (const auto& spend : tx.second->spends)
|
||||
{
|
||||
if (spend.second.secret)
|
||||
{
|
||||
self.primary.txes.emplace_hint(
|
||||
self.primary.txes.end(), tx.first, std::make_shared<transaction>(*tx.second)
|
||||
);
|
||||
break; // inner loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated_wallet;
|
||||
|
||||
for (const auto& tx : source.transactions)
|
||||
{
|
||||
auto inserted = self.primary.txes.try_emplace(tx.tx_hash, nullptr);
|
||||
if (inserted.second)
|
||||
{
|
||||
const auto existing = existing_txes.find(tx.tx_hash);
|
||||
try
|
||||
{
|
||||
if (existing != existing_txes.end() && existing->second)
|
||||
inserted.first->second = std::make_shared<transaction>(*existing->second);
|
||||
else
|
||||
inserted.first->second = std::make_shared<transaction>();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
self.primary.txes.erase(inserted.first);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (existing != existing_txes.end())
|
||||
out.push_back(inserted.first->second);
|
||||
}
|
||||
update_tx(self.primary, *inserted.first->second, tx);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void merge_response(account& self, const rpc::get_unspent_outs& source)
|
||||
void merge_response(wallet& self, const rpc::get_unspent_outs& source)
|
||||
{
|
||||
self.per_byte_fee = source.per_byte_fee;
|
||||
self.fee_mask = source.fee_mask;
|
||||
for (const auto& output : source.outputs)
|
||||
{
|
||||
auto iter = self.txes.find(output.tx_hash);
|
||||
if (iter != self.txes.end())
|
||||
merge_output(iter->second, output);
|
||||
auto iter = self.primary.txes.find(output.tx_hash);
|
||||
if (iter != self.primary.txes.end())
|
||||
merge_output(iter->second, output, self.primary.view.sec);
|
||||
}
|
||||
}
|
||||
} // anonymous
|
||||
@@ -253,39 +350,21 @@ namespace {
|
||||
void write_bytes(wire::writer& dest, const account& source)
|
||||
{ map_account(dest, source); }
|
||||
|
||||
transfer_in::transfer_in()
|
||||
: global_index(0),
|
||||
amount(0),
|
||||
recipient(),
|
||||
index(0),
|
||||
output_pub{},
|
||||
rct_mask{}
|
||||
{}
|
||||
|
||||
transfer_in::transfer_in(const rpc::output& source)
|
||||
: global_index(std::uint64_t(source.global_index)),
|
||||
amount(std::uint64_t(source.amount)),
|
||||
recipient(source.recipient.value_or(rpc::address_meta{})),
|
||||
index(source.index),
|
||||
output_pub(source.public_key),
|
||||
rct_mask(source.rct.mask)
|
||||
{}
|
||||
|
||||
wallet::wallet(const bool generated_locally)
|
||||
: client(),
|
||||
wallet::wallet()
|
||||
: listener(nullptr),
|
||||
client(),
|
||||
primary{},
|
||||
status(),
|
||||
last_sync(0),
|
||||
scan_height(0),
|
||||
blockchain_height(0),
|
||||
sync(),
|
||||
generated_locally(generated_locally)
|
||||
sync()
|
||||
{}
|
||||
|
||||
expect<epee::byte_slice> wallet::to_bytes() const
|
||||
{
|
||||
epee::byte_stream dest{};
|
||||
dest.reserve(config::initial_buffer_size);
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{sync};
|
||||
const std::error_code error = wire::msgpack::to_bytes(dest, primary);
|
||||
if (error)
|
||||
@@ -293,16 +372,13 @@ namespace {
|
||||
return epee::byte_slice{std::move(dest)};
|
||||
}
|
||||
|
||||
expect<void> wallet::from_bytes(epee::byte_slice source)
|
||||
std::error_code wallet::from_bytes(epee::byte_slice source)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync};
|
||||
const std::error_code error = wire::msgpack::from_bytes(std::move(source), primary);
|
||||
if (error)
|
||||
return error;
|
||||
return success();
|
||||
return wire::msgpack::from_bytes(std::move(source), primary);
|
||||
}
|
||||
|
||||
void wallet::refresh(const bool mandatory)
|
||||
std::error_code wallet::refresh(const bool mandatory)
|
||||
{
|
||||
if (!mandatory)
|
||||
{
|
||||
@@ -311,7 +387,7 @@ namespace {
|
||||
std::chrono::steady_clock::time_point::duration{last_sync.load()}
|
||||
};
|
||||
if (now - start < config::refresh_interval)
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
expect<rpc::get_unspent_outs> outs_response{common_error::kInvalidArgument};
|
||||
@@ -320,21 +396,56 @@ namespace {
|
||||
if (txs_response)
|
||||
outs_response = rpc::invoke<rpc::get_unspent_outs>(client, login);
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{sync};
|
||||
boost::unique_lock<boost::mutex> lock{sync};
|
||||
|
||||
if (!txs_response)
|
||||
{
|
||||
status = txs_response.error();
|
||||
return;
|
||||
}
|
||||
return txs_response.error();
|
||||
if (!outs_response)
|
||||
return outs_response.error();
|
||||
|
||||
const std::uint64_t orig_scan_height = scan_height;
|
||||
const auto new_txes = merge_response(*this, *txs_response);
|
||||
merge_response(*this, *outs_response);
|
||||
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
|
||||
if (!listener)
|
||||
return {};
|
||||
|
||||
// Call listener functions without holding lock, in case a call is made
|
||||
// back into the library.
|
||||
const std::uint64_t new_scan_height = std::max(orig_scan_height, scan_height);
|
||||
lock.unlock();
|
||||
|
||||
listener->refreshed();
|
||||
if (!new_txes.empty())
|
||||
listener->updated();
|
||||
|
||||
for (std::uint64_t i = orig_scan_height; i < new_scan_height; ++i)
|
||||
listener->newBlock(i);
|
||||
|
||||
for (const auto& tx : new_txes)
|
||||
{
|
||||
status = outs_response.error();
|
||||
return;
|
||||
const auto txid = epee::string_tools::pod_to_hex(tx->id);
|
||||
if (tx->direction == TransactionInfo::Direction::In)
|
||||
{
|
||||
if (tx->height)
|
||||
listener->moneyReceived(txid, tx->amount);
|
||||
else
|
||||
listener->unconfirmedMoneyReceived(txid, tx->amount);
|
||||
}
|
||||
else
|
||||
listener->moneySpent(txid, tx->amount);
|
||||
}
|
||||
|
||||
status = std::error_code{};
|
||||
merge_response(*this, *txs_response);
|
||||
merge_response(primary, *outs_response);
|
||||
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code wallet::send_tx(epee::byte_slice tx_bytes)
|
||||
{
|
||||
const rpc::submit_raw_tx_request request{std::move(tx_bytes)};
|
||||
auto response = rpc::invoke<rpc::submit_raw_tx_response>(client, request);
|
||||
if (!response)
|
||||
return response.error();
|
||||
return {};
|
||||
}
|
||||
}}} // lwsf // internal // backend
|
||||
|
||||
+51
-21
@@ -35,11 +35,13 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "common/expect.h" // moneor/src
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "crypto/hash.h" // monero/src
|
||||
@@ -70,11 +72,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
crypto::public_key tx_pub;
|
||||
|
||||
transfer_out()
|
||||
: address(), amount(0), sender(), secret(), tx_pub{}
|
||||
{}
|
||||
|
||||
explicit transfer_out(const std::uint64_t amount, const crypto::public_key& tx_pub)
|
||||
: address(), amount(amount), sender(), secret(), tx_pub(tx_pub)
|
||||
: address(), amount(0), sender{}, secret(), tx_pub{}
|
||||
{}
|
||||
};
|
||||
WIRE_DECLARE_OBJECT(transfer_out);
|
||||
@@ -85,19 +83,49 @@ namespace lwsf { namespace internal { namespace backend
|
||||
std::uint64_t amount;
|
||||
rpc::address_meta recipient;
|
||||
std::uint16_t index;
|
||||
crypto::public_key output_pub;
|
||||
rct::key rct_mask;
|
||||
std::optional<rct::key> rct_mask;
|
||||
crypto::public_key tx_pub;
|
||||
|
||||
transfer_in();
|
||||
explicit transfer_in(const rpc::output& source);
|
||||
transfer_in()
|
||||
: global_index(0),
|
||||
amount(0),
|
||||
recipient{},
|
||||
index(0),
|
||||
rct_mask(),
|
||||
tx_pub{}
|
||||
{}
|
||||
};
|
||||
WIRE_DECLARE_OBJECT(transfer_in);
|
||||
|
||||
|
||||
struct transaction
|
||||
{
|
||||
transaction() = delete;
|
||||
std::vector<transfer_out> spends;
|
||||
std::map<crypto::public_key, transfer_in, memory> receives;
|
||||
transaction()
|
||||
: spends(),
|
||||
receives(),
|
||||
description(),
|
||||
label(),
|
||||
timestamp(),
|
||||
amount(0),
|
||||
fee(0),
|
||||
height(),
|
||||
unlock_time(0),
|
||||
direction(TransactionInfo::Direction::Out),
|
||||
payment_id(),
|
||||
id{},
|
||||
prefix{},
|
||||
coinbase(false)
|
||||
{}
|
||||
|
||||
transaction(transaction&&) = default;
|
||||
transaction(const transaction&) = default;
|
||||
transaction& operator=(transaction&&) = default;
|
||||
transaction& operator=(const transaction&) = default;
|
||||
|
||||
/*! flat_map is used here for faster copies/merging. A single allocation
|
||||
is needed in the copy (done every refresh interval), instead of an
|
||||
allocation per key. */
|
||||
boost::container::flat_map<crypto::key_image, transfer_out, memory> spends;
|
||||
boost::container::flat_map<crypto::public_key, transfer_in, memory> receives; //!< Key is output pub
|
||||
std::string description;
|
||||
std::string label;
|
||||
std::optional<std::time_t> timestamp;
|
||||
@@ -107,8 +135,6 @@ namespace lwsf { namespace internal { namespace backend
|
||||
std::uint64_t unlock_time;
|
||||
TransactionInfo::Direction direction;
|
||||
std::variant<std::nullptr_t, crypto::hash8, crypto::hash> payment_id;
|
||||
std::optional<crypto::secret_key> tx_secret;
|
||||
crypto::public_key pub;
|
||||
crypto::hash id;
|
||||
crypto::hash prefix;
|
||||
bool coinbase;
|
||||
@@ -132,26 +158,30 @@ namespace lwsf { namespace internal { namespace backend
|
||||
NetworkType type;
|
||||
keypair view;
|
||||
keypair spend;
|
||||
bool generated_locally;
|
||||
};
|
||||
WIRE_DECLARE_OBJECT(account);
|
||||
|
||||
struct wallet
|
||||
{
|
||||
std::shared_ptr<WalletListener> listener;
|
||||
rpc::http_client client;
|
||||
account primary;
|
||||
std::error_code status;
|
||||
std::atomic<std::chrono::steady_clock::time_point::rep> last_sync;
|
||||
std::atomic<std::chrono::steady_clock::time_point::duration::rep> last_sync;
|
||||
std::uint64_t scan_height;
|
||||
std::uint64_t blockchain_height;
|
||||
std::uint64_t per_byte_fee;
|
||||
std::uint64_t fee_mask;
|
||||
mutable boost::mutex sync;
|
||||
const bool generated_locally;
|
||||
|
||||
explicit wallet(bool generated_locally);
|
||||
wallet();
|
||||
|
||||
expect<epee::byte_slice> to_bytes() const;
|
||||
expect<void> from_bytes(epee::byte_slice source);
|
||||
std::error_code from_bytes(epee::byte_slice source);
|
||||
|
||||
bool login() const;
|
||||
void refresh(bool mandatory = false);
|
||||
std::error_code refresh(bool mandatory = false);
|
||||
|
||||
std::error_code send_tx(epee::byte_slice tx_bytes);
|
||||
};
|
||||
}}} // lwsf // internal // backend
|
||||
|
||||
+73
-12
@@ -33,8 +33,10 @@
|
||||
#include <boost/spirit/include/qi_parse_attr.hpp>
|
||||
#include <boost/spirit/include/qi_sequence.hpp>
|
||||
#include <ctime>
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
#include "lwsf_config.h"
|
||||
#include "net/http_client.h" // monero/contrib/epee/include
|
||||
#include "ringct/rctOps.h" // monero/contrib/epee/include
|
||||
#include "wire.h"
|
||||
#include "wire/adapted/crypto.h"
|
||||
#include "wire/field.h"
|
||||
@@ -71,14 +73,14 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
struct category final : std::error_category
|
||||
{
|
||||
virtual const char* name() const noexcept override final
|
||||
{
|
||||
return "lwsf::internal::rpc::error_category()";
|
||||
}
|
||||
{
|
||||
return "lwsf::internal::rpc::error_category()";
|
||||
}
|
||||
|
||||
virtual std::string message(int value) const override final
|
||||
{
|
||||
return get_string(error(value));
|
||||
}
|
||||
virtual std::string message(int value) const override final
|
||||
{
|
||||
return get_string(error(value));
|
||||
}
|
||||
};
|
||||
static const category instance{};
|
||||
return instance;
|
||||
@@ -95,7 +97,7 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
return {error::invoke_failure};
|
||||
if (!response)
|
||||
return {error::invoke_failure};
|
||||
if(response->m_response_code != 200)
|
||||
if(response->m_response_code != 200 && response->m_response_code != 201)
|
||||
return {error::wrong_response_code};
|
||||
|
||||
return response->m_body;
|
||||
@@ -149,7 +151,7 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
using min_spent = wire::min_element_sizeof<crypto::key_image>;
|
||||
|
||||
std::optional<std::string> timestamp;
|
||||
std::optional<std::vector<std::uint8_t>> payment_id;
|
||||
std::optional<epee::byte_slice> payment_id;
|
||||
wire::object(source,
|
||||
WIRE_FIELD_ARRAY(spent_outputs, min_spent),
|
||||
wire::optional_field("payment_id", std::ref(payment_id)),
|
||||
@@ -159,10 +161,10 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
WIRE_FIELD(unlock_time),
|
||||
WIRE_OPTIONAL_FIELD(height),
|
||||
WIRE_FIELD(tx_hash),
|
||||
WIRE_FIELD(is_coinbase)
|
||||
WIRE_FIELD(is_coinbase),
|
||||
WIRE_FIELD(mempool)
|
||||
);
|
||||
|
||||
|
||||
if (payment_id && !payment_id->empty())
|
||||
{
|
||||
if (payment_id->size() == sizeof(crypto::hash8))
|
||||
@@ -207,19 +209,67 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// This field is a mess between implementations, this cleans it up a bit
|
||||
struct convert_rct
|
||||
{
|
||||
ringct operator()(const std::nullptr_t&) const noexcept
|
||||
{
|
||||
return ringct{{}, ringct::format::none};
|
||||
}
|
||||
|
||||
ringct operator()(const std::string& source) const
|
||||
{
|
||||
struct ringct_triplet
|
||||
{
|
||||
ringct_triplet() = delete;
|
||||
rct::key commitment;
|
||||
rct::key mask;
|
||||
rct::key amount;
|
||||
};
|
||||
static_assert(sizeof(ringct_triplet) == 96);
|
||||
|
||||
if (source.empty())
|
||||
return ringct{{}, ringct::format::none};
|
||||
|
||||
rct::key out{};
|
||||
if (source == "coinbase") // non-standard, openmonero
|
||||
return ringct{rct::identity(), ringct::format::unencrypted};
|
||||
else if (source.size() == sizeof(ringct_triplet) * 2)
|
||||
{
|
||||
ringct_triplet rct{};
|
||||
if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(rct), source))
|
||||
WIRE_DLOG_THROW(wire::error::schema::binary, "Invalid hex for ringct");
|
||||
return ringct{rct.mask, ringct::format::encrypted};
|
||||
}
|
||||
|
||||
return ringct{{}, ringct::format::recompute};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ringct operator()(const T&) const
|
||||
{
|
||||
WIRE_DLOG_THROW(wire::error::schema::string, "Expected string or null");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void read_bytes(wire::json_reader& source, output& self)
|
||||
{
|
||||
wire::basic_value raw_rct{};
|
||||
wire::object(source,
|
||||
WIRE_FIELD(amount),
|
||||
WIRE_FIELD(index),
|
||||
WIRE_FIELD(global_index),
|
||||
WIRE_OPTIONAL_FIELD(recipient),
|
||||
WIRE_FIELD(rct),
|
||||
wire::optional_field("rct", std::ref(raw_rct)),
|
||||
WIRE_FIELD(tx_hash),
|
||||
WIRE_FIELD(tx_prefix_hash),
|
||||
WIRE_FIELD(public_key),
|
||||
WIRE_FIELD(tx_pub_key)
|
||||
);
|
||||
self.rct = std::visit(convert_rct{}, raw_rct.value);
|
||||
}
|
||||
|
||||
void read_bytes(wire::json_reader& source, get_unspent_outs& self)
|
||||
@@ -233,5 +283,16 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
WIRE_FIELD(fee_mask)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void write_bytes(wire::json_writer& dest, const submit_raw_tx_request& self)
|
||||
{
|
||||
wire::object(dest, WIRE_FIELD(tx));
|
||||
}
|
||||
|
||||
void read_bytes(wire::json_reader& source, submit_raw_tx_response& self)
|
||||
{
|
||||
wire::object(source, WIRE_FIELD(status));
|
||||
}
|
||||
}}} // lwsf // internal // rpc
|
||||
|
||||
|
||||
@@ -30,17 +30,17 @@
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "byte_stream.h" // moneor/contrib/epee/include
|
||||
#include "common/expect.h" // monero/src
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "ringct/rctTypes.h" // monero/src
|
||||
#include "wire/basic_value.h"
|
||||
#include "wire/fwd.h"
|
||||
#include "wire/json.h"
|
||||
|
||||
// Contains code for interact
|
||||
|
||||
namespace epee { namespace net_utils
|
||||
{
|
||||
class blocked_mode_client;
|
||||
@@ -63,7 +63,7 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
inline std::error_code make_error_code(const error value) noexcept
|
||||
{
|
||||
return std::error_code{int(value), error_category()};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! Send `payload` to `client` at uri `endpoint`, and \return payload response
|
||||
@@ -84,7 +84,7 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
F out{};
|
||||
error = wire::json::from_bytes(std::move(*result), out);
|
||||
if (error)
|
||||
return out;
|
||||
return error;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -123,9 +123,10 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
|
||||
std::optional<bool> generated_locally;
|
||||
};
|
||||
inline void read_bytes(const wire::json_reader&, const login_response&)
|
||||
inline void read_bytes(const wire::json_reader&, const login_response&) noexcept
|
||||
{}
|
||||
|
||||
|
||||
struct daemon_status
|
||||
{
|
||||
daemon_status() = delete;
|
||||
@@ -181,6 +182,7 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
std::optional<std::uint64_t> height;
|
||||
crypto::hash tx_hash;
|
||||
bool is_coinbase;
|
||||
bool mempool;
|
||||
|
||||
transaction()
|
||||
: spent_outputs(),
|
||||
@@ -191,7 +193,8 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
unlock_time(0),
|
||||
height(),
|
||||
tx_hash{},
|
||||
is_coinbase(false)
|
||||
is_coinbase(false),
|
||||
mempool(false)
|
||||
{}
|
||||
};
|
||||
void read_bytes(wire::json_reader&, transaction&);
|
||||
@@ -212,11 +215,18 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
struct ringct
|
||||
{
|
||||
ringct() = delete;
|
||||
rct::key commitment;
|
||||
|
||||
enum class format : std::uint8_t
|
||||
{
|
||||
none = 0, //!< Not ringct
|
||||
encrypted,
|
||||
recompute,
|
||||
unencrypted
|
||||
};
|
||||
|
||||
rct::key mask;
|
||||
rct::key amount;
|
||||
format type;
|
||||
};
|
||||
static_assert(sizeof(ringct) == 96);
|
||||
|
||||
struct output
|
||||
{
|
||||
@@ -254,6 +264,24 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
std::uint64_t fee_mask;
|
||||
};
|
||||
void read_bytes(wire::json_reader&, get_unspent_outs&);
|
||||
|
||||
|
||||
struct submit_raw_tx_request
|
||||
{
|
||||
submit_raw_tx_request() = delete;
|
||||
epee::byte_slice tx;
|
||||
};
|
||||
void write_bytes(wire::json_writer&, const submit_raw_tx_request&);
|
||||
|
||||
struct submit_raw_tx_response
|
||||
{
|
||||
submit_raw_tx_response() = delete;
|
||||
static constexpr const char* endpoint() noexcept { return "/submit_raw_tx"; }
|
||||
|
||||
std::string status;
|
||||
};
|
||||
void read_bytes(wire::json_reader&, submit_raw_tx_response&);
|
||||
|
||||
}}} // lwsf // internal // rpc
|
||||
|
||||
WIRE_DECLARE_BLOB_NS(lwsf::internal::rpc::ringct);
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
#include "transaction_info.h"
|
||||
|
||||
#include "string_tools.h" // monero/contrib/epee/include
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
@@ -59,17 +59,15 @@ namespace lwsf { namespace internal
|
||||
std::shared_ptr<TransactionInfo> transaction_history::transaction(int index) const
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
if (std::numeric_limits<int>::max() < data_->primary.txes.size())
|
||||
throw std::runtime_error{"Exceeded max int size in transaction_history:transaction"};
|
||||
if (data_->primary.txes.size() < index || index < 0)
|
||||
if (index < 0 || data_->primary.txes.size() < unsigned(index))
|
||||
throw std::runtime_error{"index provided to transaction invalid"};
|
||||
return std::make_shared<transaction_info>(data_, data_->primary.txes.nth(index)->second);
|
||||
return std::make_shared<transaction_info>(data_, data_->primary.txes.nth(unsigned(index))->second);
|
||||
}
|
||||
|
||||
std::shared_ptr<TransactionInfo> transaction_history::transaction(const std::string &id) const
|
||||
{
|
||||
crypto::hash binary_id{};
|
||||
if (!epee::string_tools::hex_to_pod(id, binary_id))
|
||||
if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(binary_id), id))
|
||||
throw std::runtime_error{"transaction_history given invalid hex id"};
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
|
||||
@@ -54,17 +54,24 @@ namespace lwsf { namespace internal
|
||||
|
||||
transfers_.reserve(data_->spends.size());
|
||||
for (const auto& spend : data_->spends)
|
||||
transfers_.emplace_back(spend.amount, spend.address);
|
||||
transfers_.emplace_back(spend.second.amount, spend.second.address);
|
||||
}
|
||||
|
||||
transaction_info::~transaction_info()
|
||||
{}
|
||||
|
||||
std::string transaction_info::description() const
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
return data_->description;
|
||||
}
|
||||
|
||||
uint64_t transaction_info::confirmations() const
|
||||
{
|
||||
const auto block_height = blockHeight();
|
||||
if (!data_->height)
|
||||
return 0;
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
return std::max(wallet_->blockchain_height, block_height) - block_height;
|
||||
return std::max(wallet_->blockchain_height, *data_->height) - *data_->height + 1;
|
||||
}
|
||||
|
||||
std::string transaction_info::hash() const
|
||||
|
||||
@@ -55,13 +55,13 @@ namespace lwsf { namespace internal
|
||||
transaction_info& operator=(transaction_info&&) = delete;
|
||||
|
||||
virtual Direction direction() const override { return data_->direction; }
|
||||
virtual bool isPending() const override { return confirmations() == 0; }
|
||||
virtual bool isPending() const override { return !data_->height; }
|
||||
virtual bool isFailed() const override { return false; }
|
||||
virtual bool isCoinbase() const override { return data_->coinbase; }
|
||||
virtual uint64_t amount() const override { return data_->amount; }
|
||||
virtual uint64_t fee() const override { return data_->fee; }
|
||||
virtual uint64_t blockHeight() const override { return data_->height.value_or(0); }
|
||||
virtual std::string description() const override { return data_->description; }
|
||||
virtual std::string description() const override;
|
||||
virtual std::set<uint32_t> subaddrIndex() const override { return {}; };
|
||||
virtual uint32_t subaddrAccount() const override { return 0; }
|
||||
virtual std::string label() const override { return data_->label; }
|
||||
|
||||
+429
-24
@@ -28,16 +28,30 @@
|
||||
|
||||
#include "wallet.h"
|
||||
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sodium/core.h>
|
||||
#include <sodium/crypto_pwhash_argon2id.h>
|
||||
#include <sodium/randombytes.h>
|
||||
#include <sodium/crypto_aead_chacha20poly1305.h>
|
||||
#include "backend.h"
|
||||
#include "error.h"
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
#include "lwsf_config.h"
|
||||
#include "net/parse.h" // monero/src
|
||||
#include "net/socks_connect.h" // monero/src
|
||||
#include "transaction_history.h"
|
||||
#include "wire.h"
|
||||
#include "wire/msgpack.h"
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
struct null_connector
|
||||
@@ -48,29 +62,369 @@ namespace lwsf { namespace internal
|
||||
throw std::runtime_error{"Unable to connect"};
|
||||
}
|
||||
};
|
||||
|
||||
struct encrypted_file
|
||||
{
|
||||
std::string cipher; //!< Name of cipher+authentication used
|
||||
std::string pwhasher; //!< Name of pwhasher used
|
||||
epee::byte_slice salt; //!< Pwhashing salt
|
||||
epee::byte_slice nonce; //!< Encryption iv/nonce
|
||||
epee::byte_slice epayload; //!< Encrypted contents
|
||||
std::uint64_t iterations; //!< Pwhashing iterations
|
||||
std::uint32_t memory; //!< Pwhashing memory
|
||||
|
||||
encrypted_file() = delete;
|
||||
|
||||
//! \pre `sodium_init()` has been called
|
||||
static epee::byte_slice get_random(const std::size_t length)
|
||||
{
|
||||
epee::byte_stream out;
|
||||
out.put_n(0, length);
|
||||
randombytes_buf(out.data(), length);
|
||||
return epee::byte_slice{std::move(out)};
|
||||
}
|
||||
|
||||
static constexpr const char* pwhasher_name() noexcept
|
||||
{
|
||||
return "argon2id";
|
||||
}
|
||||
static constexpr int pwhash_algorithm() noexcept
|
||||
{
|
||||
return crypto_pwhash_argon2id_ALG_ARGON2ID13;
|
||||
}
|
||||
static constexpr std::size_t salt_size() noexcept
|
||||
{
|
||||
return crypto_pwhash_argon2id_SALTBYTES;
|
||||
}
|
||||
static constexpr std::uint64_t ops_min() noexcept { return 5; }
|
||||
static constexpr std::size_t memory_limit() noexcept { return 7 * 1024 * 1024; }
|
||||
static epee::byte_slice get_salt() { return get_random(salt_size()); }
|
||||
|
||||
static constexpr const char* cipher_name() noexcept
|
||||
{
|
||||
return "chacha20-poly1305_ietf";
|
||||
}
|
||||
static constexpr unsigned long long max_size() noexcept
|
||||
{
|
||||
return crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX;
|
||||
}
|
||||
static constexpr std::size_t key_size() noexcept
|
||||
{
|
||||
return crypto_aead_chacha20poly1305_ietf_KEYBYTES;
|
||||
}
|
||||
static constexpr std::size_t tag_size() noexcept
|
||||
{
|
||||
return crypto_aead_chacha20poly1305_ietf_ABYTES;
|
||||
}
|
||||
static constexpr std::size_t nonce_size() noexcept
|
||||
{
|
||||
return crypto_aead_chacha20poly1305_ietf_NPUBBYTES;
|
||||
}
|
||||
static epee::byte_slice get_nonce() { return get_random(nonce_size()); }
|
||||
|
||||
//! \pre `sodium_init()` has been called
|
||||
std::array<std::uint8_t, 32> get_key(const std::string& password) const
|
||||
{
|
||||
std::array<std::uint8_t, key_size()> key{{}};
|
||||
if (crypto_pwhash_argon2id(
|
||||
key.data(), key.size(),
|
||||
password.data(), password.size(),
|
||||
salt.data(),
|
||||
iterations, memory, pwhash_algorithm()) != 0)
|
||||
{
|
||||
throw std::runtime_error{std::string{pwhasher_name()} + " failed"};
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
static encrypted_file
|
||||
make(const epee::byte_slice& upayload, const std::uint64_t iterations, const std::string& password)
|
||||
{
|
||||
if (sodium_init() < 0)
|
||||
throw std::runtime_error{"Failed to initialize libsodium"};
|
||||
|
||||
if (max_size() < upayload.size())
|
||||
throw std::runtime_error{std::string{"Exceeded max size for "} + cipher_name()};
|
||||
if (tag_size() < std::numeric_limits<std::size_t>::max() - upayload.size())
|
||||
throw std::runtime_error{"Exceeded max size_t after authentication tag"};
|
||||
|
||||
encrypted_file out{
|
||||
cipher_name(),
|
||||
pwhasher_name(),
|
||||
get_salt(),
|
||||
get_nonce(),
|
||||
nullptr,
|
||||
std::max(iterations, ops_min()),
|
||||
memory_limit()
|
||||
};
|
||||
|
||||
const auto key = out.get_key(password);
|
||||
|
||||
epee::byte_stream buffer;
|
||||
buffer.put_n(0, upayload.size() + tag_size());
|
||||
|
||||
unsigned long long out_bytes = buffer.size();
|
||||
if (crypto_aead_chacha20poly1305_ietf_encrypt(
|
||||
buffer.data(), std::addressof(out_bytes),
|
||||
upayload.data(), upayload.size(),
|
||||
nullptr, 0,
|
||||
nullptr,
|
||||
out.nonce.data(), key.data()) != 0)
|
||||
{
|
||||
throw std::runtime_error{std::string{cipher_name()} + " encryption failed"};
|
||||
}
|
||||
|
||||
out.epayload = epee::byte_slice{std::move(buffer)}.take_slice(out_bytes);
|
||||
return out;
|
||||
}
|
||||
|
||||
//! \return Unencrypted payload, or `nullptr`.
|
||||
epee::byte_slice get_payload(const std::string& password) const
|
||||
{
|
||||
if (nonce.size() != nonce_size())
|
||||
return nullptr;
|
||||
if (salt.size() != salt_size())
|
||||
return nullptr;
|
||||
if (cipher != cipher_name())
|
||||
return nullptr;
|
||||
if (pwhasher != pwhasher_name())
|
||||
return nullptr;
|
||||
if (max_size() < epayload.size())
|
||||
return nullptr;
|
||||
|
||||
if (sodium_init() < 0)
|
||||
throw std::runtime_error{"Failed to initialize libsodium"};
|
||||
|
||||
const auto key = get_key(password);
|
||||
|
||||
epee::byte_stream buffer;
|
||||
buffer.put_n(0, epayload.size());
|
||||
|
||||
static_assert(
|
||||
std::numeric_limits<std::size_t>::max() <= std::numeric_limits<unsigned long long>::max()
|
||||
);
|
||||
unsigned long long out_bytes = buffer.size();
|
||||
if (crypto_aead_chacha20poly1305_ietf_decrypt(
|
||||
buffer.data(), std::addressof(out_bytes),
|
||||
nullptr,
|
||||
epayload.data(), epayload.size(),
|
||||
nullptr, 0,
|
||||
nonce.data(), key.data()) == 0)
|
||||
{
|
||||
throw std::runtime_error{std::string{cipher_name()} + " decryption failed"};
|
||||
}
|
||||
|
||||
return epee::byte_slice{std::move(buffer)}.take_slice(out_bytes);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F, typename T>
|
||||
void map_encrypted_file(F& format, T& self)
|
||||
{
|
||||
wire::object(format,
|
||||
WIRE_FIELD(cipher),
|
||||
WIRE_FIELD(pwhasher),
|
||||
WIRE_FIELD(salt),
|
||||
WIRE_FIELD(nonce),
|
||||
WIRE_FIELD(epayload),
|
||||
WIRE_FIELD(iterations),
|
||||
WIRE_FIELD(memory)
|
||||
);
|
||||
}
|
||||
|
||||
WIRE_DEFINE_OBJECT(encrypted_file, map_encrypted_file);
|
||||
|
||||
epee::byte_slice try_load(const std::string& filename)
|
||||
{
|
||||
std::ifstream file{filename, std::ios::binary | std::ios::ate};
|
||||
if (!file.is_open())
|
||||
return nullptr;
|
||||
|
||||
const auto size = file.tellg();
|
||||
if (size < 0)
|
||||
return nullptr;
|
||||
|
||||
epee::byte_stream buffer;
|
||||
buffer.put_n(0, std::size_t(size));
|
||||
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
|
||||
|
||||
if (file.good())
|
||||
return epee::byte_slice{std::move(buffer)};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
bool atomic_file_write(const std::filesystem::path& filename, const std::filesystem::path& directory, epee::byte_slice contents) noexcept
|
||||
{
|
||||
const int fd =
|
||||
open(filename.c_str(), O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
bool rc = false;
|
||||
if (0 <= write(fd, contents.data(), contents.size()) && 0 <= fsync(fd))
|
||||
{
|
||||
const int dir = open(directory.c_str(), O_RDONLY);
|
||||
if (0 <= dir)
|
||||
{
|
||||
if (0 <= fsync(dir))
|
||||
rc = true;
|
||||
close(dir);
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#error atomic_file_write not implemented for this platform
|
||||
#endif
|
||||
} // anonymous
|
||||
|
||||
void wallet::set_error(const std::error_code status)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
status_ = status;
|
||||
exception_error_.clear();
|
||||
}
|
||||
|
||||
wallet::wallet(std::string filename, std::shared_ptr<backend::wallet> data)
|
||||
: data_(std::move(data)), filename_(std::move(filename)), thread_()
|
||||
void wallet::set_critical(const std::exception& e)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
exception_error_ = e.what();
|
||||
status_.clear();
|
||||
}
|
||||
|
||||
wallet::wallet(create_tag, NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data)
|
||||
: data_(std::move(data)),
|
||||
filename_(std::move(filename)),
|
||||
password_(std::move(password)),
|
||||
exception_error_(),
|
||||
status_(),
|
||||
thread_(),
|
||||
iterations_(kdf_rounds),
|
||||
sync_()
|
||||
{
|
||||
if (!data_)
|
||||
throw std::logic_error{"Unepected nullptr in internal::wallet"};
|
||||
throw std::logic_error{"Unexpected nullptr in internal::wallet"};
|
||||
if (sodium_init() < 0)
|
||||
throw std::runtime_error{"Failed to initialize libsodium"};
|
||||
|
||||
// use libsodium random, more portable
|
||||
crypto::secret_key recovery;
|
||||
randombytes_buf(std::addressof(unwrap(unwrap(recovery))), sizeof(recovery));
|
||||
|
||||
cryptonote::account_base base{};
|
||||
base.generate(recovery, true);
|
||||
|
||||
const auto& keys = base.get_keys();
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
data_->primary.view.sec = keys.m_view_secret_key;
|
||||
data_->primary.view.pub = keys.m_account_address.m_view_public_key;
|
||||
|
||||
data_->primary.spend.sec = keys.m_spend_secret_key;
|
||||
data_->primary.spend.pub = keys.m_account_address.m_spend_public_key;
|
||||
|
||||
data_->primary.generated_locally = true;
|
||||
data_->primary.type = nettype;
|
||||
|
||||
store("");
|
||||
}
|
||||
|
||||
wallet::wallet(open_tag, NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data)
|
||||
: data_(std::move(data)),
|
||||
filename_(std::move(filename)),
|
||||
password_(std::move(password)),
|
||||
exception_error_(),
|
||||
status_(),
|
||||
thread_(),
|
||||
iterations_(kdf_rounds),
|
||||
sync_()
|
||||
{
|
||||
if (!data_)
|
||||
throw std::logic_error{"Unexpected nullptr in internal::wallet"};
|
||||
|
||||
try
|
||||
{
|
||||
epee::byte_slice file = try_load(filename + ".new");
|
||||
if (file.empty())
|
||||
file = try_load(filename);
|
||||
|
||||
if (!file.empty())
|
||||
{
|
||||
encrypted_file contents{};
|
||||
if (!(status_ = wire::msgpack::from_bytes(std::move(file), contents)))
|
||||
{
|
||||
epee::byte_slice payload = contents.get_payload(password_);
|
||||
if (!payload.empty())
|
||||
status_ = data_->from_bytes(std::move(payload));
|
||||
else
|
||||
status_ = error::crypto_failure;
|
||||
}
|
||||
}
|
||||
else
|
||||
status_ = error::read_failure;
|
||||
}
|
||||
catch (const std::system_error& e)
|
||||
{
|
||||
status_ = e.code();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
status_.clear();
|
||||
exception_error_ = e.what();
|
||||
}
|
||||
}
|
||||
|
||||
wallet::~wallet()
|
||||
{}
|
||||
|
||||
void wallet::statusWithErrorString(int& status, std::string& errorString) const
|
||||
int wallet::status() const
|
||||
{
|
||||
const boost::unique_lock<boost::mutex> lock{data_->sync};
|
||||
status = data_->status.value();
|
||||
if (status)
|
||||
errorString = data_->status.message();
|
||||
else
|
||||
errorString.clear();
|
||||
int out = 0;
|
||||
std::string unused;
|
||||
statusWithErrorString(out, unused);
|
||||
return out;
|
||||
}
|
||||
|
||||
NetworkType wallet::nettype() const { data_->primary.type; }
|
||||
std::string wallet::errorString() const
|
||||
{
|
||||
int unused = 0;
|
||||
std::string out;
|
||||
statusWithErrorString(unused, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
void wallet::statusWithErrorString(int& status, std::string& errorString) const
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
if (!exception_error_.empty())
|
||||
{
|
||||
status = Status_Critical;
|
||||
errorString = exception_error_;
|
||||
}
|
||||
else if (status_)
|
||||
{
|
||||
status = Status_Error;
|
||||
errorString = status_.message();
|
||||
}
|
||||
else
|
||||
{
|
||||
status = Status_Ok;
|
||||
errorString.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool wallet::setPassword(const std::string &password)
|
||||
{
|
||||
password_ = password;
|
||||
return true;
|
||||
}
|
||||
|
||||
NetworkType wallet::nettype() const { return data_->primary.type; }
|
||||
|
||||
std::string wallet::secretViewKey() const
|
||||
{
|
||||
@@ -94,22 +448,62 @@ namespace lwsf { namespace internal
|
||||
|
||||
bool wallet::store(const std::string& path)
|
||||
{
|
||||
expect<epee::byte_slice> bytes = data_->to_bytes();
|
||||
if (!bytes)
|
||||
return false;
|
||||
const std::string& real_path = path.empty() ? filename_ : path;
|
||||
|
||||
//! \TODO Write, fsync, then atomic swap filenames
|
||||
std::error_code status{};
|
||||
try
|
||||
{
|
||||
expect<epee::byte_slice> payload = data_->to_bytes();
|
||||
if (payload)
|
||||
{
|
||||
const auto contents = encrypted_file::make(*payload, iterations_, password_);
|
||||
const auto payload_size = payload->size();
|
||||
*payload = nullptr; // free up some memory that is no longer needed
|
||||
|
||||
std::ofstream file{path, std::ios::out | std::ios::binary};
|
||||
file.write(reinterpret_cast<const char*>(bytes->data()), bytes->size());
|
||||
file.flush();
|
||||
return file.good();
|
||||
epee::byte_stream buffer;
|
||||
buffer.reserve(payload_size + 2048);
|
||||
|
||||
if (!(status = wire::msgpack::to_bytes(buffer, contents)))
|
||||
{
|
||||
const std::filesystem::path file = real_path;
|
||||
const std::filesystem::path new_file = real_path + ".new";
|
||||
const std::filesystem::path directory =
|
||||
std::filesystem::path{real_path}.remove_filename();
|
||||
|
||||
if (std::filesystem::exists(new_file, status))
|
||||
std::filesystem::remove(file, status);
|
||||
std::filesystem::rename(new_file, file, status);
|
||||
|
||||
// blocks until file and directory contents are synced
|
||||
if (atomic_file_write(new_file, directory, epee::byte_slice{std::move(buffer)}))
|
||||
{
|
||||
std::filesystem::remove(file, status);
|
||||
std::filesystem::rename(new_file, file, status);
|
||||
}
|
||||
else
|
||||
status = error::write_failure;
|
||||
}
|
||||
}
|
||||
else
|
||||
status = payload.error();
|
||||
}
|
||||
catch (const std::system_error& e)
|
||||
{
|
||||
status = e.code();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
set_critical(e);
|
||||
}
|
||||
|
||||
// this will clear any existing errors
|
||||
set_error(status);
|
||||
}
|
||||
|
||||
bool wallet::init(const std::string &daemon_address, uint64_t, const std::string &daemon_username, const std::string &daemon_password, bool use_ssl, bool light_wallet, const std::string &proxy_address)
|
||||
{
|
||||
if (!light_wallet)
|
||||
throw std::runtime_error{"Only light_wallets are supported with this instance"};
|
||||
throw std::logic_error{"Only light_wallets are supported with this instance"};
|
||||
|
||||
const auto split = daemon_address.rfind(':');
|
||||
if (!setProxy(proxy_address))
|
||||
@@ -148,7 +542,7 @@ namespace lwsf { namespace internal
|
||||
Wallet::ConnectionStatus wallet::connected() const
|
||||
{
|
||||
return data_->client.is_connected() ?
|
||||
ConnectionStatus::Connected : ConnectionStatus::Disconnected;
|
||||
ConnectionStatus_Connected : ConnectionStatus_Disconnected;
|
||||
}
|
||||
|
||||
bool wallet::setProxy(const std::string &address)
|
||||
@@ -157,8 +551,7 @@ namespace lwsf { namespace internal
|
||||
if (!endpoint)
|
||||
{
|
||||
data_->client.set_connector(null_connector{});
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
data_->status = endpoint.error();
|
||||
set_error(endpoint.error());
|
||||
return false;
|
||||
}
|
||||
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
|
||||
@@ -177,9 +570,21 @@ namespace lwsf { namespace internal
|
||||
uint64_t wallet::daemonBlockChainTargetHeight() const
|
||||
{ return blockChainHeight(); }
|
||||
|
||||
|
||||
uint64_t wallet::estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
|
||||
PendingTransaction::Priority priority) const
|
||||
{
|
||||
throw std::runtime_error{"Not Implemented yet"};
|
||||
}
|
||||
|
||||
std::shared_ptr<TransactionHistory> wallet::history()
|
||||
{
|
||||
return std::make_shared<transaction_history>(data_);
|
||||
}
|
||||
|
||||
|
||||
void wallet::setListener(std::shared_ptr<WalletListener> listener)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
data_->listener = std::move(listener);
|
||||
}
|
||||
}} // lwsf // internal
|
||||
|
||||
+29
-11
@@ -30,7 +30,9 @@
|
||||
|
||||
#include "lws_frontend.h"
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "cryptonote_basic/account.h" // monero/src
|
||||
#include "net/http_client.h" // monero/contrib/epee/include
|
||||
@@ -41,15 +43,28 @@ namespace internal
|
||||
{
|
||||
namespace backend { struct wallet; }
|
||||
|
||||
|
||||
//! \TODO Mark final when completely implemented
|
||||
class wallet : public lwsf::Wallet
|
||||
{
|
||||
const std::shared_ptr<backend::wallet> data_;
|
||||
std::string filename_;
|
||||
const std::string filename_;
|
||||
std::string password_;
|
||||
std::string exception_error_;
|
||||
std::error_code status_;
|
||||
boost::thread thread_;
|
||||
|
||||
public:
|
||||
explicit wallet(std::string filename, std::shared_ptr<backend::wallet> data);
|
||||
const std::uint64_t iterations_;
|
||||
mutable boost::mutex sync_;
|
||||
|
||||
void set_error(const std::error_code status);
|
||||
void set_critical(const std::exception& e);
|
||||
|
||||
public:
|
||||
struct create_tag{};
|
||||
struct open_tag{};
|
||||
|
||||
wallet(create_tag, NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data);
|
||||
wallet(open_tag, NetworkType nettpe, std::string filename, std::string password, std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data);
|
||||
|
||||
wallet(const wallet&) = delete;
|
||||
wallet(wallet&&) = delete;
|
||||
@@ -62,10 +77,12 @@ public:
|
||||
virtual std::string getSeedLanguage() const = 0;
|
||||
virtual void setSeedLanguage(const std::string &arg) = 0;
|
||||
|
||||
virtual int status() const override;
|
||||
virtual std::string errorString() const override;
|
||||
virtual void statusWithErrorString(int& status, std::string& errorString) const override;
|
||||
|
||||
virtual bool setPassword(const std::string &password) = 0;
|
||||
virtual const std::string& getPassword() const = 0;
|
||||
virtual bool setPassword(const std::string &password) override;
|
||||
virtual const std::string& getPassword() const override { return password_; }
|
||||
|
||||
virtual bool setDevicePin(const std::string &) override { return false; };
|
||||
virtual bool setDevicePassphrase(const std::string &) override { return false; };
|
||||
@@ -212,8 +229,8 @@ public:
|
||||
* @return - true if connected
|
||||
*/
|
||||
virtual ConnectionStatus connected() const override;
|
||||
virtual void setTrustedDaemon(bool arg) = 0;
|
||||
virtual bool trustedDaemon() const = 0;
|
||||
virtual void setTrustedDaemon(bool) override {}
|
||||
virtual bool trustedDaemon() const override { return true; }
|
||||
virtual bool setProxy(const std::string &address) override;
|
||||
virtual uint64_t balance(uint32_t accountIndex = 0) const = 0;
|
||||
uint64_t balanceAll() const {
|
||||
@@ -499,7 +516,8 @@ public:
|
||||
* \brief disposeTransaction - destroys transaction object
|
||||
* \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned;
|
||||
*/
|
||||
virtual void disposeTransaction(PendingTransaction * t) = 0;
|
||||
virtual void disposeTransaction(PendingTransaction * t) override final
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Estimates transaction fee.
|
||||
@@ -507,7 +525,7 @@ public:
|
||||
* \return Estimated fee.
|
||||
*/
|
||||
virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
|
||||
PendingTransaction::Priority priority) const = 0;
|
||||
PendingTransaction::Priority priority) const override final;
|
||||
|
||||
/*!
|
||||
* \brief exportKeyImages - exports key images to file
|
||||
@@ -549,7 +567,7 @@ public:
|
||||
virtual std::shared_ptr<AddressBook> addressBook() = 0;
|
||||
virtual std::shared_ptr<Subaddress> subaddress() = 0;
|
||||
virtual std::shared_ptr<SubaddressAccount> subaddressAccount() = 0;
|
||||
virtual void setListener(WalletListener *) = 0;
|
||||
virtual void setListener(std::shared_ptr<WalletListener>) override;
|
||||
/*!
|
||||
* \brief defaultMixin - returns number of mixins used in transactions
|
||||
* \return
|
||||
|
||||
+30
-8
@@ -28,11 +28,14 @@
|
||||
|
||||
#include "lws_frontend.h"
|
||||
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include "backend.h"
|
||||
#include "common/dns_utils.h" // monero/src
|
||||
#include "lwsf_config.h"
|
||||
#include "net/http_client.h" // monero/contrib/epee/include
|
||||
#include "net/parse.h" // monero/src
|
||||
@@ -91,8 +94,9 @@ namespace lwsf
|
||||
*/
|
||||
std::unique_ptr<Wallet> createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds) override
|
||||
{
|
||||
//! \TODO Complete
|
||||
return std::make_unique<wallet>(path, std::make_shared<backend::wallet>(true));
|
||||
return std::make_unique<wallet>(
|
||||
wallet::create_tag{}, nettype, path, password, kdf_rounds, std::make_shared<backend::wallet>()
|
||||
);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -107,7 +111,11 @@ namespace lwsf
|
||||
|
||||
std::unique_ptr<Wallet> openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, std::shared_ptr<WalletListener> listener) override
|
||||
{
|
||||
//! \TODO Complete
|
||||
auto back = std::make_shared<backend::wallet>();
|
||||
back->listener = std::move(listener);
|
||||
return std::make_unique<wallet>(
|
||||
wallet::open_tag{}, nettype, path, password, kdf_rounds, std::move(back)
|
||||
);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -219,7 +227,7 @@ namespace lwsf
|
||||
* \param wallet previously opened / created wallet instance
|
||||
* \return None
|
||||
*/
|
||||
bool closeWallet(std::unique_ptr<Wallet> wallet, bool store) override
|
||||
bool closeWallet(std::unique_ptr<Wallet> wallet, const bool store) override
|
||||
{
|
||||
if (wallet && store)
|
||||
wallet->store(wallet->filename());
|
||||
@@ -236,7 +244,7 @@ namespace lwsf
|
||||
*/
|
||||
bool walletExists(const std::string &path) override
|
||||
{
|
||||
//! \TODO Complete
|
||||
return std::filesystem::exists(std::filesystem::path{path});
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -324,11 +332,25 @@ namespace lwsf
|
||||
|
||||
std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const override
|
||||
{
|
||||
//! \TODO Complete
|
||||
const boost::string_ref prefix{"oa1:xmr "};
|
||||
std::string real_address =
|
||||
tools::DNSResolver::instance().get_dns_format_from_oa_address(address);
|
||||
|
||||
bool dnssec_available = false;
|
||||
std::vector<std::string> records =
|
||||
tools::DNSResolver::instance().get_txt_record(real_address, dnssec_available, dnssec_valid);
|
||||
for (boost::string_ref record : records)
|
||||
{
|
||||
if (record.starts_with(prefix))
|
||||
{
|
||||
record = record.substr(prefix.size());
|
||||
return std::string{record.data(), record.size()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! checks for an update and returns version, hash and url
|
||||
static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(
|
||||
//! checks for an update and returns version, hash and url
|
||||
static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(
|
||||
const std::string &software,
|
||||
std::string subdir,
|
||||
const char *buildtag = nullptr,
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
set(monero-lws-wire_sources error.cpp read.cpp write.cpp)
|
||||
set(monero-lws-wire_headers error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
|
||||
set(monero-lws-wire_sources basic_value.cpp error.cpp read.cpp write.cpp)
|
||||
set(monero-lws-wire_headers basic_value.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
|
||||
|
||||
add_library(monero-lws-wire ${monero-lws-wire_sources} ${monero-lws-wire_headers})
|
||||
target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
|
||||
namespace wire
|
||||
{
|
||||
struct basic_value;
|
||||
class reader;
|
||||
struct writer;
|
||||
}
|
||||
|
||||
+105
-74
@@ -25,17 +25,18 @@
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "read.h"
|
||||
#include "serialization/wire/json/read.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <rapidjson/memorystream.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "common/expect.h" // monero/src
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
#include "wire/error.h"
|
||||
#include "wire/json/error.h"
|
||||
#include "byte_stream.h"
|
||||
#include "hex.h"
|
||||
#include "serialization/wire/basic_value.h"
|
||||
#include "serialization/wire/error.h"
|
||||
#include "serialization/wire/json/error.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -50,8 +51,7 @@ namespace
|
||||
//! \throw std::system_error by converting `code` into a std::error_code
|
||||
[[noreturn]] void throw_json_error(const epee::span<const std::uint8_t> source, const rapidjson::Reader& reader, const wire::error::schema expected)
|
||||
{
|
||||
const std::size_t offset = std::min(source.size(), reader.GetErrorOffset());
|
||||
const std::size_t start = offset;//std::max(snippet_size / 2, offset) - (snippet_size / 2);
|
||||
const std::size_t start = std::min(source.size(), reader.GetErrorOffset());
|
||||
const std::size_t end = start + std::min(snippet_size, source.size() - start);
|
||||
|
||||
const boost::string_ref text{reinterpret_cast<const char*>(source.data()) + start, end - start};
|
||||
@@ -64,7 +64,7 @@ namespace
|
||||
case rapidjson::kParseErrorTermination: // the handler returned false
|
||||
break;
|
||||
}
|
||||
WIRE_DLOG_THROW(expected, "near '" << text << '\'');
|
||||
WIRE_DLOG_THROW(expected, "near \"" << text << '"');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,36 +72,37 @@ namespace wire
|
||||
{
|
||||
struct json_reader::rapidjson_sax
|
||||
{
|
||||
struct string_contents
|
||||
{
|
||||
const char* ptr;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
bool boolean;
|
||||
std::intmax_t integer;
|
||||
std::uintmax_t unsigned_integer;
|
||||
double number;
|
||||
string_contents string;
|
||||
} value;
|
||||
|
||||
error::schema expected_;
|
||||
std::string* const string_;
|
||||
const error::schema expected_;
|
||||
error::schema actual_;
|
||||
bool negative;
|
||||
|
||||
explicit rapidjson_sax(error::schema expected) noexcept
|
||||
: expected_(expected), negative(false)
|
||||
rapidjson_sax(error::schema expected) noexcept
|
||||
: string_(nullptr), expected_(expected), actual_(error::schema::maximum_depth), negative(false)
|
||||
{}
|
||||
|
||||
bool Null() const noexcept
|
||||
explicit rapidjson_sax(std::string& string, error::schema expected) noexcept
|
||||
: string_(std::addressof(string)), expected_(expected), actual_(error::schema::maximum_depth), negative(false)
|
||||
{}
|
||||
|
||||
bool Null() noexcept
|
||||
{
|
||||
actual_ = error::schema::none;
|
||||
return expected_ == error::schema::none;
|
||||
}
|
||||
|
||||
bool Bool(bool i) noexcept
|
||||
{
|
||||
value.boolean = i;
|
||||
actual_ = error::schema::boolean;
|
||||
return expected_ == error::schema::boolean || expected_ == error::schema::none;
|
||||
}
|
||||
|
||||
@@ -120,13 +121,14 @@ namespace wire
|
||||
{
|
||||
default:
|
||||
return false;
|
||||
case error::schema::none:
|
||||
case error::schema::integer:
|
||||
value.integer = i;
|
||||
actual_ = error::schema::integer;
|
||||
break;
|
||||
case error::schema::number:
|
||||
value.number = i;
|
||||
break;
|
||||
case error::schema::none:
|
||||
actual_ = error::schema::number;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
@@ -137,13 +139,14 @@ namespace wire
|
||||
{
|
||||
default:
|
||||
return false;
|
||||
case error::schema::none:
|
||||
case error::schema::integer:
|
||||
value.unsigned_integer = i;
|
||||
actual_ = error::schema::integer;
|
||||
break;
|
||||
case error::schema::number:
|
||||
value.number = i;
|
||||
break;
|
||||
case error::schema::none:
|
||||
actual_ = error::schema::number;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
@@ -152,17 +155,20 @@ namespace wire
|
||||
bool Double(double i) noexcept
|
||||
{
|
||||
value.number = i;
|
||||
actual_ = error::schema::number;
|
||||
return expected_ == error::schema::number || expected_ == error::schema::none;
|
||||
}
|
||||
|
||||
bool RawNumber(const char*, std::size_t, bool) const noexcept
|
||||
static constexpr bool RawNumber(const char*, std::size_t, bool) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool String(const char* str, std::size_t length, bool) noexcept
|
||||
{
|
||||
value.string = {str, length};
|
||||
actual_ = error::schema::string;
|
||||
if (string_)
|
||||
string_->assign(str, length);
|
||||
return expected_ == error::schema::string || expected_ == error::schema::none;
|
||||
}
|
||||
bool Key(const char* str, std::size_t length, bool)
|
||||
@@ -215,20 +221,59 @@ namespace wire
|
||||
read_next_value(accept_all);
|
||||
}
|
||||
|
||||
json_reader::json_reader(std::string&& source)
|
||||
: reader(nullptr),
|
||||
source_(std::move(source)),
|
||||
reader_()
|
||||
std::size_t json_reader::do_start_array(std::size_t)
|
||||
{
|
||||
remaining_ = {reinterpret_cast<const std::uint8_t*>(source_.data()), source_.size()};
|
||||
if (get_next_token() != '[')
|
||||
WIRE_DLOG_THROW_(error::schema::array);
|
||||
remaining_.remove_prefix(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t json_reader::do_start_object()
|
||||
{
|
||||
if (get_next_token() != '{')
|
||||
WIRE_DLOG_THROW_(error::schema::object);
|
||||
remaining_.remove_prefix(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
json_reader::json_reader(epee::span<const char> source)
|
||||
: reader({reinterpret_cast<const std::uint8_t*>(source.data()), source.size()}),
|
||||
str_buffer_(),
|
||||
reader_()
|
||||
{}
|
||||
|
||||
void json_reader::check_complete() const
|
||||
{
|
||||
if (depth())
|
||||
WIRE_DLOG_THROW(error::rapidjson_e(rapidjson::kParseErrorUnspecificSyntaxError), "Unexpected end");
|
||||
}
|
||||
|
||||
basic_value json_reader::basic()
|
||||
{
|
||||
rapidjson_sax json_any{str_buffer_, error::schema::none};
|
||||
read_next_value(json_any);
|
||||
switch(json_any.actual_)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case error::schema::none:
|
||||
return {nullptr};
|
||||
case error::schema::boolean:
|
||||
return {json_any.value.boolean};
|
||||
case error::schema::integer:
|
||||
if (json_any.negative)
|
||||
return {json_any.value.integer};
|
||||
else
|
||||
return {json_any.value.unsigned_integer};
|
||||
case error::schema::number:
|
||||
return {json_any.value.number};
|
||||
case error::schema::string:
|
||||
return {std::move(str_buffer_)};
|
||||
};
|
||||
WIRE_DLOG_THROW(error::schema::number, "expected a boolean, integer, float or string");
|
||||
}
|
||||
|
||||
bool json_reader::boolean()
|
||||
{
|
||||
rapidjson_sax json_bool{error::schema::boolean};
|
||||
@@ -264,24 +309,24 @@ namespace wire
|
||||
WIRE_DLOG_THROW_(error::schema::larger_integer);
|
||||
return static_cast<std::uintmax_t>(json_uint.value.integer);
|
||||
}
|
||||
/*
|
||||
const std::vector<std::uintmax_t>& json_reader::unsigned_integer_array()
|
||||
{
|
||||
read_next_unsigned_array(
|
||||
}*/
|
||||
|
||||
std::uintmax_t json_reader::safe_unsigned_integer()
|
||||
{
|
||||
if (get_next_token() != '"')
|
||||
WIRE_DLOG_THROW_(error::schema::string);
|
||||
remaining_.remove_prefix(1);
|
||||
bool is_string = false;
|
||||
if (get_next_token() == '"')
|
||||
{
|
||||
remaining_.remove_prefix(1);
|
||||
is_string = true;
|
||||
}
|
||||
|
||||
const std::uintmax_t out = unsigned_integer();
|
||||
|
||||
if (get_next_token() != '"')
|
||||
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
|
||||
remaining_.remove_prefix(1);
|
||||
|
||||
if (is_string)
|
||||
{
|
||||
if (get_next_token() != '"')
|
||||
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
|
||||
remaining_.remove_prefix(1);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -294,38 +339,34 @@ namespace wire
|
||||
|
||||
std::string json_reader::string()
|
||||
{
|
||||
rapidjson_sax json_string{error::schema::string};
|
||||
read_next_value(json_string);
|
||||
return std::string{json_string.value.string.ptr, json_string.value.string.length};
|
||||
{
|
||||
rapidjson_sax json_string{str_buffer_, error::schema::string};
|
||||
read_next_value(json_string);
|
||||
}
|
||||
return std::string{std::move(str_buffer_)};
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> json_reader::binary()
|
||||
epee::byte_slice json_reader::binary()
|
||||
{
|
||||
const boost::string_ref value = get_next_string();
|
||||
|
||||
std::vector<std::uint8_t> out;
|
||||
out.resize(value.size() / 2);
|
||||
epee::byte_stream out{};
|
||||
out.put_n(0, value.size() / 2);
|
||||
|
||||
if (!epee::from_hex::to_buffer(epee::to_mut_span(out), value))
|
||||
WIRE_DLOG_THROW_(error::schema::binary);
|
||||
WIRE_DLOG_THROW(error::schema::binary, "invalid hex");
|
||||
|
||||
return out;
|
||||
return epee::byte_slice{std::move(out)};
|
||||
}
|
||||
|
||||
void json_reader::binary(epee::span<std::uint8_t> dest)
|
||||
std::size_t json_reader::binary(epee::span<std::uint8_t> dest, const bool exact)
|
||||
{
|
||||
const boost::string_ref value = get_next_string();
|
||||
if (!exact && value.size() / 2 <= dest.size())
|
||||
dest = {dest.data(), value.size() / 2}; // `from_hex` will ensure exact size
|
||||
if (!epee::from_hex::to_buffer(dest, value))
|
||||
WIRE_DLOG_THROW(error::schema::fixed_binary, "of size" << dest.size() * 2 << " but got " << value.size());
|
||||
}
|
||||
|
||||
std::size_t json_reader::start_array(std::size_t)
|
||||
{
|
||||
if (get_next_token() != '[')
|
||||
WIRE_DLOG_THROW_(error::schema::array);
|
||||
remaining_.remove_prefix(1);
|
||||
increment_depth();
|
||||
return 0;
|
||||
WIRE_DLOG_THROW(exact ? error::schema::fixed_binary : error::schema::binary, "invalid hex and/or expected size " << dest.size() * 2 << " but got " << value.size());
|
||||
return dest.size();
|
||||
}
|
||||
|
||||
bool json_reader::is_array_end(const std::size_t count)
|
||||
@@ -348,21 +389,11 @@ namespace wire
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t json_reader::start_object()
|
||||
{
|
||||
if (get_next_token() != '{')
|
||||
WIRE_DLOG_THROW_(error::schema::object);
|
||||
remaining_.remove_prefix(1);
|
||||
increment_depth();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool json_reader::key(const epee::span<const key_map> map, std::size_t& state, std::size_t& index)
|
||||
{
|
||||
rapidjson_sax json_key{error::schema::string};
|
||||
const auto process_key = [map] (const rapidjson_sax::string_contents value)
|
||||
rapidjson_sax json_key{str_buffer_, error::schema::string};
|
||||
const auto process_key = [map] (const boost::string_ref key)
|
||||
{
|
||||
const boost::string_ref key{value.ptr, value.length};
|
||||
for (std::size_t i = 0; i < map.size(); ++i)
|
||||
{
|
||||
if (map[i].name == key)
|
||||
@@ -395,7 +426,7 @@ namespace wire
|
||||
|
||||
// parse key
|
||||
read_next_value(json_key);
|
||||
index = process_key(json_key.value.string);
|
||||
index = process_key(str_buffer_);
|
||||
if (get_next_token() != ':')
|
||||
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissColon));
|
||||
remaining_.remove_prefix(1);
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "wire/field.h"
|
||||
#include "wire/fwd.h"
|
||||
#include "wire/json/base.h"
|
||||
#include "wire/read.h"
|
||||
#include "wire/traits.h"
|
||||
@@ -63,6 +64,9 @@ namespace wire
|
||||
//! \throw wire::exception if JSON parsing is incomplete.
|
||||
void check_complete() const override final;
|
||||
|
||||
//! \throw wire::exception if array, object, or end of stream.
|
||||
basic_value basic();
|
||||
|
||||
//! \throw wire::exception if next token not a boolean.
|
||||
bool boolean() override final;
|
||||
|
||||
@@ -72,7 +76,7 @@ namespace wire
|
||||
//! \throw wire::exception if next token not an unsigned integer.
|
||||
std::uintmax_t unsigned_integer() override final;
|
||||
|
||||
//! \throw wire::exception if next token is not an integer encoded as string
|
||||
//! \throw wire::exception if next token is not an integer encoded as string OR unsigned integer
|
||||
std::uintmax_t safe_unsigned_integer();
|
||||
|
||||
//! \throw wire::exception if next token not a valid real number
|
||||
@@ -82,7 +86,7 @@ namespace wire
|
||||
std::string string() override final;
|
||||
|
||||
//! \throw wire::exception if next token cannot be read as hex
|
||||
std::vector<std::uint8_t> binary() override final;
|
||||
epee::byte_slice binary() override final;
|
||||
|
||||
//! \throw wire::exception if next token cannot be read as hex into `dest`.
|
||||
void binary(epee::span<std::uint8_t> dest) override final;
|
||||
|
||||
@@ -413,11 +413,12 @@ namespace wire
|
||||
return std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()};
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> msgpack_reader::binary()
|
||||
epee::byte_slice msgpack_reader::binary()
|
||||
{
|
||||
update_tags_remaining();
|
||||
const epee::span<const std::uint8_t> bytes = read_binary(remaining_, get_tag());
|
||||
return std::vector<std::uint8_t>{bytes.begin(), bytes.end()};
|
||||
const std::size_t begin = bytes.data() - source_.data();
|
||||
return source_.get_slice(begin, begin + bytes.size());
|
||||
}
|
||||
|
||||
void msgpack_reader::binary(epee::span<std::uint8_t> dest)
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace wire
|
||||
std::string string() override final;
|
||||
|
||||
//! \throw wire::exception if next token cannot be read as hex
|
||||
std::vector<std::uint8_t> binary() override final;
|
||||
epee::byte_slice binary() override final;
|
||||
|
||||
//! \throw wire::exception if next token cannot be read as hex into `dest`.
|
||||
void binary(epee::span<std::uint8_t> dest) override final;
|
||||
|
||||
+3
-3
@@ -32,8 +32,8 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "common/expect.h" // monero/src
|
||||
#include "span.h" // monero/contrib/epee/include
|
||||
#include "wire/error.h"
|
||||
@@ -106,7 +106,7 @@ namespace wire
|
||||
virtual std::string string() = 0;
|
||||
|
||||
// ! \throw wire::exception if next value cannot be read as binary
|
||||
virtual std::vector<std::uint8_t> binary() = 0;
|
||||
virtual epee::byte_slice binary() = 0;
|
||||
|
||||
//! \throw wire::exception if next value cannot be read as binary into `dest`.
|
||||
virtual void binary(epee::span<std::uint8_t> dest) = 0;
|
||||
@@ -163,7 +163,7 @@ namespace wire
|
||||
{ dest = source.string(); }
|
||||
|
||||
template<typename R>
|
||||
inline void read_bytes(R& source, std::vector<std::uint8_t>& dest)
|
||||
inline void read_bytes(R& source, epee::byte_slice& dest)
|
||||
{ dest = source.binary(); }
|
||||
|
||||
template<typename R, typename T>
|
||||
|
||||
Reference in New Issue
Block a user