aboutsummaryrefslogtreecommitdiff
path: root/src/crypto
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/crypto/crypto.cpp24
-rw-r--r--src/crypto/crypto.h20
-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
-rw-r--r--src/cryptonote_config.h1
-rw-r--r--src/cryptonote_core/blockchain.cpp32
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp14
-rw-r--r--src/cryptonote_core/cryptonote_core.h3
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp35
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h10
12 files changed, 276 insertions, 63 deletions
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 1f46164d7..77a36069a 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -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;
+ #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));
+ }
}
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index 596090329..d8cd6c6a0 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -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);
@@ -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();
@@ -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);
@@ -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;
}
@@ -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;
@@ -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)
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);
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index 8621e1696..88316fd6a 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -183,6 +183,7 @@
#define HF_VERSION_CLSAG 13
#define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13
#define HF_VERSION_BULLETPROOF_PLUS 15
+#define HF_VERSION_VIEW_TAGS 15
#define HF_VERSION_2021_SCALING 15
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 5f9db676c..0b6a3abf3 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -1341,6 +1341,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std:
// one input, of type txin_gen, with height set to the block's height
// correct miner tx unlock time
// a non-overflowing tx amount (dubious necessity on this check)
+// valid output types
bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, uint8_t hf_version)
{
LOG_PRINT_L3("Blockchain::" << __func__);
@@ -1369,6 +1370,8 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height,
return false;
}
+ CHECK_AND_ASSERT_MES(check_output_types(b.miner_tx, hf_version), false, "miner transaction has invalid output type(s) in block " << get_block_hash(b));
+
return true;
}
//------------------------------------------------------------------
@@ -3044,12 +3047,14 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
// from v4, forbid invalid pubkeys
if (hf_version >= 4) {
for (const auto &o: tx.vout) {
- if (o.target.type() == typeid(txout_to_key)) {
- const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target);
- if (!crypto::check_key(out_to_key.key)) {
- tvc.m_invalid_output = true;
- return false;
- }
+ crypto::public_key output_public_key;
+ if (!get_output_public_key(o, output_public_key)) {
+ tvc.m_invalid_output = true;
+ return false;
+ }
+ if (!crypto::check_key(output_public_key)) {
+ tvc.m_invalid_output = true;
+ return false;
}
}
}
@@ -3166,6 +3171,13 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
}
}
+ // from v15, require view tags on outputs
+ if (!check_output_types(tx, hf_version))
+ {
+ tvc.m_invalid_output = true;
+ return false;
+ }
+
return true;
}
//------------------------------------------------------------------
@@ -3977,9 +3989,11 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons
}
// The original code includes a check for the output corresponding to this input
- // to be a txout_to_key. This is removed, as the database does not store this info,
- // but only txout_to_key outputs are stored in the DB in the first place, done in
- // Blockchain*::add_output
+ // to be a txout_to_key. This is removed, as the database does not store this info.
+ // Only txout_to_key (and since HF_VERSION_VIEW_TAGS, txout_to_tagged_key)
+ // outputs are stored in the DB in the first place, done in Blockchain*::add_output.
+ // Additional type checks on outputs were also added via cryptonote::check_output_types
+ // and cryptonote::get_output_public_key (see Blockchain::check_tx_outputs).
m_output_keys.push_back(rct::ctkey({rct::pk2rct(pubkey), commitment}));
return true;
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 2d2b1cbcf..a78f5d673 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -1177,7 +1177,8 @@ namespace cryptonote
return false;
}
- if (!check_tx_inputs_ring_members_diff(tx))
+ const uint8_t hf_version = m_blockchain_storage.get_current_hard_fork_version();
+ if (!check_tx_inputs_ring_members_diff(tx, hf_version))
{
MERROR_VER("tx uses duplicate ring members");
return false;
@@ -1189,6 +1190,12 @@ namespace cryptonote
return false;
}
+ if (!check_output_types(tx, hf_version))
+ {
+ MERROR_VER("tx does not use valid output type(s)");
+ return false;
+ }
+
return true;
}
//-----------------------------------------------------------------------------------------------
@@ -1295,10 +1302,9 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const
+ bool core::check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const
{
- const uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
- if (version >= 6)
+ if (hf_version >= 6)
{
for(const auto& in: tx.vin)
{
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 2e5248c5d..0b36730b6 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -1012,10 +1012,11 @@ namespace cryptonote
* @brief verify that each ring uses distinct members
*
* @param tx the transaction to check
+ * @param hf_version the hard fork version rules to use
*
* @return false if any ring uses duplicate members, true otherwise
*/
- bool check_tx_inputs_ring_members_diff(const transaction& tx) const;
+ bool check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const;
/**
* @brief verify that each input key image in a transaction is in
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 76192be5d..1d2024a05 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -149,12 +149,17 @@ namespace cryptonote
r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key);
CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")");
- txout_to_key tk;
- tk.key = out_eph_public_key;
+ uint64_t amount = out_amounts[no];
+ summary_amounts += amount;
+
+ bool use_view_tags = hard_fork_version >= HF_VERSION_VIEW_TAGS;
+ crypto::view_tag view_tag;
+ if (use_view_tags)
+ crypto::derive_view_tag(derivation, no, view_tag);
tx_out out;
- summary_amounts += out.amount = out_amounts[no];
- out.target = tk;
+ cryptonote::set_tx_out(amount, out_eph_public_key, use_view_tags, view_tag, out);
+
tx.vout.push_back(out);
}
@@ -198,7 +203,7 @@ namespace cryptonote
return addr.m_view_public_key;
}
//---------------------------------------------------------------
- bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs)
+ bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs, bool use_view_tags)
{
hw::device &hwdev = sender_account_keys.get_device();
@@ -406,17 +411,16 @@ namespace cryptonote
{
CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount);
crypto::public_key out_eph_public_key;
+ crypto::view_tag view_tag;
hwdev.generate_output_ephemeral_keys(tx.version,sender_account_keys, txkey_pub, tx_key,
dst_entr, change_addr, output_index,
need_additional_txkeys, additional_tx_keys,
- additional_tx_public_keys, amount_keys, out_eph_public_key);
+ additional_tx_public_keys, amount_keys, out_eph_public_key,
+ use_view_tags, view_tag);
tx_out out;
- out.amount = dst_entr.amount;
- txout_to_key tk;
- tk.key = out_eph_public_key;
- out.target = tk;
+ cryptonote::set_tx_out(dst_entr.amount, out_eph_public_key, use_view_tags, view_tag, out);
tx.vout.push_back(out);
output_index++;
summary_outs_money += dst_entr.amount;
@@ -546,7 +550,9 @@ namespace cryptonote
}
for (size_t i = 0; i < tx.vout.size(); ++i)
{
- destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key));
+ crypto::public_key output_public_key;
+ get_output_public_key(tx.vout[i], output_public_key);
+ destinations.push_back(rct::pk2rct(output_public_key));
outamounts.push_back(tx.vout[i].amount);
amount_out += tx.vout[i].amount;
}
@@ -607,7 +613,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout)
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool use_view_tags)
{
hw::device &hwdev = sender_account_keys.get_device();
hwdev.open_tx(tx_key);
@@ -627,7 +633,8 @@ namespace cryptonote
}
}
- bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout);
+ bool shuffle_outs = true;
+ bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout, shuffle_outs, use_view_tags);
hwdev.close_tx();
return r;
} catch(...) {
@@ -643,7 +650,7 @@ namespace cryptonote
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::vector<tx_destination_entry> destinations_copy = destinations;
- return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL);
+ return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL, false);
}
//---------------------------------------------------------------
bool generate_genesis_block(
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index be6a6d946..f4ffb98ff 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -119,21 +119,23 @@ namespace cryptonote
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr);
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time);
- bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true);
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL);
+ bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true, bool use_view_tags = false);
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool use_view_tags = false);
bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key,
const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index,
const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys,
std::vector<crypto::public_key> &additional_tx_public_keys,
std::vector<rct::key> &amount_keys,
- crypto::public_key &out_eph_public_key) ;
+ crypto::public_key &out_eph_public_key,
+ const bool use_view_tags, crypto::view_tag &view_tag) ;
bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key,
const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index,
const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys,
std::vector<crypto::public_key> &additional_tx_public_keys,
std::vector<rct::key> &amount_keys,
- crypto::public_key &out_eph_public_key) ;
+ crypto::public_key &out_eph_public_key,
+ const bool use_view_tags, crypto::view_tag &view_tag) ;
bool generate_genesis_block(
block& bl