aboutsummaryrefslogtreecommitdiff
path: root/src/cryptonote_basic
diff options
context:
space:
mode:
authorj-berman <justinberman@protonmail.com>2021-11-15 05:23:53 -0800
committerj-berman <justinberman@protonmail.com>2022-04-18 00:49:53 -0700
commitea87b30f8907ee11252433811e7a7d0c46758cca (patch)
tree61dedf56a781a83285be092b078019bebdc94f2e /src/cryptonote_basic
parentMerge pull request #8207 (diff)
downloadmonero-ea87b30f8907ee11252433811e7a7d0c46758cca.tar.xz
Add view tags to outputs to reduce wallet scanning time
Implements view tags as proposed by @UkoeHB in MRL issue https://github.com/monero-project/research-lab/issues/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.
Diffstat (limited to 'src/cryptonote_basic')
-rw-r--r--src/cryptonote_basic/cryptonote_basic.h19
-rw-r--r--src/cryptonote_basic/cryptonote_boost_serialization.h13
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp159
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h9
4 files changed, 170 insertions, 30 deletions
diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h
index 4ae29281a..127958796 100644
--- a/src/cryptonote_basic/cryptonote_basic.h
+++ b/src/cryptonote_basic/cryptonote_basic.h
@@ -74,6 +74,7 @@ namespace cryptonote
crypto::hash hash;
};
+ // outputs <= HF_VERSION_VIEW_TAGS
struct txout_to_key
{
txout_to_key() { }
@@ -81,6 +82,19 @@ namespace cryptonote
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 */
@@ -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
@@ -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);
@@ -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");
@@ -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");
diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h
index d7149b5c9..493c9d91d 100644
--- a/src/cryptonote_basic/cryptonote_boost_serialization.h
+++ b/src/cryptonote_basic/cryptonote_boost_serialization.h
@@ -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);
+ }
template <class Archive>
inline void serialize(Archive &a, crypto::signature &x, const boost::serialization::version_type ver)
{
@@ -103,6 +107,13 @@ namespace boost
}
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)
{
a & x.hash;
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index d8f9bb176..432617a4f 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -155,12 +155,13 @@ namespace cryptonote
}
for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n)
{
- if (tx.vout[n].target.type() != typeid(txout_to_key))
+ crypto::public_key output_public_key;
+ if (!get_output_public_key(tx.vout[n], output_public_key))
{
- LOG_PRINT_L1("Unsupported output type in tx " << get_transaction_hash(tx));
+ LOG_PRINT_L1("Failed to get output public key for output " << n << " in tx " << get_transaction_hash(tx));
return false;
}
- rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key);
+ rv.outPk[n].dest = rct::pk2rct(output_public_key);
}
if (!base_only)
@@ -852,16 +853,16 @@ namespace cryptonote
{
for(const tx_out& out: tx.vout)
{
- CHECK_AND_ASSERT_MES(out.target.type() == typeid(txout_to_key), false, "wrong variant type: "
- << out.target.type().name() << ", expected " << typeid(txout_to_key).name()
- << ", in transaction id=" << get_transaction_hash(tx));
+ crypto::public_key output_public_key;
+ CHECK_AND_ASSERT_MES(get_output_public_key(out, output_public_key), false, "Failed to get output public key (output type: "
+ << out.target.type().name() << "), in transaction id=" << get_transaction_hash(tx));
if (tx.version == 1)
{
CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount output in transaction id=" << get_transaction_hash(tx));
}
- if(!check_key(boost::get<txout_to_key>(out.target).key))
+ if(!check_key(output_public_key))
return false;
}
return true;
@@ -905,6 +906,30 @@ namespace cryptonote
return outputs_amount;
}
//---------------------------------------------------------------
+ bool get_output_public_key(const cryptonote::tx_out& out, crypto::public_key& output_public_key)
+ {
+ // before HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_key
+ // after HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_tagged_key
+ if (out.target.type() == typeid(txout_to_key))
+ output_public_key = boost::get< txout_to_key >(out.target).key;
+ else if (out.target.type() == typeid(txout_to_tagged_key))
+ output_public_key = boost::get< txout_to_tagged_key >(out.target).key;
+ else
+ {
+ LOG_ERROR("Unexpected output target type found: " << out.target.type().name());
+ return false;
+ }
+
+ return true;
+ }
+ //---------------------------------------------------------------
+ boost::optional<crypto::view_tag> get_output_view_tag(const cryptonote::tx_out& out)
+ {
+ return out.target.type() == typeid(txout_to_tagged_key)
+ ? boost::optional<crypto::view_tag>(boost::get< txout_to_tagged_key >(out.target).view_tag)
+ : boost::optional<crypto::view_tag>();
+ }
+ //---------------------------------------------------------------
std::string short_hash_str(const crypto::hash& h)
{
std::string res = string_tools::pod_to_hex(h);
@@ -914,45 +939,126 @@ namespace cryptonote
return res;
}
//---------------------------------------------------------------
- bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index)
+ void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out)
+ {
+ out.amount = amount;
+ if (use_view_tags)
+ {
+ txout_to_tagged_key ttk;
+ ttk.key = output_public_key;
+ ttk.view_tag = view_tag;
+ out.target = ttk;
+ }
+ else
+ {
+ txout_to_key tk;
+ tk.key = output_public_key;
+ out.target = tk;
+ }
+ }
+ //---------------------------------------------------------------
+ bool check_output_types(const transaction& tx, const uint8_t hf_version)
+ {
+ for (const auto &o: tx.vout)
+ {
+ if (hf_version > HF_VERSION_VIEW_TAGS)
+ {
+ // from v15, require outputs have view tags
+ CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: "
+ << o.target.type().name() << ", expected txout_to_tagged_key in transaction id=" << get_transaction_hash(tx));
+ }
+ else if (hf_version < HF_VERSION_VIEW_TAGS)
+ {
+ // require outputs to be of type txout_to_key
+ CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong variant type: "
+ << o.target.type().name() << ", expected txout_to_key in transaction id=" << get_transaction_hash(tx));
+ }
+ else //(hf_version == HF_VERSION_VIEW_TAGS)
+ {
+ // require outputs be of type txout_to_key OR txout_to_tagged_key
+ // to allow grace period before requiring all to be txout_to_tagged_key
+ CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key) || o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: "
+ << o.target.type().name() << ", expected txout_to_key or txout_to_tagged_key in transaction id=" << get_transaction_hash(tx));
+
+ // require all outputs in a tx be of the same type
+ CHECK_AND_ASSERT_MES(o.target.type() == tx.vout[0].target.type(), false, "non-matching variant types: "
+ << o.target.type().name() << " and " << tx.vout[0].target.type().name() << ", "
+ << "expected matching variant types in transaction id=" << get_transaction_hash(tx));
+ }
+ }
+ return true;
+ }
+ //---------------------------------------------------------------
+ bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index)
+ {
+ // If there is no view tag to check, the output can possibly belong to the account.
+ // Will need to derive the output pub key to be certain whether or not the output belongs to the account.
+ if (!view_tag_opt)
+ return true;
+
+ crypto::view_tag view_tag = *view_tag_opt;
+
+ // If the output's view tag does *not* match the derived view tag, the output should not belong to the account.
+ // Therefore can fail out early to avoid expensive crypto ops needlessly deriving output public key to
+ // determine if output belongs to the account.
+ crypto::view_tag derived_view_tag;
+ crypto::derive_view_tag(derivation, output_index, derived_view_tag);
+ return view_tag == derived_view_tag;
+ }
+ //---------------------------------------------------------------
+ bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt)
{
crypto::key_derivation derivation;
bool r = acc.get_device().generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
crypto::public_key pk;
- r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
- CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key");
- if (pk == out_key.key)
- return true;
+ if (out_can_be_to_acc(view_tag_opt, derivation, output_index))
+ {
+ r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
+ CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key");
+ if (pk == output_public_key)
+ return true;
+ }
+
// try additional tx pubkeys if available
if (!additional_tx_pub_keys.empty())
{
CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys");
r = acc.get_device().generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
- r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
- CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key");
- return pk == out_key.key;
+ if (out_can_be_to_acc(view_tag_opt, derivation, output_index))
+ {
+ r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
+ CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key");
+ return pk == output_public_key;
+ }
}
return false;
}
//---------------------------------------------------------------
- boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev)
+ boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev, const boost::optional<crypto::view_tag>& view_tag_opt)
{
// try the shared tx pubkey
crypto::public_key subaddress_spendkey;
- hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey);
- auto found = subaddresses.find(subaddress_spendkey);
- if (found != subaddresses.end())
- return subaddress_receive_info{ found->second, derivation };
+ if (out_can_be_to_acc(view_tag_opt, derivation, output_index))
+ {
+ hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey);
+ auto found = subaddresses.find(subaddress_spendkey);
+ if (found != subaddresses.end())
+ return subaddress_receive_info{ found->second, derivation };
+ }
+
// try additional tx pubkeys if available
if (!additional_derivations.empty())
{
CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations");
- hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey);
- found = subaddresses.find(subaddress_spendkey);
- if (found != subaddresses.end())
- return subaddress_receive_info{ found->second, additional_derivations[output_index] };
+ if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index))
+ {
+ hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey);
+ auto found = subaddresses.find(subaddress_spendkey);
+ if (found != subaddresses.end())
+ return subaddress_receive_info{ found->second, additional_derivations[output_index] };
+ }
}
return boost::none;
}
@@ -973,8 +1079,9 @@ namespace cryptonote
size_t i = 0;
for(const tx_out& o: tx.vout)
{
- CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" );
- if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, additional_tx_pub_keys, i))
+ crypto::public_key output_public_key;
+ CHECK_AND_ASSERT_MES(get_output_public_key(o, output_public_key), false, "unable to get output public key from transaction out" );
+ if(is_out_to_acc(acc, output_public_key, tx_pub_key, additional_tx_pub_keys, i, get_output_view_tag(o)))
{
outs.push_back(i);
money_transfered += o.amount;
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index ce3ce7492..8f5459ca7 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -89,13 +89,16 @@ namespace cryptonote
void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id);
bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id);
bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id);
- bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index);
+ void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out);
+ bool check_output_types(const transaction& tx, const uint8_t hf_version);
+ bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index);
+ bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>());
struct subaddress_receive_info
{
subaddress_index index;
crypto::key_derivation derivation;
};
- boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev);
+ boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>());
bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, std::vector<size_t>& outs, uint64_t& money_transfered);
bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered);
bool get_tx_fee(const transaction& tx, uint64_t & fee);
@@ -126,6 +129,8 @@ namespace cryptonote
bool parse_and_validate_block_from_blob(const blobdata_ref& b_blob, block& b, crypto::hash &block_hash);
bool get_inputs_money_amount(const transaction& tx, uint64_t& money);
uint64_t get_outs_money_amount(const transaction& tx);
+ bool get_output_public_key(const cryptonote::tx_out& out, crypto::public_key& output_public_key);
+ boost::optional<crypto::view_tag> get_output_view_tag(const cryptonote::tx_out& out);
bool check_inputs_types_supported(const transaction& tx);
bool check_outs_valid(const transaction& tx);
bool parse_amount(uint64_t& amount, const std::string& str_amount);