diff options
Diffstat (limited to 'src/multisig/multisig_tx_builder_ringct.cpp')
-rw-r--r-- | src/multisig/multisig_tx_builder_ringct.cpp | 142 |
1 files changed, 129 insertions, 13 deletions
diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index cbc556b71..e5c9ac483 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -34,6 +34,7 @@ #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_config.h" #include "cryptonote_core/cryptonote_tx_utils.h" #include "device/device.hpp" #include "multisig_clsag_context.h" @@ -47,6 +48,7 @@ #include <cstring> #include <limits> #include <set> +#include <string> #include <unordered_map> #include <unordered_set> #include <vector> @@ -242,6 +244,80 @@ static bool set_tx_extra( } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- +static void make_tx_secret_key_seed(const crypto::secret_key& tx_secret_key_entropy, + const std::vector<cryptonote::tx_source_entry>& sources, + crypto::secret_key& tx_secret_key_seed) +{ + // seed = H(H("domain separator"), entropy, {KI}) + static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED}; + + rct::keyV hash_context; + hash_context.reserve(2 + sources.size()); + auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(hash_context.data(), hash_context.size()); + }); + hash_context.emplace_back(); + rct::cn_fast_hash(hash_context.back(), domain_separator.data(), domain_separator.size()); //domain sep + hash_context.emplace_back(rct::sk2rct(tx_secret_key_entropy)); //entropy + + for (const cryptonote::tx_source_entry& source : sources) + hash_context.emplace_back(source.multisig_kLRki.ki); //{KI} + + // set the seed + tx_secret_key_seed = rct::rct2sk(rct::cn_fast_hash(hash_context)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_tx_secret_keys(const crypto::secret_key& tx_secret_key_seed, + const std::size_t num_tx_keys, + std::vector<crypto::secret_key>& tx_secret_keys) +{ + // make tx secret keys as a hash chain of the seed + // h1 = H_n(seed || H("domain separator")) + // h2 = H_n(seed || h1) + // h3 = H_n(seed || h2) + // ... + static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS}; + + rct::keyV hash_context; + hash_context.resize(2); + auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(hash_context.data(), hash_context.size()); + }); + hash_context[0] = rct::sk2rct(tx_secret_key_seed); + rct::cn_fast_hash(hash_context[1], domain_separator.data(), domain_separator.size()); + + tx_secret_keys.clear(); + tx_secret_keys.resize(num_tx_keys); + + for (crypto::secret_key& tx_secret_key : tx_secret_keys) + { + // advance the hash chain + hash_context[1] = rct::hash_to_scalar(hash_context); + + // set this key + tx_secret_key = rct::rct2sk(hash_context[1]); + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool collect_tx_secret_keys(const std::vector<crypto::secret_key>& tx_secret_keys, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys) +{ + if (tx_secret_keys.size() == 0) + return false; + + tx_secret_key = tx_secret_keys[0]; + tx_aux_secret_keys.clear(); + tx_aux_secret_keys.reserve(tx_secret_keys.size() - 1); + for (std::size_t tx_key_index{1}; tx_key_index < tx_secret_keys.size(); ++tx_key_index) + tx_aux_secret_keys.emplace_back(tx_secret_keys[tx_key_index]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- static bool compute_keys_for_destinations( const cryptonote::account_keys& account_keys, const std::uint32_t subaddr_account, @@ -250,6 +326,7 @@ static bool compute_keys_for_destinations( const std::vector<std::uint8_t>& extra, const bool use_view_tags, const bool reconstruction, + const crypto::secret_key& tx_secret_key_seed, crypto::secret_key& tx_secret_key, std::vector<crypto::secret_key>& tx_aux_secret_keys, rct::keyV& output_public_keys, @@ -288,8 +365,35 @@ static bool compute_keys_for_destinations( unique_std_recipients.insert(dst_entr.addr); } - if (not reconstruction) { - tx_secret_key = rct::rct2sk(rct::skGen()); + // figure out how many tx secret keys are needed + // - tx aux keys: add if there are > 1 non-change recipients, with at least one to a subaddress + const std::size_t num_destinations = destinations.size(); + const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1; + + const std::size_t num_tx_keys = 1 + (need_tx_aux_keys ? num_destinations : 0); + + // make tx secret keys + std::vector<crypto::secret_key> all_tx_secret_keys; + make_tx_secret_keys(tx_secret_key_seed, num_tx_keys, all_tx_secret_keys); + + // split up tx secret keys + crypto::secret_key tx_secret_key_temp; + std::vector<crypto::secret_key> tx_aux_secret_keys_temp; + if (not collect_tx_secret_keys(all_tx_secret_keys, tx_secret_key_temp, tx_aux_secret_keys_temp)) + return false; + + if (reconstruction) + { + // when reconstructing, the tx secret keys should be reproducible from input seed + if (!(tx_secret_key == tx_secret_key_temp)) + return false; + if (!(tx_aux_secret_keys == tx_aux_secret_keys_temp)) + return false; + } + else + { + tx_secret_key = tx_secret_key_temp; + tx_aux_secret_keys = std::move(tx_aux_secret_keys_temp); } // tx pub key: R @@ -312,17 +416,6 @@ static bool compute_keys_for_destinations( } // additional tx pubkeys: R_t - // - add if there are > 1 non-change recipients, with at least one to a subaddress - const std::size_t num_destinations = destinations.size(); - - const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1; - if (not reconstruction and need_tx_aux_keys) { - tx_aux_secret_keys.clear(); - tx_aux_secret_keys.reserve(num_destinations); - for(std::size_t i = 0; i < num_destinations; ++i) - tx_aux_secret_keys.push_back(rct::rct2sk(rct::skGen())); - } - output_public_keys.resize(num_destinations); view_tags.resize(num_destinations); std::vector<crypto::public_key> tx_aux_public_keys; @@ -738,6 +831,7 @@ bool tx_builder_ringct_t::init( const bool reconstruction, crypto::secret_key& tx_secret_key, std::vector<crypto::secret_key>& tx_aux_secret_keys, + crypto::secret_key& tx_secret_key_entropy, cryptonote::transaction& unsigned_tx ) { @@ -765,6 +859,23 @@ bool tx_builder_ringct_t::init( // sort inputs sort_sources(sources); + // prepare tx secret key seed (must be AFTER sorting sources) + // - deriving the seed from sources plus entropy ensures uniqueness for every new tx attempt + // - the goal is that two multisig txs added to the chain will never have outputs with the same onetime addresses, + // which would burn funds (embedding the inputs' key images guarantees this) + // - it is acceptable if two tx attempts use the same input set and entropy (only a malicious tx proposer will do + // that, but all it can accomplish is leaking information about the recipients - which a malicious proposer can + // easily do outside the signing ritual anyway) + if (not reconstruction) + tx_secret_key_entropy = rct::rct2sk(rct::skGen()); + + // expect not null (note: wallet serialization code may set this to null if handling an old partial tx) + if (tx_secret_key_entropy == crypto::null_skey) + return false; + + crypto::secret_key tx_secret_key_seed; + make_tx_secret_key_seed(tx_secret_key_entropy, sources, tx_secret_key_seed); + // get secret keys for signing input CLSAGs (multisig: or for the initial partial signature) rct::keyV input_secret_keys; auto input_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ @@ -791,6 +902,7 @@ bool tx_builder_ringct_t::init( extra, use_view_tags, reconstruction, + tx_secret_key_seed, tx_secret_key, tx_aux_secret_keys, output_public_keys, @@ -921,6 +1033,7 @@ bool tx_builder_ringct_t::finalize_tx( cryptonote::transaction& unsigned_tx ) { + // checks const std::size_t num_sources = sources.size(); if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) return false; @@ -928,6 +1041,8 @@ bool tx_builder_ringct_t::finalize_tx( return false; if (num_sources != s.size()) return false; + + // finalize tx signatures for (std::size_t i = 0; i < num_sources; ++i) { const std::size_t ring_size = unsigned_tx.rct_signatures.p.CLSAGs[i].s.size(); if (sources[i].real_output >= ring_size) @@ -935,6 +1050,7 @@ bool tx_builder_ringct_t::finalize_tx( unsigned_tx.rct_signatures.p.CLSAGs[i].s[sources[i].real_output] = s[i]; unsigned_tx.rct_signatures.p.CLSAGs[i].c1 = c_0[i]; } + return true; } //---------------------------------------------------------------------------------------------------------------------- |