Skip to content

Commit

Permalink
Add view tags to outputs to reduce wallet scanning time
Browse files Browse the repository at this point in the history
Implements view tags as proposed by @UkoeHB in MRL issue
monero-project/research-lab#73

At tx construction, the sender adds a 1-byte view tag to each
output. The view tag is derived from the sender-receiver
shared secret. When scanning for outputs, the receiver can
check the view tag for a match, in order to reduce scanning
time. When the view tag does not match, the wallet avoids the
more expensive EC operations when deriving the output public
key using the shared secret.
  • Loading branch information
j-berman committed Nov 15, 2021
1 parent b328fbe commit 437ad18
Show file tree
Hide file tree
Showing 42 changed files with 1,331 additions and 190 deletions.
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("Wrong output type: expected txout_to_key or txout_to_tagged_key"));
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
2 changes: 2 additions & 0 deletions src/crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ set(crypto_sources
keccak.c
oaes_lib.c
random.c
siphash.c
skein.c
slow-hash.c
rx-slow-hash.c
Expand Down Expand Up @@ -75,6 +76,7 @@ set(crypto_private_headers
oaes_config.h
oaes_lib.h
random.h
siphash.h
skein.h
skein_port.h
CryptonightR_JIT.h
Expand Down
12 changes: 12 additions & 0 deletions src/crypto/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
#include "warnings.h"
#include "crypto.h"
#include "hash.h"
extern "C" {
#include "crypto/siphash.h"
}

#include "cryptonote_config.h"

Expand Down Expand Up @@ -749,4 +752,13 @@ 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) {
ec_scalar scalar;
derivation_to_scalar(derivation, output_index, scalar);
const char salt[9] = "view_tag"; // separate domain for view tag
uint8_t view_tag_full; // siphash result will be 8 bytes
siphash(&salt, sizeof(salt), &scalar, &view_tag_full, 8); // note that siphash will only use the first 16 bytes of the scalar
memcpy(&view_tag, &view_tag_full, 1); // only need the first byte to realize optimal perf/space efficiency
}
}
20 changes: 19 additions & 1 deletion src/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,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 @@ -102,7 +106,7 @@ namespace crypto {
static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
sizeof(public_key) == 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 @@ -146,6 +150,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 @@ -292,6 +298,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 @@ -307,6 +321,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 @@ -316,3 +333,4 @@ CRYPTO_MAKE_HASHABLE(public_key)
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key)
CRYPTO_MAKE_HASHABLE(key_image)
CRYPTO_MAKE_COMPARABLE(signature)
CRYPTO_MAKE_COMPARABLE(view_tag)
Loading

0 comments on commit 437ad18

Please sign in to comment.