mirror of
https://codeberg.org/wownero/wownero-lws
synced 2026-01-09 23:25:16 -08:00
339 lines
12 KiB
C++
339 lines
12 KiB
C++
// Copyright (c) 2018-2020, The Monero Project
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
// permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
// conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
// materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// 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 "light_wallet.h"
|
|
|
|
#include <boost/range/adaptor/indexed.hpp>
|
|
#include <ctime>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <type_traits>
|
|
|
|
#include "db/string.h"
|
|
#include "error.h"
|
|
#include "misc_os_dependent.h" // monero/contrib/epee/include
|
|
#include "ringct/rctOps.h" // monero/src
|
|
#include "span.h" // monero/contrib/epee/include
|
|
#include "util/random_outputs.h"
|
|
#include "wire/crypto.h"
|
|
#include "wire/error.h"
|
|
#include "wire/json.h"
|
|
#include "wire/traits.h"
|
|
#include "wire/vector.h"
|
|
|
|
namespace
|
|
{
|
|
enum class iso_timestamp : std::uint64_t {};
|
|
|
|
struct rct_bytes
|
|
{
|
|
rct::key commitment;
|
|
rct::key mask;
|
|
rct::key amount;
|
|
};
|
|
static_assert(sizeof(rct_bytes) == 32 * 3, "padding in rct struct");
|
|
|
|
struct expand_outputs
|
|
{
|
|
const std::pair<lws::db::output, std::vector<crypto::key_image>>& data;
|
|
const crypto::secret_key& user_key;
|
|
};
|
|
} // anonymous
|
|
|
|
namespace wire
|
|
{
|
|
template<>
|
|
struct is_blob<rct_bytes>
|
|
: std::true_type
|
|
{};
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void write_bytes(wire::json_writer& dest, const iso_timestamp self)
|
|
{
|
|
static_assert(std::is_integral<std::time_t>::value, "unexpected time_t type");
|
|
if (std::numeric_limits<std::time_t>::max() < std::uint64_t(self))
|
|
throw std::runtime_error{"Exceeded max time_t value"};
|
|
|
|
std::tm value;
|
|
if (!epee::misc_utils::get_gmt_time(std::time_t(self), value))
|
|
throw std::runtime_error{"Failed to convert std::time_t to std::tm"};
|
|
|
|
char buf[21] = {0};
|
|
if (sizeof(buf) - 1 != std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::addressof(value)))
|
|
throw std::runtime_error{"strftime failed"};
|
|
|
|
dest.string({buf, sizeof(buf) - 1});
|
|
}
|
|
|
|
void write_bytes(wire::json_writer& dest, const expand_outputs self)
|
|
{
|
|
/*! \TODO Sending the public key for the output isn't necessary, as it can be
|
|
re-computed from the other parts. Same with the rct commitment and rct
|
|
amount. Consider dropping these from the API after client upgrades. Not
|
|
storing them in the DB saves 96-bytes per received out. */
|
|
|
|
rct_bytes rct{};
|
|
rct_bytes const* optional_rct = nullptr;
|
|
if (unpack(self.data.first.extra).first & lws::db::ringct_output)
|
|
{
|
|
crypto::key_derivation derived;
|
|
if (!crypto::generate_key_derivation(self.data.first.spend_meta.tx_public, self.user_key, derived))
|
|
MONERO_THROW(lws::error::crypto_failure, "generate_key_derivation failed");
|
|
|
|
crypto::secret_key scalar;
|
|
rct::ecdhTuple encrypted{self.data.first.ringct_mask, rct::d2h(self.data.first.spend_meta.amount)};
|
|
|
|
crypto::derivation_to_scalar(derived, self.data.first.spend_meta.index, scalar);
|
|
rct::ecdhEncode(encrypted, rct::sk2rct(scalar), false);
|
|
|
|
rct.commitment = rct::commit(self.data.first.spend_meta.amount, self.data.first.ringct_mask);
|
|
rct.mask = encrypted.mask;
|
|
rct.amount = encrypted.amount;
|
|
|
|
optional_rct = std::addressof(rct);
|
|
}
|
|
|
|
wire::object(dest,
|
|
wire::field("amount", lws::rpc::safe_uint64(self.data.first.spend_meta.amount)),
|
|
wire::field("public_key", self.data.first.pub),
|
|
wire::field("index", self.data.first.spend_meta.index),
|
|
wire::field("global_index", self.data.first.spend_meta.id.low),
|
|
wire::field("tx_id", self.data.first.spend_meta.id.low),
|
|
wire::field("tx_hash", std::cref(self.data.first.link.tx_hash)),
|
|
wire::field("tx_prefix_hash", std::cref(self.data.first.tx_prefix_hash)),
|
|
wire::field("tx_pub_key", self.data.first.spend_meta.tx_public),
|
|
wire::field("timestamp", iso_timestamp(self.data.first.timestamp)),
|
|
wire::field("height", self.data.first.link.height),
|
|
wire::field("spend_key_images", std::cref(self.data.second)),
|
|
wire::optional_field("rct", optional_rct)
|
|
);
|
|
}
|
|
|
|
void convert_address(const boost::string_ref source, lws::db::account_address& dest)
|
|
{
|
|
expect<lws::db::account_address> bytes = lws::db::address_string(source);
|
|
if (!bytes)
|
|
WIRE_DLOG_THROW(wire::error::schema::fixed_binary, "invalid Monero address format - " << bytes.error());
|
|
dest = std::move(*bytes);
|
|
}
|
|
} // anonymous
|
|
|
|
namespace lws
|
|
{
|
|
static void write_bytes(wire::json_writer& dest, random_output const& self)
|
|
{
|
|
const rct_bytes rct{self.keys.mask, rct::zero(), rct::zero()};
|
|
wire::object(dest,
|
|
wire::field("global_index", rpc::safe_uint64(self.index)),
|
|
wire::field("public_key", std::cref(self.keys.key)),
|
|
wire::field("rct", std::cref(rct))
|
|
);
|
|
}
|
|
static void write_bytes(wire::json_writer& dest, random_ring const& self)
|
|
{
|
|
wire::object(dest,
|
|
wire::field("amount", rpc::safe_uint64(self.amount)),
|
|
wire::field("outputs", std::cref(self.ring))
|
|
);
|
|
};
|
|
|
|
void rpc::read_bytes(wire::json_reader& source, safe_uint64& self)
|
|
{
|
|
self = safe_uint64(wire::integer::convert_to<std::uint64_t>(source.safe_unsigned_integer()));
|
|
}
|
|
void rpc::write_bytes(wire::json_writer& dest, const safe_uint64 self)
|
|
{
|
|
auto buf = wire::json_writer::to_string(std::uint64_t(self));
|
|
dest.string(buf.data());
|
|
}
|
|
void rpc::read_bytes(wire::json_reader& source, safe_uint64_array& self)
|
|
{
|
|
for (std::size_t count = source.start_array(); !source.is_array_end(count); --count)
|
|
self.values.emplace_back(wire::integer::convert_to<std::uint64_t>(source.safe_unsigned_integer()));
|
|
source.end_array();
|
|
}
|
|
|
|
void rpc::read_bytes(wire::json_reader& source, account_credentials& self)
|
|
{
|
|
std::string address;
|
|
wire::object(source,
|
|
wire::field("address", std::ref(address)),
|
|
wire::field("view_key", std::ref(unwrap(unwrap(self.key))))
|
|
);
|
|
convert_address(address, self.address);
|
|
}
|
|
|
|
void rpc::write_bytes(wire::json_writer& dest, const transaction_spend& self)
|
|
{
|
|
wire::object(dest,
|
|
wire::field("amount", safe_uint64(self.meta.amount)),
|
|
wire::field("key_image", std::cref(self.possible_spend.image)),
|
|
wire::field("tx_pub_key", std::cref(self.meta.tx_public)),
|
|
wire::field("out_index", self.meta.index),
|
|
wire::field("mixin", self.possible_spend.mixin_count)
|
|
);
|
|
}
|
|
|
|
void rpc::write_bytes(wire::json_writer& dest, const get_address_info_response& self)
|
|
{
|
|
wire::object(dest,
|
|
WIRE_FIELD_COPY(locked_funds),
|
|
WIRE_FIELD_COPY(total_received),
|
|
WIRE_FIELD_COPY(total_sent),
|
|
WIRE_FIELD_COPY(scanned_height),
|
|
WIRE_FIELD_COPY(scanned_block_height),
|
|
WIRE_FIELD_COPY(start_height),
|
|
WIRE_FIELD_COPY(transaction_height),
|
|
WIRE_FIELD_COPY(blockchain_height),
|
|
WIRE_FIELD(spent_outputs),
|
|
WIRE_OPTIONAL_FIELD(rates)
|
|
);
|
|
}
|
|
|
|
namespace rpc
|
|
{
|
|
static void write_bytes(wire::json_writer& dest, boost::range::index_value<const get_address_txs_response::transaction&> self)
|
|
{
|
|
epee::span<const std::uint8_t> const* payment_id = nullptr;
|
|
epee::span<const std::uint8_t> payment_id_bytes;
|
|
|
|
const auto extra = db::unpack(self.value().info.extra);
|
|
if (extra.second)
|
|
{
|
|
payment_id = std::addressof(payment_id_bytes);
|
|
|
|
if (extra.second == sizeof(self.value().info.payment_id.short_))
|
|
payment_id_bytes = epee::as_byte_span(self.value().info.payment_id.short_);
|
|
else
|
|
payment_id_bytes = epee::as_byte_span(self.value().info.payment_id.long_);
|
|
}
|
|
|
|
const bool is_coinbase = (extra.first & db::coinbase_output);
|
|
|
|
wire::object(dest,
|
|
wire::field("id", std::uint64_t(self.index())),
|
|
wire::field("hash", std::cref(self.value().info.link.tx_hash)),
|
|
wire::field("timestamp", iso_timestamp(self.value().info.timestamp)),
|
|
wire::field("total_received", safe_uint64(self.value().info.spend_meta.amount)),
|
|
wire::field("total_sent", safe_uint64(self.value().spent)),
|
|
wire::field("unlock_time", self.value().info.unlock_time),
|
|
wire::field("height", self.value().info.link.height),
|
|
wire::optional_field("payment_id", payment_id),
|
|
wire::field("coinbase", is_coinbase),
|
|
wire::field("mempool", false),
|
|
wire::field("mixin", self.value().info.spend_meta.mixin_count),
|
|
wire::field("spent_outputs", std::cref(self.value().spends))
|
|
);
|
|
}
|
|
} // rpc
|
|
void rpc::write_bytes(wire::json_writer& dest, const get_address_txs_response& self)
|
|
{
|
|
wire::object(dest,
|
|
wire::field("total_received", safe_uint64(self.total_received)),
|
|
WIRE_FIELD_COPY(scanned_height),
|
|
WIRE_FIELD_COPY(scanned_block_height),
|
|
WIRE_FIELD_COPY(start_height),
|
|
WIRE_FIELD_COPY(transaction_height),
|
|
WIRE_FIELD_COPY(blockchain_height),
|
|
wire::field("transactions", wire::as_array(boost::adaptors::index(self.transactions)))
|
|
);
|
|
}
|
|
|
|
void rpc::read_bytes(wire::json_reader& source, get_random_outs_request& self)
|
|
{
|
|
wire::object(source, WIRE_FIELD(count), WIRE_FIELD(amounts));
|
|
}
|
|
void rpc::write_bytes(wire::json_writer& dest, const get_random_outs_response& self)
|
|
{
|
|
wire::object(dest, WIRE_FIELD(amount_outs));
|
|
}
|
|
|
|
void rpc::read_bytes(wire::json_reader& source, get_unspent_outs_request& self)
|
|
{
|
|
std::string address;
|
|
wire::object(source,
|
|
wire::field("address", std::ref(address)),
|
|
wire::field("view_key", std::ref(unwrap(unwrap(self.creds.key)))),
|
|
WIRE_FIELD(amount),
|
|
WIRE_OPTIONAL_FIELD(mixin),
|
|
WIRE_OPTIONAL_FIELD(use_dust),
|
|
WIRE_OPTIONAL_FIELD(dust_threshold)
|
|
);
|
|
convert_address(address, self.creds.address);
|
|
}
|
|
void rpc::write_bytes(wire::json_writer& dest, const get_unspent_outs_response& self)
|
|
{
|
|
const auto expand = [&self] (const std::pair<db::output, std::vector<crypto::key_image>>& src)
|
|
{
|
|
return expand_outputs{src, self.user_key};
|
|
};
|
|
wire::object(dest,
|
|
wire::field(self.use_per_byte_fee ? "per_byte_fee" : "per_kb_fee", self.base_fee),
|
|
WIRE_FIELD_COPY(fee_mask),
|
|
WIRE_FIELD_COPY(amount),
|
|
wire::field("outputs", wire::as_array(std::cref(self.outputs), expand))
|
|
);
|
|
}
|
|
|
|
void rpc::write_bytes(wire::json_writer& dest, const import_response& self)
|
|
{
|
|
wire::object(dest,
|
|
WIRE_FIELD_COPY(import_fee),
|
|
WIRE_FIELD_COPY(status),
|
|
WIRE_FIELD_COPY(new_request),
|
|
WIRE_FIELD_COPY(request_fulfilled)
|
|
);
|
|
}
|
|
|
|
void rpc::read_bytes(wire::json_reader& source, login_request& self)
|
|
{
|
|
std::string address;
|
|
wire::object(source,
|
|
wire::field("address", std::ref(address)),
|
|
wire::field("view_key", std::ref(unwrap(unwrap(self.creds.key)))),
|
|
WIRE_FIELD(create_account),
|
|
WIRE_FIELD(generated_locally)
|
|
);
|
|
convert_address(address, self.creds.address);
|
|
}
|
|
void rpc::write_bytes(wire::json_writer& dest, const login_response self)
|
|
{
|
|
wire::object(dest, WIRE_FIELD_COPY(new_address), WIRE_FIELD_COPY(generated_locally));
|
|
}
|
|
|
|
void rpc::read_bytes(wire::json_reader& source, submit_raw_tx_request& self)
|
|
{
|
|
wire::object(source, WIRE_FIELD(tx));
|
|
}
|
|
void rpc::write_bytes(wire::json_writer& dest, const submit_raw_tx_response self)
|
|
{
|
|
wire::object(dest, WIRE_FIELD_COPY(status));
|
|
}
|
|
} // lws
|