diff options
author | anon <anon [at] nowhere> | 2021-12-06 10:25:01 +0000 |
---|---|---|
committer | koe <ukoe@protonmail.com> | 2022-06-30 12:56:40 -0500 |
commit | c7b2944f8960c208ceddeb3075a673630ae000cd (patch) | |
tree | 5ab335821b3dd9398a7a0ec645b4ad79d38e750b /tests/core_tests | |
parent | Merge pull request #8340 (diff) | |
download | monero-c7b2944f8960c208ceddeb3075a673630ae000cd.tar.xz |
multisig: fix critical vulnerabilities in signing
Diffstat (limited to 'tests/core_tests')
-rw-r--r-- | tests/core_tests/chaingen.cpp | 2 | ||||
-rw-r--r-- | tests/core_tests/multisig.cpp | 181 | ||||
-rw-r--r-- | tests/core_tests/multisig.h | 2 | ||||
-rw-r--r-- | tests/core_tests/rct.cpp | 2 | ||||
-rw-r--r-- | tests/core_tests/wallet_tools.cpp | 2 |
5 files changed, 89 insertions, 100 deletions
diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 61195c7b0..144e87bc2 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1076,7 +1076,7 @@ bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std:: std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - 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, rct, rct_config, nullptr); + 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, rct, rct_config); } transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head, diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index 3db3d4059..28d176e56 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -28,6 +28,11 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "ringct/rctSigs.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "multisig/multisig.h" +#include "multisig/multisig_tx_builder_ringct.h" +#include "common/apply_permutation.h" #include "chaingen.h" #include "multisig.h" @@ -113,7 +118,7 @@ static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accoun bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, - size_t threshold, size_t total, size_t creator, std::vector<size_t> signers, + size_t threshold, size_t total, size_t creator, std::vector<size_t> other_signers, const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx, const std::function<void(transaction &tx)> &post_tx) const { @@ -122,30 +127,18 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(total >= 2, false, "Bad scheme"); CHECK_AND_ASSERT_MES(threshold <= total, false, "Bad scheme"); -#ifdef NO_MULTISIG - CHECK_AND_ASSERT_MES(total <= 5, false, "Unsupported scheme"); -#endif CHECK_AND_ASSERT_MES(inputs >= 1 && inputs <= 8, false, "Inputs should between 1 and 8"); // given as 1 based for clarity --creator; - for (size_t &signer: signers) + for (size_t &signer: other_signers) --signer; CHECK_AND_ASSERT_MES(creator < total, false, "invalid creator"); - for (size_t signer: signers) + for (size_t signer: other_signers) CHECK_AND_ASSERT_MES(signer < total, false, "invalid signer"); -#ifdef NO_MULTISIG - GENERATE_ACCOUNT(acc0); - GENERATE_ACCOUNT(acc1); - GENERATE_ACCOUNT(acc2); - GENERATE_ACCOUNT(acc3); - GENERATE_ACCOUNT(acc4); - account_base miner_account[5] = {acc0, acc1, acc2, acc3, acc4}; -#else GENERATE_MULTISIG_ACCOUNT(miner_account, threshold, total); -#endif MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start); @@ -193,14 +186,13 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry { tx_pub_key[n] = get_tx_pub_key_from_extra(blocks[n].miner_tx); MDEBUG("tx_pub_key: " << tx_pub_key); - output_pub_key[n] = boost::get<txout_to_key>(blocks[n].miner_tx.vout[0].target).key; + cryptonote::get_output_public_key(blocks[n].miner_tx.vout[0], output_pub_key[n]); MDEBUG("output_pub_key: " << output_pub_key); } std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; -#ifndef NO_MULTISIG // create k/L/R/ki for that output we're going to spend std::vector<std::vector<std::vector<crypto::secret_key>>> account_k(total); std::vector<std::vector<std::vector<crypto::public_key>>> account_L(total); @@ -213,6 +205,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry false, "Mismatched spend public keys"); size_t nlr = threshold < total ? threshold - 1 : 1; + nlr *= multisig::signing::kAlphaComponents; account_k[msidx].resize(inputs); account_L[msidx].resize(inputs); account_R[msidx].resize(inputs); @@ -226,9 +219,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen())); multisig::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]); } - size_t numki = miner_account[msidx].get_multisig_keys().size(); - account_ki[msidx][tdidx].resize(numki); - for (size_t kiidx = 0; kiidx < numki; ++kiidx) + size_t num_account_partial_ki = miner_account[msidx].get_multisig_keys().size(); + account_ki[msidx][tdidx].resize(num_account_partial_ki); + for (size_t kiidx = 0; kiidx < num_account_partial_ki; ++kiidx) { r = multisig::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]); CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image"); @@ -248,7 +241,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry MDEBUG("ki: " << ki); } } -#endif // create kLRki std::vector<rct::multisig_kLRki> kLRkis; @@ -257,34 +249,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry { kLRkis.push_back(rct::multisig_kLRki()); rct::multisig_kLRki &kLRki = kLRkis.back(); -#ifdef NO_MULTISIG - kLRki = {rct::zero(), rct::zero(), rct::zero(), rct::zero()}; -#else - kLRki.k = rct::sk2rct(account_k[creator][tdidx][0]); - kLRki.L = rct::pk2rct(account_L[creator][tdidx][0]); - kLRki.R = rct::pk2rct(account_R[creator][tdidx][0]); - MDEBUG("Starting with k " << kLRki.k); - MDEBUG("Starting with L " << kLRki.L); - MDEBUG("Starting with R " << kLRki.R); - for (size_t msidx = 0; msidx < total; ++msidx) - { - if (msidx == creator) - continue; - if (std::find(signers.begin(), signers.end(), msidx) == signers.end()) - continue; - for (size_t lr = 0; lr < account_L[msidx][tdidx].size(); ++lr) - { - if (used_L.find(account_L[msidx][tdidx][lr]) == used_L.end()) - { - used_L.insert(account_L[msidx][tdidx][lr]); - MDEBUG("Adding L " << account_L[msidx][tdidx][lr] << " (for k " << account_k[msidx][tdidx][lr] << ")"); - MDEBUG("Adding R " << account_R[msidx][tdidx][lr]); - rct::addKeys((rct::key&)kLRki.L, kLRki.L, rct::pk2rct(account_L[msidx][tdidx][lr])); - rct::addKeys((rct::key&)kLRki.R, kLRki.R, rct::pk2rct(account_R[msidx][tdidx][lr])); - break; - } - } - } std::vector<crypto::key_image> pkis; for (size_t msidx = 0; msidx < total; ++msidx) for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n) @@ -292,8 +256,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry r = multisig::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki); CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); MDEBUG("composite ki: " << kLRki.ki); - MDEBUG("L: " << kLRki.L); - MDEBUG("R: " << kLRki.R); for (size_t n = 1; n < total; ++n) { rct::key ki; @@ -302,9 +264,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match"); } } -#endif - // create a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 + // prepare a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 std::vector<tx_source_entry> sources; for (size_t n = 0; n < inputs; ++n) { @@ -322,7 +283,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry for (size_t m = 0; m <= mixin; ++m) { rct::ctkey ctkey; - ctkey.dest = rct::pk2rct(boost::get<txout_to_key>(blocks[m].miner_tx.vout[0].target).key); + crypto::public_key output_public_key; + cryptonote::get_output_public_key(blocks[m].miner_tx.vout[0], output_public_key); + ctkey.dest = rct::pk2rct(output_public_key); MDEBUG("using " << (m == n ? "real" : "fake") << " input " << ctkey.dest); ctkey.mask = rct::commit(blocks[m].miner_tx.vout[0].amount, rct::identity()); // since those are coinbases, the masks are known src.outputs.push_back(std::make_pair(m, ctkey)); @@ -333,11 +296,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry tx_destination_entry td; td.addr = miner_account[creator].get_keys().m_account_address; td.amount = amount_paid; - std::vector<tx_destination_entry> destinations; + std::vector<tx_destination_entry> destinations; //tx need two outputs since HF_VERSION_MIN_2_OUTPUTS destinations.push_back(td); - cryptonote::account_base dummy; - dummy.generate(); - td.addr = dummy.get_keys().m_account_address; td.amount = 0; destinations.push_back(td); @@ -346,18 +306,11 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry transaction tx; crypto::secret_key tx_key; -#ifdef NO_MULTISIG - rct::multisig_out *msoutp = NULL; -#else - rct::multisig_out msout; - rct::multisig_out *msoutp = &msout; -#endif std::vector<crypto::secret_key> additional_tx_secret_keys; auto sources_copy = sources; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 0 }, msoutp); - CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + multisig::signing::tx_builder_ringct_t tx_builder; + CHECK_AND_ASSERT_MES(tx_builder.init(miner_account[creator].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, false, tx_key, additional_tx_secret_keys, tx), false, "error: multisig::signing::tx_builder_t::init"); -#ifndef NO_MULTISIG // work out the permutation done on sources std::vector<size_t> ins_order; for (size_t n = 0; n < sources.size(); ++n) @@ -371,15 +324,50 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } } CHECK_AND_ASSERT_MES(ins_order.size() == sources.size(), false, "Failed to work out sources permutation"); -#endif -#ifndef NO_MULTISIG + struct { + rct::keyM total_alpha_G; + rct::keyM total_alpha_H; + rct::keyV c_0; + rct::keyV s; + } sig; + { + used_L.clear(); + sig.total_alpha_G.resize(sources.size(), rct::keyV(multisig::signing::kAlphaComponents, rct::identity())); + sig.total_alpha_H.resize(sources.size(), rct::keyV(multisig::signing::kAlphaComponents, rct::identity())); + sig.c_0.resize(sources.size()); + sig.s.resize(sources.size()); + for (std::size_t i = 0; i < sources.size(); ++i) { + rct::keyV alpha(multisig::signing::kAlphaComponents); + for (std::size_t m = 0; m < multisig::signing::kAlphaComponents; ++m) { + alpha[m] = rct::sk2rct(account_k[creator][ins_order[i]][m]); + sig.total_alpha_G[i][m] = rct::pk2rct(account_L[creator][ins_order[i]][m]); + sig.total_alpha_H[i][m] = rct::pk2rct(account_R[creator][ins_order[i]][m]); + for (size_t j = 0; j < total; ++j) { + if (j == creator) + continue; + if (std::find(other_signers.begin(), other_signers.end(), j) == other_signers.end()) + continue; + for (std::size_t n = 0; n < account_L[j][ins_order[i]].size(); ++n) { + if (used_L.find(account_L[j][ins_order[i]][n]) == used_L.end()) { + used_L.insert(account_L[j][ins_order[i]][n]); + rct::addKeys(sig.total_alpha_G[i][m], sig.total_alpha_G[i][m], rct::pk2rct(account_L[j][ins_order[i]][n])); + rct::addKeys(sig.total_alpha_H[i][m], sig.total_alpha_H[i][m], rct::pk2rct(account_R[j][ins_order[i]][n])); + break; + } + } + } + } + CHECK_AND_ASSERT_MES(tx_builder.first_partial_sign(i, sig.total_alpha_G[i], sig.total_alpha_H[i], alpha, sig.c_0[i], sig.s[i]), false, "error: multisig::signing::tx_builder_ringct_t::first_partial_sign"); + } + } + // sign std::unordered_set<crypto::secret_key> used_keys; const std::vector<crypto::secret_key> &msk0 = miner_account[creator].get_multisig_keys(); for (const auto &sk: msk0) - used_keys.insert(sk); - for (size_t signer: signers) + used_keys.insert(sk); //these were used in 'tx_builder.init() -> tx_builder.first_partial_sign()' + for (size_t signer: other_signers) { rct::key skey = rct::zero(); const std::vector<crypto::secret_key> &msk1 = miner_account[signer].get_multisig_keys(); @@ -393,38 +381,37 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } } CHECK_AND_ASSERT_MES(!(skey == rct::zero()), false, "failed to find secret multisig key to sign transaction"); - std::vector<unsigned int> indices; - for (const auto &src: sources_copy) - indices.push_back(src.real_output); - rct::keyV k; - for (size_t tdidx = 0; tdidx < inputs; ++tdidx) - { - k.push_back(rct::zero()); - for (size_t n = 0; n < account_k[signer][tdidx].size(); ++n) - { - crypto::public_key L; - rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][tdidx][n])); - if (used_L.find(L) != used_L.end()) - { - sc_add(k.back().bytes, k.back().bytes, rct::sk2rct(account_k[signer][tdidx][n]).bytes); + rct::keyM k(sources.size(), rct::keyV(multisig::signing::kAlphaComponents)); + for (std::size_t i = 0; i < sources.size(); ++i) { + for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) { + for (std::size_t n = 0; n < account_k[signer][i].size(); ++n) { + crypto::public_key L; + rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][i][n])); + if (used_L.find(L) != used_L.end()) { + k[i][j] = rct::sk2rct(account_k[signer][i][n]); + account_k[signer][i][n] = rct::rct2sk(rct::zero()); //demo: always clear nonces from long-term storage after use + break; + } } + CHECK_AND_ASSERT_MES(!(k[i][j] == rct::zero()), false, "failed to find k to sign transaction"); } - CHECK_AND_ASSERT_MES(!(k.back() == rct::zero()), false, "failed to find k to sign transaction"); } - tools::apply_permutation(ins_order, indices); tools::apply_permutation(ins_order, k); + multisig::signing::tx_builder_ringct_t signer_tx_builder; + CHECK_AND_ASSERT_MES(signer_tx_builder.init(miner_account[signer].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, true, tx_key, additional_tx_secret_keys, tx), false, "error: multisig::signing::tx_builder_t::init"); MDEBUG("signing with k size " << k.size()); - MDEBUG("signing with k " << k.back()); + for (size_t n = 0; n < multisig::signing::kAlphaComponents; ++n) + MDEBUG("signing with k " << k.back()[n]); MDEBUG("signing with sk " << skey); for (const auto &sk: used_keys) MDEBUG(" created with sk " << sk); - MDEBUG("signing with c size " << msout.c.size()); - MDEBUG("signing with c " << msout.c.back()); - r = rct::signMultisig(tx.rct_signatures, indices, k, msout, skey); - CHECK_AND_ASSERT_MES(r, false, "failed to sign transaction"); + CHECK_AND_ASSERT_MES(signer_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, k, skey, sig.c_0, sig.s), false, "error: multisig::signing::tx_builder_ringct_t::next_partial_sign"); + + // in round-robin signing, the last signer finalizes the tx + if (signer == other_signers.back()) + CHECK_AND_ASSERT_MES(signer_tx_builder.finalize_tx(sources, sig.c_0, sig.s, tx), false, "error: multisig::signing::tx_builder_ringct_t::finalize_tx"); } -#endif // verify this tx is really to the expected address const crypto::public_key tx_pub_key2 = get_tx_pub_key_from_extra(tx, 0); @@ -433,10 +420,12 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(r, false, "Failed to generate derivation"); uint64_t n_outs = 0, amount = 0; std::vector<crypto::key_derivation> additional_derivations; + crypto::public_key output_public_key; for (size_t n = 0; n < tx.vout.size(); ++n) { - CHECK_AND_ASSERT_MES(typeid(txout_to_key) == tx.vout[n].target.type(), false, "Unexpected tx out type"); - if (is_out_to_acc_precomp(subaddresses, boost::get<txout_to_key>(tx.vout[n].target).key, derivation, additional_derivations, n, hw::get_device(("default")))) + CHECK_AND_ASSERT_MES(typeid(txout_to_tagged_key) == tx.vout[n].target.type(), false, "Unexpected tx out type"); + cryptonote::get_output_public_key(tx.vout[n], output_public_key); + if (is_out_to_acc_precomp(subaddresses, output_public_key, derivation, additional_derivations, n, hw::get_device(("default")))) { ++n_outs; CHECK_AND_ASSERT_MES(tx.vout[n].amount == 0, false, "Destination amount is not zero"); @@ -451,7 +440,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry amount += rct::h2d(ecdh_info.amount); } } - CHECK_AND_ASSERT_MES(n_outs == 1, false, "Not exactly 1 output was received"); + CHECK_AND_ASSERT_MES(n_outs == 2, false, "Not exactly 2 outputs were received"); CHECK_AND_ASSERT_MES(amount == amount_paid, false, "Amount paid was not the expected amount"); if (post_tx) diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index e9a2cf5f3..948c19458 100644 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -71,7 +71,7 @@ struct gen_multisig_tx_validation_base : public test_chain_unit_base bool generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, - size_t threshold, size_t total, size_t creator, std::vector<size_t> signers, + size_t threshold, size_t total, size_t creator, std::vector<size_t> other_signers, const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx, const std::function<void(cryptonote::transaction &tx)> &post_tx) const; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 0926483fe..4e51ed713 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -229,7 +229,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry std::vector<crypto::secret_key> additional_tx_keys; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; - bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, NULL, use_view_tags); + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, use_view_tags); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx) diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index fdc4753f9..a3b66e835 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -280,5 +280,5 @@ bool construct_tx_rct(tools::wallet2 * sender_wallet, std::vector<cryptonote::tx std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, nullptr); + return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); } |