From fa5697127f5a99ddd20311cec8180f6a89b31ceb Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 21 Oct 2017 12:14:31 +0100 Subject: [PATCH] make multisig work with subaddresses Thanks to kenshi84 for help getting this work --- .../cryptonote_format_utils.cpp | 53 +++++++++++++++++-- src/cryptonote_core/cryptonote_tx_utils.cpp | 17 ++---- src/cryptonote_core/cryptonote_tx_utils.h | 6 ++- src/multisig/multisig.cpp | 28 ++-------- src/multisig/multisig.h | 6 +-- src/wallet/wallet2.cpp | 42 +++++---------- src/wallet/wallet2.h | 1 - tests/core_tests/multisig.cpp | 18 ++++--- 8 files changed, 89 insertions(+), 82 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index a22c3bdea..8f7ab94db 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -80,6 +80,31 @@ static std::atomic tx_hashes_cached_count(0); static std::atomic block_hashes_calculated_count(0); static std::atomic block_hashes_cached_count(0); +#define CHECK_AND_ASSERT_THROW_MES_L1(expr, message) {if(!(expr)) {MWARNING(message); throw std::runtime_error(message);}} + +namespace cryptonote +{ + static inline unsigned char *operator &(ec_point &point) { + return &reinterpret_cast(point); + } + static inline const unsigned char *operator &(const ec_point &point) { + return &reinterpret_cast(point); + } + + // a copy of rct::addKeys, since we can't link to libringct to avoid circular dependencies + static void add_public_key(crypto::public_key &AB, const crypto::public_key &A, const crypto::public_key &B) { + ge_p3 B2, A2; + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, &B) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, &A) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast(__LINE__)); + ge_cached tmp2; + ge_p3_to_cached(&tmp2, &B2); + ge_p1p1 tmp3; + ge_add(&tmp3, &A2, &tmp2); + ge_p1p1_to_p3(&A2, &tmp3); + ge_p3_tobytes(&AB, &A2); + } +} + namespace cryptonote { //--------------------------------------------------------------- @@ -182,6 +207,7 @@ namespace cryptonote crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b // step 2: add Hs(a || index_major || index_minor) + crypto::secret_key subaddr_sk; crypto::secret_key scalar_step2; if (received_index.is_zero()) { @@ -189,13 +215,32 @@ namespace cryptonote } else { - crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index); - sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m); + subaddr_sk = get_subaddress_secret_key(ack.m_view_secret_key, received_index); + sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&subaddr_sk); } in_ephemeral.sec = scalar_step2; - crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one"); + + if (ack.m_multisig_keys.empty()) + { + // when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase + CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub), false, "Failed to derive public key"); + } + else + { + // when in multisig, we only know the partial spend secret key. but we do know the full spend public key, so the output pubkey can be obtained by using the standard CN key derivation + CHECK_AND_ASSERT_MES(crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub), false, "Failed to derive public key"); + // and don't forget to add the contribution from the subaddress part + if (!received_index.is_zero()) + { + crypto::public_key subaddr_pk; + CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(subaddr_sk, subaddr_pk), false, "Failed to derive public key"); + add_public_key(in_ephemeral.pub, in_ephemeral.pub, subaddr_pk); + } + } + + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, + false, "key image helper precomp: given output pubkey doesn't match the derived one"); } crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index fb1f972b3..89f24a4d4 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -269,24 +269,15 @@ namespace cryptonote in_contexts.push_back(input_generation_context_data()); keypair& in_ephemeral = in_contexts.back().in_ephemeral; crypto::key_image img; - bool r; - if (msout) - { - r = generate_key_image_helper_old(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); - } - else - { - const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); - r = generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img); - } - if (!r) + const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); + if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img)) { LOG_ERROR("Key image generation failed!"); return false; } - //check that derivated key is equal with real output key - if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + //check that derivated key is equal with real output key (if non multisig) + if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 2b6cf26db..5947522e2 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -64,6 +64,7 @@ namespace cryptonote FIELD(amount) FIELD(rct) FIELD(mask) + FIELD(multisig_kLRki) if (real_output >= outputs.size()) return false; @@ -100,7 +101,7 @@ namespace cryptonote } -BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0) +BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 1) BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1) namespace boost @@ -117,7 +118,10 @@ namespace boost a & x.amount; a & x.rct; a & x.mask; + if (ver < 1) + return; a & x.multisig_kLRki; + a & x.real_out_additional_tx_keys; } template diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 0a9933b13..a99f66e64 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -41,21 +41,6 @@ using namespace std; namespace cryptonote { - //----------------------------------------------------------------- - bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) - { - crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); - - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); - - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); - return true; - } //----------------------------------------------------------------- void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) { @@ -107,14 +92,11 @@ namespace cryptonote return rct::rct2pk(spend_public_key); } //----------------------------------------------------------------- - bool generate_multisig_key_image(const account_keys &keys, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) + bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki) { if (multisig_key_index >= keys.m_multisig_keys.size()) return false; - if (!cryptonote::generate_key_image_helper_old(keys, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, keys.m_multisig_keys[multisig_key_index], ki); + crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki); return true; } //----------------------------------------------------------------- @@ -124,16 +106,16 @@ namespace cryptonote crypto::generate_key_image(pkey, k, (crypto::key_image&)R); } //----------------------------------------------------------------- - bool generate_multisig_composite_key_image(const account_keys &keys, const crypto::public_key &tx_public_key, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki) + bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector& additional_tx_public_keys, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki) { cryptonote::keypair in_ephemeral; - if (!cryptonote::generate_key_image_helper_old(keys, tx_public_key, real_output_index, in_ephemeral, ki)) + if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki)) return false; std::unordered_set used; for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) { crypto::key_image pki; - bool r = cryptonote::generate_multisig_key_image(keys, tx_public_key, real_output_index, in_ephemeral, pki, m); + bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki); if (!r) return false; used.insert(pki); diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index c5312182b..5cb469c1b 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -38,13 +38,11 @@ namespace cryptonote { struct account_keys; - bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); - void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); void generate_multisig_N1_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector &skeys); crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector &pkeys); - bool generate_multisig_key_image(const account_keys &keys, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index); + bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki); void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); - bool generate_multisig_composite_key_image(const account_keys &keys, const crypto::public_key &tx_public_key, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki); + bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector& additional_tx_public_keys, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9c2587f25..4ad6d852d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -527,18 +527,6 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } -bool wallet_generate_key_image_helper_old(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, bool multisig_export = false) -{ - if (!cryptonote::generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - if (multisig_export) - { - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, ack.m_spend_secret_key, ki); - } - return true; -} - crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { crypto::hash8 payment_id8 = null_hash8; @@ -895,26 +883,22 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- -bool wallet2::wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const -{ - THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); - return cryptonote::generate_multisig_key_image(ack, tx_public_key, real_output_index, in_ephemeral, ki, multisig_key_index); -} -//---------------------------------------------------------------------------------------------------- void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs) { - bool r; + THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); if (m_multisig) { - r = wallet_generate_key_image_helper_old(keys, tx_pub_key, i, tx_scan_info.in_ephemeral, tx_scan_info.ki); + tx_scan_info.in_ephemeral.pub = boost::get(tx.vout[i].target).key; + tx_scan_info.in_ephemeral.sec = crypto::null_skey; + tx_scan_info.ki = rct::rct2ki(rct::zero()); } else { - r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); } - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (tx_scan_info.money_transfered == 0) @@ -8373,13 +8357,14 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index"); const transfer_details &td = m_transfers[n]; - crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const std::vector additional_tx_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx); crypto::key_image ki; std::vector pkis; for (const auto &info: td.m_multisig_info) for (const auto &pki: info.m_partial_key_images) pkis.push_back(pki); - bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), tx_key, td.m_internal_output_index, pkis, ki); + bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } @@ -8394,8 +8379,7 @@ std::vector wallet2::export_multisig() for (size_t n = 0; n < m_transfers.size(); ++n) { transfer_details &td = m_transfers[n]; - crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); - cryptonote::keypair in_ephemeral; + const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); crypto::key_image ki; td.m_multisig_k.clear(); info[n].m_LR.clear(); @@ -8404,7 +8388,7 @@ std::vector wallet2::export_multisig() for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, m); + bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); info[n].m_partial_key_images.push_back(ki); } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 79199a30c..45f82fa1a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -977,7 +977,6 @@ namespace tools void set_unspent(size_t idx); void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; - bool wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const; crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector &unused_transfers_indices, const std::vector &unused_dust_indices) const; std::vector get_only_rct(const std::vector &unused_dust_indices, const std::vector &unused_transfers_indices) const; diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index 484b8b7ab..cf63d20f7 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -132,8 +132,14 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector> account_L(total); std::vector> account_R(total); std::vector> account_ki(total); + std::vector additional_tx_keys; + std::unordered_map subaddresses; + subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; for (size_t msidx = 0; msidx < total; ++msidx) { + CHECK_AND_ASSERT_MES(miner_account[msidx].get_keys().m_account_address.m_spend_public_key == miner_account[0].get_keys().m_account_address.m_spend_public_key, + false, "Mismatched spend public keys"); + size_t nlr = threshold < total ? threshold - 1 : 1; account_L[msidx].resize(nlr); account_R[msidx].resize(nlr); @@ -146,7 +152,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector subaddresses; - subaddresses[miner_account[creator].get_keys().m_account_address.m_spend_public_key] = {0,0}; - std::vector additional_tx_keys; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), tx, 0, tx_key, additional_tx_keys, true, false, msoutp); + std::vector additional_tx_secret_keys; + r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), tx, 0, tx_key, additional_tx_secret_keys, true, false, msoutp); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); #ifndef NO_MULTISIG