Skip to content

Commit

Permalink
Add (working draft) subaddress support (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
vtnerd authored Dec 6, 2023
1 parent 9f41642 commit 29d3859
Show file tree
Hide file tree
Showing 21 changed files with 1,539 additions and 88 deletions.
21 changes: 14 additions & 7 deletions src/db/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ namespace lws
crypto::secret_key view_key;
};

account::account(std::shared_ptr<const internal> immutable, db::block_id height, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs) noexcept
account::account(std::shared_ptr<const internal> immutable, db::block_id height, std::vector<std::pair<db::output_id, db::address_index>> spendable, std::vector<crypto::public_key> pubs) noexcept
: immutable_(std::move(immutable))
, spendable_(std::move(spendable))
, pubs_(std::move(pubs))
, spends_()
, spends_()
, outputs_()
, height_(height)
{}
Expand All @@ -87,7 +87,7 @@ namespace lws
MONERO_THROW(::common_error::kInvalidArgument, "using moved from account");
}

account::account(db::account const& source, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs)
account::account(db::account const& source, std::vector<std::pair<db::output_id, db::address_index>> spendable, std::vector<crypto::public_key> pubs)
: account(std::make_shared<internal>(source), source.scan_height, std::move(spendable), std::move(pubs))
{
std::sort(spendable_.begin(), spendable_.end());
Expand Down Expand Up @@ -151,9 +151,15 @@ namespace lws
return immutable_->view_key;
}

bool account::has_spendable(db::output_id const& id) const noexcept
boost::optional<db::address_index> account::get_spendable(db::output_id const& id) const noexcept
{
return std::binary_search(spendable_.begin(), spendable_.end(), id);
const auto searchable =
std::make_pair(id, db::address_index{db::major_index::primary, db::minor_index::primary});
const auto account =
std::lower_bound(spendable_.begin(), spendable_.end(), searchable);
if (account == spendable_.end() || account->first != id)
return boost::none;
return account->second;
}

bool account::add_out(db::output const& out)
Expand All @@ -163,9 +169,10 @@ namespace lws
return false;

pubs_.insert(existing_pub, out.pub);
auto spendable_value = std::make_pair(out.spend_meta.id, out.recipient);
spendable_.insert(
std::lower_bound(spendable_.begin(), spendable_.end(), out.spend_meta.id),
out.spend_meta.id
std::lower_bound(spendable_.begin(), spendable_.end(), spendable_value),
spendable_value
);
outputs_.push_back(out);
return true;
Expand Down
12 changes: 7 additions & 5 deletions src/db/account.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once

#include <boost/optional/optional.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "crypto/crypto.h"
#include "fwd.h"
#include "db/data.h"
#include "db/fwd.h"

namespace lws
Expand All @@ -43,19 +45,19 @@ namespace lws
struct internal;

std::shared_ptr<const internal> immutable_;
std::vector<db::output_id> spendable_;
std::vector<std::pair<db::output_id, db::address_index>> spendable_;
std::vector<crypto::public_key> pubs_;
std::vector<db::spend> spends_;
std::vector<db::output> outputs_;
db::block_id height_;

explicit account(std::shared_ptr<const internal> immutable, db::block_id height, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs) noexcept;
explicit account(std::shared_ptr<const internal> immutable, db::block_id height, std::vector<std::pair<db::output_id, db::address_index>> spendable, std::vector<crypto::public_key> pubs) noexcept;
void null_check() const;

public:

//! Construct an account from `source` and current `spendable` outputs.
explicit account(db::account const& source, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs);
explicit account(db::account const& source, std::vector<std::pair<db::output_id, db::address_index>> spendable, std::vector<crypto::public_key> pubs);

