Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add view tags to outputs to reduce wallet scanning time #8061

Merged
merged 1 commit into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/blockchain_db/lmdb/db_lmdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1045,8 +1045,9 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash,
CURSOR(output_txs)
CURSOR(output_amounts)

if (tx_output.target.type() != typeid(txout_to_key))
throw0(DB_ERROR("Wrong output type: expected txout_to_key"));
crypto::public_key output_public_key;
if (!get_output_public_key(tx_output, output_public_key))
throw0(DB_ERROR("Could not get an output public key from a tx output."));
if (tx_output.amount == 0 && !commitment)
throw0(DB_ERROR("RCT output without commitment"));

Expand Down Expand Up @@ -1074,7 +1075,7 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash,
else
ok.amount_index = 0;
ok.output_id = m_num_outputs;
ok.data.pubkey = boost::get < txout_to_key > (tx_output.target).key;
ok.data.pubkey = output_public_key;
ok.data.unlock_time = unlock_time;
ok.data.height = m_height;
if (tx_output.amount == 0)
Expand Down
24 changes: 24 additions & 0 deletions src/crypto/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,28 @@ POP_WARNINGS
sc_sub(&h, &h, &sum);
return sc_isnonzero(&h) == 0;
}

void crypto_ops::derive_view_tag(const key_derivation &derivation, size_t output_index, view_tag &view_tag) {
#pragma pack(push, 1)
struct {
char salt[8]; // view tag domain-separator
key_derivation derivation;
char output_index[(sizeof(size_t) * 8 + 6) / 7];
} buf;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to disable auto alignment since the whole struct is hashed below, though it should be ok in practice

#pragma pack(pop)

char *end = buf.output_index;
memcpy(buf.salt, "view_tag", 8); // leave off null terminator
buf.derivation = derivation;
tools::write_varint(end, output_index);
assert(end <= buf.output_index + sizeof buf.output_index);

// view_tag_full = H[salt|derivation|output_index]
hash view_tag_full;
cn_fast_hash(&buf, end - reinterpret_cast<char *>(&buf), view_tag_full);

// only need a slice of view_tag_full to realize optimal perf/space efficiency
static_assert(sizeof(crypto::view_tag) <= sizeof(view_tag_full), "view tag should not be larger than hash result");
memcpy(&view_tag, &view_tag_full, sizeof(crypto::view_tag));
}
}
20 changes: 19 additions & 1 deletion src/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ namespace crypto {
ec_scalar c, r;
friend class crypto_ops;
};

POD_CLASS view_tag {
char data;
};
#pragma pack(pop)

void hash_to_scalar(const void *data, size_t length, ec_scalar &res);
Expand All @@ -107,7 +111,7 @@ namespace crypto {
static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 &&
sizeof(key_derivation) == 32 && sizeof(key_image) == 32 &&
sizeof(signature) == 64, "Invalid structure size");
sizeof(signature) == 64 && sizeof(view_tag) == 1, "Invalid structure size");

class crypto_ops {
crypto_ops();
Expand Down Expand Up @@ -151,6 +155,8 @@ namespace crypto {
const public_key *const *, std::size_t, const signature *);
friend bool check_ring_signature(const hash &, const key_image &,
const public_key *const *, std::size_t, const signature *);
static void derive_view_tag(const key_derivation &, std::size_t, view_tag &);
friend void derive_view_tag(const key_derivation &, std::size_t, view_tag &);
};

void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes);
Expand Down Expand Up @@ -297,6 +303,14 @@ namespace crypto {
return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig);
}

/* Derive a 1-byte view tag from the sender-receiver shared secret to reduce scanning time.
* When scanning outputs that were not sent to the user, checking the view tag for a match removes the need to proceed with expensive EC operations
* for an expected 99.6% of outputs (expected false positive rate = 1/2^8 = 1/256 = 0.4% = 100% - 99.6%).
*/
inline void derive_view_tag(const key_derivation &derivation, std::size_t output_index, view_tag &vt) {
crypto_ops::derive_view_tag(derivation, output_index, vt);
}

inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) {
epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
}
Expand All @@ -312,6 +326,9 @@ namespace crypto {
inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) {
epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
}
inline std::ostream &operator <<(std::ostream &o, const crypto::view_tag &v) {
epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
}

const extern crypto::public_key null_pkey;
const extern crypto::secret_key null_skey;
Expand All @@ -325,3 +342,4 @@ CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key)
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe)
CRYPTO_MAKE_HASHABLE(key_image)
CRYPTO_MAKE_COMPARABLE(signature)
CRYPTO_MAKE_COMPARABLE(view_tag)
19 changes: 18 additions & 1 deletion src/cryptonote_basic/cryptonote_basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,27 @@ namespace cryptonote
crypto::hash hash;
};

// outputs <= HF_VERSION_VIEW_TAGS
struct txout_to_key
{
txout_to_key() { }
txout_to_key(const crypto::public_key &_key) : key(_key) { }
crypto::public_key key;
};

// outputs >= HF_VERSION_VIEW_TAGS
struct txout_to_tagged_key
{
txout_to_tagged_key() { }
txout_to_tagged_key(const crypto::public_key &_key, const crypto::view_tag &_view_tag) : key(_key), view_tag(_view_tag) { }
crypto::public_key key;
crypto::view_tag view_tag; // optimization to reduce scanning time

BEGIN_SERIALIZE_OBJECT()
FIELD(key)
FIELD(view_tag)
END_SERIALIZE()
};

/* inputs */

Expand Down Expand Up @@ -137,7 +151,7 @@ namespace cryptonote

typedef boost::variant<txin_gen, txin_to_script, txin_to_scripthash, txin_to_key> txin_v;

typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key> txout_target_v;
typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key, txout_to_tagged_key> txout_target_v;

//typedef std::pair<uint64_t, txout> out_t;
struct tx_out
Expand Down Expand Up @@ -562,6 +576,7 @@ VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0);
VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1);
VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3);
VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc);
VARIANT_TAG(binary_archive, cryptonote::block, 0xbb);

Expand All @@ -572,6 +587,7 @@ VARIANT_TAG(json_archive, cryptonote::txin_to_key, "key");
VARIANT_TAG(json_archive, cryptonote::txout_to_script, "script");
VARIANT_TAG(json_archive, cryptonote::txout_to_scripthash, "scripthash");
VARIANT_TAG(json_archive, cryptonote::txout_to_key, "key");
VARIANT_TAG(json_archive, cryptonote::txout_to_tagged_key, "tagged_key");
VARIANT_TAG(json_archive, cryptonote::transaction, "tx");
VARIANT_TAG(json_archive, cryptonote::block, "block");

Expand All @@ -582,5 +598,6 @@ VARIANT_TAG(debug_archive, cryptonote::txin_to_key, "key");
VARIANT_TAG(debug_archive, cryptonote::txout_to_script, "script");
VARIANT_TAG(debug_archive, cryptonote::txout_to_scripthash, "scripthash");
VARIANT_TAG(debug_archive, cryptonote::txout_to_key, "key");
VARIANT_TAG(debug_archive, cryptonote::txout_to_tagged_key, "tagged_key");
VARIANT_TAG(debug_archive, cryptonote::transaction, "tx");
VARIANT_TAG(debug_archive, cryptonote::block, "block");
13 changes: 12 additions & 1 deletion src/cryptonote_basic/cryptonote_boost_serialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ namespace boost
{
a & reinterpret_cast<char (&)[sizeof(crypto::key_image)]>(x);
}

template <class Archive>
inline void serialize(Archive &a, crypto::view_tag &x, const boost::serialization::version_type ver)
{
a & reinterpret_cast<char (&)[sizeof(crypto::view_tag)]>(x);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this complicated construct instead of a & x.data; do you want to be prepared for view tags that are possibly more than 1 character in the future?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was following the pattern for the other crypto classes right above, I figure deviating from the pattern is more complicated than not

I'd guess the intention with the pattern is to avoid referencing data to maintain a higher level abstraction when working with the crypto classes across the code base (there seems to be a conscious effort not to reference data with the others in other places in the code), but I don't know for sure

}
template <class Archive>
inline void serialize(Archive &a, crypto::signature &x, const boost::serialization::version_type ver)
{
Expand Down Expand Up @@ -102,6 +106,13 @@ namespace boost
a & x.key;
}

template <class Archive>
inline void serialize(Archive &a, cryptonote::txout_to_tagged_key &x, const boost::serialization::version_type ver)
{
a & x.key;
a & x.view_tag;
}

template <class Archive>
inline void serialize(Archive &a, cryptonote::txout_to_scripthash &x, const boost::serialization::version_type ver)
{
Expand Down
Loading