/*!
\return False if this is a "moved-from" account (i.e. the internal memory
Expand Down Expand Up @@ -96,8 +98,8 @@ namespace lws
//! \return Current scan height of `this`.
db::block_id scan_height() const noexcept { return height_; }

//! \return True iff `id` is spendable by `this`.
bool has_spendable(db::output_id const& id) const noexcept;
//! \return Subaddress index iff `id` is spendable by `this`.
boost::optional<db::address_index> get_spendable(db::output_id const& id) const noexcept;

//! \return Outputs matched during the latest scan.
std::vector<db::output> const& outputs() const noexcept { return outputs_; }
Expand Down
108 changes: 106 additions & 2 deletions src/db/data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,18 @@
#include <cstring>
#include <memory>

#include "cryptonote_config.h" // monero/src
#include "db/string.h"
#include "int-util.h" // monero/contribe/epee/include
#include "ringct/rctOps.h" // monero/src
#include "ringct/rctTypes.h" // monero/src
#include "wire.h"
#include "wire/adapted/array.h"
#include "wire/crypto.h"
#include "wire/json/write.h"
#include "wire/msgpack.h"
#include "wire/uuid.h"
#include "wire/vector.h"
#include "wire/wrapper/defaulted.h"

namespace lws
Expand Down Expand Up @@ -69,6 +75,102 @@ namespace db
}
WIRE_DEFINE_OBJECT(account_address, map_account_address);

namespace
{
template<typename F, typename T>
void map_subaddress_dict(F& format, T& self)
{
wire::object(format,
wire::field<0>("key", std::ref(self.first)),
wire::field<1>("value", std::ref(self.second))
);
}
}

bool check_subaddress_dict(const subaddress_dict& self)
{
bool is_first = true;
minor_index last = minor_index::primary;
for (const auto& elem : self.second)
{
if (elem[1] < elem[0])
{
MERROR("Invalid subaddress_range (last before first");
return false;
}
if (std::uint32_t(elem[0]) <= std::uint64_t(last) + 1 && !is_first)
{
MERROR("Invalid subaddress_range (overlapping with previous)");
return false;
}
is_first = false;
last = elem[1];
}
return true;
}
void read_bytes(wire::reader& source, subaddress_dict& dest)
{
map_subaddress_dict(source, dest);
if (!check_subaddress_dict(dest))
WIRE_DLOG_THROW_(wire::error::schema::array);
}
void write_bytes(wire::writer& dest, const subaddress_dict& source)
{
if (!check_subaddress_dict(source))
WIRE_DLOG_THROW_(wire::error::schema::array);
map_subaddress_dict(dest, source);
}

namespace
{
template<typename F, typename T>
void map_address_index(F& format, T& self)
{
wire::object(format, WIRE_FIELD_ID(0, maj_i), WIRE_FIELD_ID(1, min_i));
}

crypto::secret_key get_subaddress_secret_key(const crypto::secret_key &a, const std::uint32_t major, const std::uint32_t minor)
{
char data[sizeof(config::HASH_KEY_SUBADDRESS) + sizeof(crypto::secret_key) + 2 * sizeof(uint32_t)];
memcpy(data, config::HASH_KEY_SUBADDRESS, sizeof(config::HASH_KEY_SUBADDRESS));
memcpy(data + sizeof(config::HASH_KEY_SUBADDRESS), &a, sizeof(crypto::secret_key));
std::uint32_t idx = SWAP32LE(major);
memcpy(data + sizeof(config::HASH_KEY_SUBADDRESS) + sizeof(crypto::secret_key), &idx, sizeof(uint32_t));
idx = SWAP32LE(minor);
memcpy(data + sizeof(config::HASH_KEY_SUBADDRESS) + sizeof(crypto::secret_key) + sizeof(uint32_t), &idx, sizeof(uint32_t));
crypto::secret_key m;
crypto::hash_to_scalar(data, sizeof(data), m);
return m;
}
}
WIRE_DEFINE_OBJECT(address_index, map_address_index);

crypto::public_key address_index::get_spend_public(account_address const& base, crypto::secret_key const& view) const
{
if (is_zero())
return base.spend_public;

// m = Hs(a || index_major || index_minor)
crypto::secret_key m = get_subaddress_secret_key(view, std::uint32_t(maj_i), std::uint32_t(min_i));

// M = m*G
crypto::public_key M;
crypto::secret_key_to_public_key(m, M);

// D = B + M
return rct::rct2pk(rct::addKeys(rct::pk2rct(base.spend_public), rct::pk2rct(M)));
}

namespace
{
template<typename F, typename T>
void map_subaddress_map(F& format, T& self)
{
wire::object(format, WIRE_FIELD_ID(0, subaddress), WIRE_FIELD_ID(1, index));
}
}
WIRE_DEFINE_OBJECT(subaddress_map, map_subaddress_map);

void write_bytes(wire::writer& dest, const account& self, const bool show_key)
{
view_key const* const key =
Expand Down Expand Up @@ -144,7 +246,8 @@ namespace db
wire::field<10>("unlock_time", self.unlock_time),
wire::field<11>("mixin_count", self.spend_meta.mixin_count),
wire::field<12>("coinbase", coinbase),
wire::field<13>("fee", self.fee)
wire::field<13>("fee", self.fee),
wire::field<14>("recipient", self.recipient)
);
}

Expand All @@ -161,7 +264,8 @@ namespace db
WIRE_FIELD(timestamp),
WIRE_FIELD(unlock_time),
WIRE_FIELD(mixin_count),
wire::optional_field("payment_id", std::ref(payment_id))
wire::optional_field("payment_id", std::ref(payment_id)),
WIRE_FIELD(sender)
);
}
}
Expand Down
64 changes: 62 additions & 2 deletions src/db/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once

#include <array>
#include <boost/uuid/uuid.hpp>
#include <cassert>
#include <cstdint>
#include <iosfwd>
#include <limits>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "crypto/crypto.h"
#include "lmdb/util.h"
Expand Down Expand Up @@ -122,6 +125,49 @@ namespace db
static_assert(sizeof(account_address) == 64, "padding in account_address");
WIRE_DECLARE_OBJECT(account_address);

//! Major index of a subaddress
enum class major_index : std::uint32_t { primary = 0 };
WIRE_AS_INTEGER(major_index);

//! Minor index of a subaddress
enum class minor_index : std::uint32_t { primary = 0 };
WIRE_AS_INTEGER(minor_index);

//! Range within a major index
using index_range = std::array<minor_index, 2>;

//! Ranges within a major index
using index_ranges = std::vector<index_range>;

//! Compatible with msgpack_table
using subaddress_dict = std::pair<major_index, index_ranges>;
bool check_subaddress_dict(const subaddress_dict&);
WIRE_DECLARE_OBJECT(subaddress_dict);

//! A specific (sub)address index
struct address_index
{
major_index maj_i;
minor_index min_i;

crypto::public_key get_spend_public(account_address const& base, crypto::secret_key const& view) const;
constexpr bool is_zero() const noexcept
{
return maj_i == major_index::primary && min_i == minor_index::primary;
}
};
static_assert(sizeof(address_index) == 4 * 2, "padding in address_index");
WIRE_DECLARE_OBJECT(address_index);

//! Maps a subaddress pubkey to its index values
struct subaddress_map
{
crypto::public_key subaddress; //!< Must be first for LMDB optimzations
address_index index;
};
static_assert(sizeof(subaddress_map) == 32 + 4 * 2, "padding in subaddress_map");
WIRE_DECLARE_OBJECT(subaddress_map);

struct account
{
account_id id; //!< Must be first for LMDB optimizations
Expand Down Expand Up @@ -205,9 +251,10 @@ namespace db
crypto::hash long_; //!< Long version of payment id (always decrypted)
} payment_id;
std::uint64_t fee; //!< Total fee for transaction
address_index recipient;
};
static_assert(
sizeof(output) == 8 + 32 + (8 * 3) + (4 * 2) + 32 + (8 * 2) + (32 * 3) + 7 + 1 + 32 + 8,
sizeof(output) == 8 + 32 + (8 * 3) + (4 * 2) + 32 + (8 * 2) + (32 * 3) + 7 + 1 + 32 + 8 + 2 * 4,
"padding in output"
);
void write_bytes(wire::writer&, const output&);
Expand All @@ -225,8 +272,9 @@ namespace db
char reserved[3];
std::uint8_t length; //!< Length of `payment_id` field (0..32).
crypto::hash payment_id; //!< Unencrypted only, can't decrypt spend
address_index sender;
};
static_assert(sizeof(spend) == 8 + 32 * 2 + 8 * 4 + 4 + 3 + 1 + 32, "padding in spend");
static_assert(sizeof(spend) == 8 + 32 * 2 + 8 * 4 + 4 + 3 + 1 + 32 + 2 * 4, "padding in spend");
WIRE_DECLARE_OBJECT(spend);

//! Key image and info needed to retrieve primary `spend` data.
Expand Down Expand Up @@ -325,6 +373,18 @@ namespace db
};
void write_bytes(wire::writer&, const webhook_new_account&);

inline constexpr bool operator==(address_index const& left, address_index const& right) noexcept
{
return left.maj_i == right.maj_i && left.min_i == right.min_i;
}

inline constexpr bool operator<(address_index const& left, address_index const& right) noexcept
{
return left.maj_i == right.maj_i ?
left.min_i < right.min_i : left.maj_i < right.maj_i;
}


bool operator==(transaction_link const& left, transaction_link const& right) noexcept;
bool operator<(transaction_link const& left, transaction_link const& right) noexcept;
bool operator<=(transaction_link const& left, transaction_link const& right) noexcept;
Expand Down
12 changes: 12 additions & 0 deletions src/db/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,30 @@ namespace db
enum class block_id : std::uint64_t;
enum extra : std::uint8_t;
enum class extra_and_length : std::uint8_t;
enum class major_index : std::uint32_t;
enum class minor_index : std::uint32_t;
enum class request : std::uint8_t;
enum class webhook_type : std::uint8_t;

struct account;
struct account_address;
struct address_index;
struct block_info;
struct key_image;
struct output;
struct output_id;
struct request_info;
struct spend;
class storage;
struct subaddress_map;
struct transaction_link;
struct view_key;
struct webhook_data;
struct webhook_dupsort;
struct webhook_event;
struct webhook_key;
struct webhook_new_account;
struct webhook_output;
struct webhook_tx_confirmation;
} // db
} // lws
Loading

0 comments on commit 29d3859

Please sign in to comment.